aboutsummaryrefslogtreecommitdiff
path: root/tests/functional/s390x
diff options
context:
space:
mode:
Diffstat (limited to 'tests/functional/s390x')
-rw-r--r--tests/functional/s390x/meson.build17
-rwxr-xr-xtests/functional/s390x/test_ccw_virtio.py274
-rwxr-xr-xtests/functional/s390x/test_pxelinux.py130
-rwxr-xr-xtests/functional/s390x/test_replay.py28
-rwxr-xr-xtests/functional/s390x/test_topology.py415
-rwxr-xr-xtests/functional/s390x/test_tuxrun.py35
6 files changed, 899 insertions, 0 deletions
diff --git a/tests/functional/s390x/meson.build b/tests/functional/s390x/meson.build
new file mode 100644
index 0000000..70cd36e
--- /dev/null
+++ b/tests/functional/s390x/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+test_s390x_timeouts = {
+ 'ccw_virtio' : 420,
+}
+
+tests_s390x_system_quick = [
+ 'vmstate',
+]
+
+tests_s390x_system_thorough = [
+ 'ccw_virtio',
+ 'pxelinux',
+ 'replay',
+ 'topology',
+ 'tuxrun',
+]
diff --git a/tests/functional/s390x/test_ccw_virtio.py b/tests/functional/s390x/test_ccw_virtio.py
new file mode 100755
index 0000000..453711a
--- /dev/null
+++ b/tests/functional/s390x/test_ccw_virtio.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots an s390x Linux guest with ccw and PCI devices
+# attached and checks whether the devices are recognized by Linux
+#
+# Copyright (c) 2020 Red Hat, Inc.
+#
+# Author:
+# Cornelia Huck <cohuck@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later. See the COPYING file in the top-level directory.
+
+import os
+import tempfile
+
+from qemu_test import QemuSystemTest, Asset
+from qemu_test import exec_command_and_wait_for_pattern
+from qemu_test import wait_for_console_pattern
+
+
+class S390CCWVirtioMachine(QemuSystemTest):
+ KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 '
+
+ timeout = 120
+
+ ASSET_BUSTER_KERNEL = Asset(
+ ('https://snapshot.debian.org/archive/debian/'
+ '20201126T092837Z/dists/buster/main/installer-s390x/'
+ '20190702+deb10u6/images/generic/kernel.debian'),
+ 'd411d17c39ae7ad38d27534376cbe88b68b403c325739364122c2e6f1537e818')
+ ASSET_BUSTER_INITRD = Asset(
+ ('https://snapshot.debian.org/archive/debian/'
+ '20201126T092837Z/dists/buster/main/installer-s390x/'
+ '20190702+deb10u6/images/generic/initrd.debian'),
+ '836bbd0fe6a5ca81274c28c2b063ea315ce1868660866e9b60180c575fef9fd5')
+
+ ASSET_F31_KERNEL = Asset(
+ ('https://archives.fedoraproject.org/pub/archive'
+ '/fedora-secondary/releases/31/Server/s390x/os'
+ '/images/kernel.img'),
+ '480859574f3f44caa6cd35c62d70e1ac0609134e22ce2a954bbed9b110c06e0b')
+ ASSET_F31_INITRD = Asset(
+ ('https://archives.fedoraproject.org/pub/archive'
+ '/fedora-secondary/releases/31/Server/s390x/os'
+ '/images/initrd.img'),
+ '04c46095b2c49020b1c2327158898b7db747e4892ae319726192fb949716aa9c')
+
+ def wait_for_console_pattern(self, success_message, vm=None):
+ wait_for_console_pattern(self, success_message,
+ failure_message='Kernel panic - not syncing',
+ vm=vm)
+
+ def wait_for_crw_reports(self):
+ exec_command_and_wait_for_pattern(self,
+ 'while ! (dmesg -c | grep CRW) ; do sleep 1 ; done',
+ 'CRW reports')
+
+ dmesg_clear_count = 1
+ def clear_guest_dmesg(self):
+ exec_command_and_wait_for_pattern(self, 'dmesg -c > /dev/null; '
+ r'echo dm_clear\ ' + str(self.dmesg_clear_count),
+ r'dm_clear ' + str(self.dmesg_clear_count))
+ self.dmesg_clear_count += 1
+
+ def test_s390x_devices(self):
+ self.set_machine('s390-ccw-virtio')
+
+ kernel_path = self.ASSET_BUSTER_KERNEL.fetch()
+ initrd_path = self.ASSET_BUSTER_INITRD.fetch()
+
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+ 'console=sclp0 root=/dev/ram0 BOOT_DEBUG=3')
+ self.vm.add_args('-nographic',
+ '-kernel', kernel_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line,
+ '-cpu', 'max,prno-trng=off',
+ '-device', 'virtio-net-ccw,devno=fe.1.1111',
+ '-device',
+ 'virtio-rng-ccw,devno=fe.2.0000,max_revision=0,id=rn1',
+ '-device',
+ 'virtio-rng-ccw,devno=fe.3.1234,max_revision=2,id=rn2',
+ '-device', 'zpci,uid=5,target=zzz',
+ '-device', 'virtio-net-pci,id=zzz',
+ '-device', 'zpci,uid=0xa,fid=12,target=serial',
+ '-device', 'virtio-serial-pci,id=serial',
+ '-device', 'virtio-balloon-ccw')
+ self.vm.launch()
+
+ shell_ready = "sh: can't access tty; job control turned off"
+ self.wait_for_console_pattern(shell_ready)
+ # first debug shell is too early, we need to wait for device detection
+ exec_command_and_wait_for_pattern(self, 'exit', shell_ready)
+
+ ccw_bus_ids="0.1.1111 0.2.0000 0.3.1234"
+ pci_bus_ids="0005:00:00.0 000a:00:00.0"
+ exec_command_and_wait_for_pattern(self, 'ls /sys/bus/ccw/devices/',
+ ccw_bus_ids)
+ exec_command_and_wait_for_pattern(self, 'ls /sys/bus/pci/devices/',
+ pci_bus_ids)
+ # check that the device at 0.2.0000 is in legacy mode, while the
+ # device at 0.3.1234 has the virtio-1 feature bit set
+ virtio_rng_features="00000000000000000000000000001100" + \
+ "10000000000000000000000000000000"
+ virtio_rng_features_legacy="00000000000000000000000000001100" + \
+ "00000000000000000000000000000000"
+ exec_command_and_wait_for_pattern(self,
+ 'cat /sys/bus/ccw/devices/0.2.0000/virtio?/features',
+ virtio_rng_features_legacy)
+ exec_command_and_wait_for_pattern(self,
+ 'cat /sys/bus/ccw/devices/0.3.1234/virtio?/features',
+ virtio_rng_features)
+ # check that /dev/hwrng works - and that it's gone after ejecting
+ exec_command_and_wait_for_pattern(self,
+ 'dd if=/dev/hwrng of=/dev/null bs=1k count=10',
+ '10+0 records out')
+ self.clear_guest_dmesg()
+ self.vm.cmd('device_del', id='rn1')
+ self.wait_for_crw_reports()
+ self.clear_guest_dmesg()
+ self.vm.cmd('device_del', id='rn2')
+ self.wait_for_crw_reports()
+ exec_command_and_wait_for_pattern(self,
+ 'dd if=/dev/hwrng of=/dev/null bs=1k count=10',
+ 'dd: /dev/hwrng: No such device')
+ # verify that we indeed have virtio-net devices (without having the
+ # virtio-net driver handy)
+ exec_command_and_wait_for_pattern(self,
+ 'cat /sys/bus/ccw/devices/0.1.1111/cutype',
+ '3832/01')
+ exec_command_and_wait_for_pattern(self,
+ r'cat /sys/bus/pci/devices/0005\:00\:00.0/subsystem_vendor',
+ r'0x1af4')
+ exec_command_and_wait_for_pattern(self,
+ r'cat /sys/bus/pci/devices/0005\:00\:00.0/subsystem_device',
+ r'0x0001')
+ # check fid propagation
+ exec_command_and_wait_for_pattern(self,
+ r'cat /sys/bus/pci/devices/000a\:00\:00.0/function_id',
+ r'0x0000000c')
+ # add another device
+ self.clear_guest_dmesg()
+ self.vm.cmd('device_add', driver='virtio-net-ccw',
+ devno='fe.0.4711', id='net_4711')
+ self.wait_for_crw_reports()
+ exec_command_and_wait_for_pattern(self, 'for i in 1 2 3 4 5 6 7 ; do '
+ 'if [ -e /sys/bus/ccw/devices/*4711 ]; then break; fi ;'
+ 'sleep 1 ; done ; ls /sys/bus/ccw/devices/',
+ '0.0.4711')
+ # and detach it again
+ self.clear_guest_dmesg()
+ self.vm.cmd('device_del', id='net_4711')
+ self.vm.event_wait(name='DEVICE_DELETED',
+ match={'data': {'device': 'net_4711'}})
+ self.wait_for_crw_reports()
+ exec_command_and_wait_for_pattern(self,
+ 'ls /sys/bus/ccw/devices/0.0.4711',
+ 'No such file or directory')
+ # test the virtio-balloon device
+ exec_command_and_wait_for_pattern(self, 'head -n 1 /proc/meminfo',
+ 'MemTotal: 115640 kB')
+ self.vm.cmd('human-monitor-command', command_line='balloon 96')
+ exec_command_and_wait_for_pattern(self, 'head -n 1 /proc/meminfo',
+ 'MemTotal: 82872 kB')
+ self.vm.cmd('human-monitor-command', command_line='balloon 128')
+ exec_command_and_wait_for_pattern(self, 'head -n 1 /proc/meminfo',
+ 'MemTotal: 115640 kB')
+
+
+ def test_s390x_fedora(self):
+ self.set_machine('s390-ccw-virtio')
+
+ kernel_path = self.ASSET_F31_KERNEL.fetch()
+
+ initrd_path = self.uncompress(self.ASSET_F31_INITRD, format="xz")
+
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + ' audit=0 '
+ 'rd.plymouth=0 plymouth.enable=0 rd.rescue')
+ self.vm.add_args('-nographic',
+ '-smp', '4',
+ '-m', '512',
+ '-name', 'Some Guest Name',
+ '-uuid', '30de4fd9-b4d5-409e-86a5-09b387f70bfa',
+ '-kernel', kernel_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line,
+ '-device', 'zpci,uid=7,target=n',
+ '-device', 'virtio-net-pci,id=n,mac=02:ca:fe:fa:ce:12',
+ '-device', 'virtio-rng-ccw,devno=fe.1.9876',
+ '-device', 'virtio-gpu-ccw,devno=fe.2.5432')
+ self.vm.launch()
+ self.wait_for_console_pattern('Kernel command line: %s'
+ % kernel_command_line)
+ self.wait_for_console_pattern('Entering emergency mode')
+
+ # Some tests to see whether the CLI options have been considered:
+ self.log.info("Test whether QEMU CLI options have been considered")
+ exec_command_and_wait_for_pattern(self,
+ 'while ! (dmesg | grep enP7p0s0) ; do sleep 1 ; done',
+ 'virtio_net virtio0 enP7p0s0: renamed')
+ exec_command_and_wait_for_pattern(self, 'lspci',
+ '0007:00:00.0 Class 0200: Device 1af4:1000')
+ exec_command_and_wait_for_pattern(self,
+ 'cat /sys/class/net/enP7p0s0/address',
+ '02:ca:fe:fa:ce:12')
+ exec_command_and_wait_for_pattern(self, 'lscss', '0.1.9876')
+ exec_command_and_wait_for_pattern(self, 'lscss', '0.2.5432')
+ exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
+ 'processors : 4')
+ exec_command_and_wait_for_pattern(self, 'grep MemTotal /proc/meminfo',
+ 'MemTotal: 499848 kB')
+ exec_command_and_wait_for_pattern(self, 'grep Name /proc/sysinfo',
+ 'Extended Name: Some Guest Name')
+ exec_command_and_wait_for_pattern(self, 'grep UUID /proc/sysinfo',
+ '30de4fd9-b4d5-409e-86a5-09b387f70bfa')
+
+ # Disable blinking cursor, then write some stuff into the framebuffer.
+ # QEMU's PPM screendumps contain uncompressed 24-bit values, while the
+ # framebuffer uses 32-bit, so we pad our text with some spaces when
+ # writing to the framebuffer. Since the PPM is uncompressed, we then
+ # can simply read the written "magic bytes" back from the PPM file to
+ # check whether the framebuffer is working as expected.
+ # Unfortunately, this test is flaky, so we don't run it by default
+ if os.getenv('QEMU_TEST_FLAKY_TESTS'):
+ self.log.info("Test screendump of virtio-gpu device")
+ exec_command_and_wait_for_pattern(self,
+ 'while ! (dmesg | grep gpudrmfb) ; do sleep 1 ; done',
+ 'virtio_gpudrmfb frame buffer device')
+ exec_command_and_wait_for_pattern(self,
+ r'echo -e "\e[?25l" > /dev/tty0', ':/#')
+ exec_command_and_wait_for_pattern(self, 'for ((i=0;i<250;i++)); do '
+ 'echo " The qu ick fo x j ump s o ver a laz y d og" >> fox.txt;'
+ 'done',
+ ':/#')
+ exec_command_and_wait_for_pattern(self,
+ 'dd if=fox.txt of=/dev/fb0 bs=1000 oflag=sync,nocache ; rm fox.txt',
+ '12+0 records out')
+ with tempfile.NamedTemporaryFile(suffix='.ppm',
+ prefix='qemu-scrdump-') as ppmfile:
+ self.vm.cmd('screendump', filename=ppmfile.name)
+ ppmfile.seek(0)
+ line = ppmfile.readline()
+ self.assertEqual(line, b"P6\n")
+ line = ppmfile.readline()
+ self.assertEqual(line, b"1280 800\n")
+ line = ppmfile.readline()
+ self.assertEqual(line, b"255\n")
+ line = ppmfile.readline(256)
+ self.assertEqual(line, b"The quick fox jumps over a lazy dog\n")
+ else:
+ self.log.info("Skipped flaky screendump of virtio-gpu device test")
+
+ # Hot-plug a virtio-crypto device and see whether it gets accepted
+ self.log.info("Test hot-plug virtio-crypto device")
+ self.clear_guest_dmesg()
+ self.vm.cmd('object-add', qom_type='cryptodev-backend-builtin',
+ id='cbe0')
+ self.vm.cmd('device_add', driver='virtio-crypto-ccw', id='crypdev0',
+ cryptodev='cbe0', devno='fe.0.2342')
+ exec_command_and_wait_for_pattern(self,
+ 'while ! (dmesg -c | grep Accelerator.device) ; do'
+ ' sleep 1 ; done', 'Accelerator device is ready')
+ exec_command_and_wait_for_pattern(self, 'lscss', '0.0.2342')
+ self.vm.cmd('device_del', id='crypdev0')
+ self.vm.cmd('object-del', id='cbe0')
+ exec_command_and_wait_for_pattern(self,
+ 'while ! (dmesg -c | grep Start.virtcrypto_remove) ; do'
+ ' sleep 1 ; done', 'Start virtcrypto_remove.')
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
diff --git a/tests/functional/s390x/test_pxelinux.py b/tests/functional/s390x/test_pxelinux.py
new file mode 100755
index 0000000..c00cce6
--- /dev/null
+++ b/tests/functional/s390x/test_pxelinux.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+'''
+Functional test that checks the pxelinux.cfg network booting of a s390x VM
+(TFTP booting without config file is already tested by the pxe qtest, so
+we don't repeat that here).
+'''
+
+import os
+import shutil
+
+from qemu_test import QemuSystemTest, Asset, wait_for_console_pattern
+
+
+PXELINUX_CFG_CONTENTS='''# pxelinux.cfg style config file
+default Debian
+label Nonexisting
+kernel kernel.notavailable
+initrd initrd.notavailable
+label Debian
+kernel kernel.debian
+initrd initrd.debian
+append testoption=teststring
+label Fedora
+kernel kernel.fedora
+'''
+
+class S390PxeLinux(QemuSystemTest):
+ '''
+ Test various ways of booting via a pxelinux.cfg file, for details see:
+ https://wiki.syslinux.org/wiki/index.php?title=PXELINUX#Configuration
+ '''
+
+ ASSET_DEBIAN_KERNEL = Asset(
+ ('https://snapshot.debian.org/archive/debian/'
+ '20201126T092837Z/dists/buster/main/installer-s390x/'
+ '20190702+deb10u6/images/generic/kernel.debian'),
+ 'd411d17c39ae7ad38d27534376cbe88b68b403c325739364122c2e6f1537e818')
+
+ ASSET_DEBIAN_INITRD = Asset(
+ ('https://snapshot.debian.org/archive/debian/'
+ '20201126T092837Z/dists/buster/main/installer-s390x/'
+ '20190702+deb10u6/images/generic/initrd.debian'),
+ '836bbd0fe6a5ca81274c28c2b063ea315ce1868660866e9b60180c575fef9fd5')
+
+ ASSET_FEDORA_KERNEL = Asset(
+ ('https://archives.fedoraproject.org/pub/archive'
+ '/fedora-secondary/releases/31/Server/s390x/os'
+ '/images/kernel.img'),
+ '480859574f3f44caa6cd35c62d70e1ac0609134e22ce2a954bbed9b110c06e0b')
+
+ def pxelinux_launch(self, pl_name='default', extra_opts=None):
+ '''Create a pxelinux.cfg file in the right location and launch QEMU'''
+ self.require_netdev('user')
+ self.set_machine('s390-ccw-virtio')
+
+ debian_kernel = self.ASSET_DEBIAN_KERNEL.fetch()
+ debian_initrd = self.ASSET_DEBIAN_INITRD.fetch()
+ fedora_kernel = self.ASSET_FEDORA_KERNEL.fetch()
+
+ # Prepare a folder for the TFTP "server":
+ tftpdir = self.scratch_file('tftp')
+ shutil.rmtree(tftpdir, ignore_errors=True) # Remove stale stuff
+ os.mkdir(tftpdir)
+ shutil.copy(debian_kernel, os.path.join(tftpdir, 'kernel.debian'))
+ shutil.copy(debian_initrd, os.path.join(tftpdir, 'initrd.debian'))
+ shutil.copy(fedora_kernel, os.path.join(tftpdir, 'kernel.fedora'))
+
+ pxelinuxdir = self.scratch_file('tftp', 'pxelinux.cfg')
+ os.mkdir(pxelinuxdir)
+
+ cfg_fname = self.scratch_file('tftp', 'pxelinux.cfg', pl_name)
+ with open(cfg_fname, 'w', encoding='utf-8') as f:
+ f.write(PXELINUX_CFG_CONTENTS)
+
+ virtio_net_dev = 'virtio-net-ccw,netdev=n1,bootindex=1'
+ if extra_opts:
+ virtio_net_dev += ',' + extra_opts
+
+ self.vm.add_args('-m', '384',
+ '-netdev', f'user,id=n1,tftp={tftpdir}',
+ '-device', virtio_net_dev)
+ self.vm.set_console()
+ self.vm.launch()
+
+
+ def test_default(self):
+ '''Check whether the guest uses the "default" file name'''
+ self.pxelinux_launch()
+ # The kernel prints its arguments to the console, so we can use
+ # this to check whether the kernel parameters are correctly handled:
+ wait_for_console_pattern(self, 'testoption=teststring')
+ # Now also check that we've successfully loaded the initrd:
+ wait_for_console_pattern(self, 'Unpacking initramfs...')
+ wait_for_console_pattern(self, 'Run /init as init process')
+
+ def test_mac(self):
+ '''Check whether the guest uses file name based on its MAC address'''
+ self.pxelinux_launch(pl_name='01-02-ca-fe-ba-be-42',
+ extra_opts='mac=02:ca:fe:ba:be:42,loadparm=3')
+ wait_for_console_pattern(self, 'Linux version 5.3.7-301.fc31.s390x')
+
+ def test_uuid(self):
+ '''Check whether the guest uses file name based on its UUID'''
+ # Also add a non-bootable disk to check the fallback to network boot:
+ self.vm.add_args('-blockdev', 'null-co,size=65536,node-name=d1',
+ '-device', 'virtio-blk,drive=d1,bootindex=0,loadparm=1',
+ '-uuid', '550e8400-e29b-11d4-a716-446655441234')
+ self.pxelinux_launch(pl_name='550e8400-e29b-11d4-a716-446655441234')
+ wait_for_console_pattern(self, 'Debian 4.19.146-1 (2020-09-17)')
+
+ def test_ip(self):
+ '''Check whether the guest uses file name based on its IP address'''
+ self.vm.add_args('-M', 'loadparm=3')
+ self.pxelinux_launch(pl_name='0A00020F')
+ wait_for_console_pattern(self, 'Linux version 5.3.7-301.fc31.s390x')
+
+ def test_menu(self):
+ '''Check whether the boot menu works for pxelinux.cfg booting'''
+ self.vm.add_args('-boot', 'menu=on,splash-time=10')
+ self.pxelinux_launch(pl_name='0A00')
+ wait_for_console_pattern(self, '[1] Nonexisting')
+ wait_for_console_pattern(self, '[2] Debian')
+ wait_for_console_pattern(self, '[3] Fedora')
+ wait_for_console_pattern(self, 'Debian 4.19.146-1 (2020-09-17)')
+
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
diff --git a/tests/functional/s390x/test_replay.py b/tests/functional/s390x/test_replay.py
new file mode 100755
index 0000000..33b5843
--- /dev/null
+++ b/tests/functional/s390x/test_replay.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+#
+# Replay test that boots a Linux kernel on an s390x machine
+# and checks the console
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset
+from replay_kernel import ReplayKernelBase
+
+
+class S390xReplay(ReplayKernelBase):
+
+ ASSET_KERNEL = Asset(
+ ('https://archives.fedoraproject.org/pub/archive/fedora-secondary/'
+ 'releases/29/Everything/s390x/os/images/kernel.img'),
+ 'dace03b8ae0c9f670ebb9b8d6ce5eb24b62987f346de8f1300a439bb00bb99e7')
+
+ def test_s390_ccw_virtio(self):
+ self.set_machine('s390-ccw-virtio')
+ kernel_path = self.ASSET_KERNEL.fetch()
+ kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=sclp0'
+ console_pattern = 'Kernel command line: %s' % kernel_command_line
+ self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=9)
+
+
+if __name__ == '__main__':
+ ReplayKernelBase.main()
diff --git a/tests/functional/s390x/test_topology.py b/tests/functional/s390x/test_topology.py
new file mode 100755
index 0000000..1b5dc65
--- /dev/null
+++ b/tests/functional/s390x/test_topology.py
@@ -0,0 +1,415 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots a Linux kernel and checks the console
+#
+# Copyright IBM Corp. 2023
+#
+# Author:
+# Pierre Morel <pmorel@linux.ibm.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later. See the COPYING file in the top-level directory.
+
+from qemu_test import QemuSystemTest, Asset
+from qemu_test import exec_command
+from qemu_test import exec_command_and_wait_for_pattern
+from qemu_test import wait_for_console_pattern
+
+
+class S390CPUTopology(QemuSystemTest):
+ """
+ S390x CPU topology consists of 4 topology layers, from bottom to top,
+ the cores, sockets, books and drawers and 2 modifiers attributes,
+ the entitlement and the dedication.
+ See: docs/system/s390x/cpu-topology.rst.
+
+ S390x CPU topology is setup in different ways:
+ - implicitly from the '-smp' argument by completing each topology
+ level one after the other beginning with drawer 0, book 0 and
+ socket 0.
+ - explicitly from the '-device' argument on the QEMU command line
+ - explicitly by hotplug of a new CPU using QMP or HMP
+ - it is modified by using QMP 'set-cpu-topology'
+
+ The S390x modifier attribute entitlement depends on the machine
+ polarization, which can be horizontal or vertical.
+ The polarization is changed on a request from the guest.
+ """
+ timeout = 90
+ event_timeout = 10
+
+ KERNEL_COMMON_COMMAND_LINE = ('printk.time=0 '
+ 'root=/dev/ram '
+ 'selinux=0 '
+ 'rdinit=/bin/sh')
+ ASSET_F35_KERNEL = Asset(
+ ('https://archives.fedoraproject.org/pub/archive'
+ '/fedora-secondary/releases/35/Server/s390x/os'
+ '/images/kernel.img'),
+ '1f2dddfd11bb1393dd2eb2e784036fbf6fc11057a6d7d27f9eb12d3edc67ef73')
+
+ ASSET_F35_INITRD = Asset(
+ ('https://archives.fedoraproject.org/pub/archive'
+ '/fedora-secondary/releases/35/Server/s390x/os'
+ '/images/initrd.img'),
+ '1100145fbca00240c8c372ae4b89b48c99844bc189b3dfbc3f481dc60055ca46')
+
+ def wait_until_booted(self):
+ wait_for_console_pattern(self, 'no job control',
+ failure_message='Kernel panic - not syncing',
+ vm=None)
+
+ def check_topology(self, c, s, b, d, e, t):
+ res = self.vm.qmp('query-cpus-fast')
+ cpus = res['return']
+ for cpu in cpus:
+ core = cpu['props']['core-id']
+ socket = cpu['props']['socket-id']
+ book = cpu['props']['book-id']
+ drawer = cpu['props']['drawer-id']
+ entitlement = cpu.get('entitlement')
+ dedicated = cpu.get('dedicated')
+ if core == c:
+ self.assertEqual(drawer, d)
+ self.assertEqual(book, b)
+ self.assertEqual(socket, s)
+ self.assertEqual(entitlement, e)
+ self.assertEqual(dedicated, t)
+
+ def kernel_init(self):
+ """
+ We need a VM that supports CPU topology,
+ currently this only the case when using KVM, not TCG.
+ We need a kernel supporting the CPU topology.
+ We need a minimal root filesystem with a shell.
+ """
+ self.require_accelerator("kvm")
+ kernel_path = self.ASSET_F35_KERNEL.fetch()
+ initrd_path = self.uncompress(self.ASSET_F35_INITRD, format="xz")
+
+ self.vm.set_console()
+ kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE
+ self.vm.add_args('-nographic',
+ '-enable-kvm',
+ '-cpu', 'max,ctop=on',
+ '-m', '512',
+ '-kernel', kernel_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line)
+
+ def system_init(self):
+ self.log.info("System init")
+ exec_command_and_wait_for_pattern(self,
+ """ mount proc -t proc /proc;
+ mount sys -t sysfs /sys;
+ cat /sys/devices/system/cpu/dispatching """,
+ '0')
+
+ def test_single(self):
+ """
+ This test checks the simplest topology with a single CPU.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.launch()
+ self.wait_until_booted()
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+ def test_default(self):
+ """
+ This test checks the implicit topology.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.add_args('-smp',
+ '13,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
+ self.vm.launch()
+ self.wait_until_booted()
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+ self.check_topology(1, 0, 0, 0, 'medium', False)
+ self.check_topology(2, 1, 0, 0, 'medium', False)
+ self.check_topology(3, 1, 0, 0, 'medium', False)
+ self.check_topology(4, 2, 0, 0, 'medium', False)
+ self.check_topology(5, 2, 0, 0, 'medium', False)
+ self.check_topology(6, 0, 1, 0, 'medium', False)
+ self.check_topology(7, 0, 1, 0, 'medium', False)
+ self.check_topology(8, 1, 1, 0, 'medium', False)
+ self.check_topology(9, 1, 1, 0, 'medium', False)
+ self.check_topology(10, 2, 1, 0, 'medium', False)
+ self.check_topology(11, 2, 1, 0, 'medium', False)
+ self.check_topology(12, 0, 0, 1, 'medium', False)
+
+ def test_move(self):
+ """
+ This test checks the topology modification by moving a CPU
+ to another socket: CPU 0 is moved from socket 0 to socket 2.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.add_args('-smp',
+ '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'socket-id': 2, 'entitlement': 'low'})
+ self.assertEqual(res['return'], {})
+ self.check_topology(0, 2, 0, 0, 'low', False)
+
+ def test_dash_device(self):
+ """
+ This test verifies that a CPU defined with the '-device'
+ command line option finds its right place inside the topology.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.add_args('-smp',
+ '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
+ self.vm.add_args('-device', 'max-s390x-cpu,core-id=10')
+ self.vm.add_args('-device',
+ 'max-s390x-cpu,'
+ 'core-id=1,socket-id=0,book-id=1,drawer-id=1,entitlement=low')
+ self.vm.add_args('-device',
+ 'max-s390x-cpu,'
+ 'core-id=2,socket-id=0,book-id=1,drawer-id=1,entitlement=medium')
+ self.vm.add_args('-device',
+ 'max-s390x-cpu,'
+ 'core-id=3,socket-id=1,book-id=1,drawer-id=1,entitlement=high')
+ self.vm.add_args('-device',
+ 'max-s390x-cpu,'
+ 'core-id=4,socket-id=1,book-id=1,drawer-id=1')
+ self.vm.add_args('-device',
+ 'max-s390x-cpu,'
+ 'core-id=5,socket-id=2,book-id=1,drawer-id=1,dedicated=true')
+
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.check_topology(10, 2, 1, 0, 'medium', False)
+ self.check_topology(1, 0, 1, 1, 'low', False)
+ self.check_topology(2, 0, 1, 1, 'medium', False)
+ self.check_topology(3, 1, 1, 1, 'high', False)
+ self.check_topology(4, 1, 1, 1, 'medium', False)
+ self.check_topology(5, 2, 1, 1, 'high', True)
+
+
+ def guest_set_dispatching(self, dispatching):
+ exec_command(self,
+ f'echo {dispatching} > /sys/devices/system/cpu/dispatching')
+ self.vm.event_wait('CPU_POLARIZATION_CHANGE', self.event_timeout)
+ exec_command_and_wait_for_pattern(self,
+ 'cat /sys/devices/system/cpu/dispatching', dispatching)
+
+
+ def test_polarization(self):
+ """
+ This test verifies that QEMU modifies the entitlement change after
+ several guest polarization change requests.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.system_init()
+ res = self.vm.qmp('query-s390x-cpu-polarization')
+ self.assertEqual(res['return']['polarization'], 'horizontal')
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+ self.guest_set_dispatching('1')
+ res = self.vm.qmp('query-s390x-cpu-polarization')
+ self.assertEqual(res['return']['polarization'], 'vertical')
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+ self.guest_set_dispatching('0')
+ res = self.vm.qmp('query-s390x-cpu-polarization')
+ self.assertEqual(res['return']['polarization'], 'horizontal')
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+
+ def check_polarization(self, polarization):
+ #We need to wait for the change to have been propagated to the kernel
+ exec_command_and_wait_for_pattern(self,
+ "\n".join([
+ "timeout 1 sh -c 'while true",
+ 'do',
+ ' syspath="/sys/devices/system/cpu/cpu0/polarization"',
+ ' polarization="$(cat "$syspath")" || exit',
+ f' if [ "$polarization" = "{polarization}" ]; then',
+ ' exit 0',
+ ' fi',
+ ' sleep 0.01',
+ #searched for strings mustn't show up in command, '' to obfuscate
+ "done' && echo succ''ess || echo fail''ure",
+ ]),
+ "success", "failure")
+
+
+ def test_entitlement(self):
+ """
+ This test verifies that QEMU modifies the entitlement
+ after a guest request and that the guest sees the change.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.system_init()
+
+ self.check_polarization('horizontal')
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+ self.guest_set_dispatching('1')
+ self.check_polarization('vertical:medium')
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'low'})
+ self.assertEqual(res['return'], {})
+ self.check_polarization('vertical:low')
+ self.check_topology(0, 0, 0, 0, 'low', False)
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'medium'})
+ self.assertEqual(res['return'], {})
+ self.check_polarization('vertical:medium')
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'high'})
+ self.assertEqual(res['return'], {})
+ self.check_polarization('vertical:high')
+ self.check_topology(0, 0, 0, 0, 'high', False)
+
+ self.guest_set_dispatching('0')
+ self.check_polarization("horizontal")
+ self.check_topology(0, 0, 0, 0, 'high', False)
+
+
+ def test_dedicated(self):
+ """
+ This test verifies that QEMU adjusts the entitlement correctly when a
+ CPU is made dedicated.
+ QEMU retains the entitlement value when horizontal polarization is in effect.
+ For the guest, the field shows the effective value of the entitlement.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.system_init()
+
+ self.check_polarization("horizontal")
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'dedicated': True})
+ self.assertEqual(res['return'], {})
+ self.check_topology(0, 0, 0, 0, 'high', True)
+ self.check_polarization("horizontal")
+
+ self.guest_set_dispatching('1')
+ self.check_topology(0, 0, 0, 0, 'high', True)
+ self.check_polarization("vertical:high")
+
+ self.guest_set_dispatching('0')
+ self.check_topology(0, 0, 0, 0, 'high', True)
+ self.check_polarization("horizontal")
+
+
+ def test_socket_full(self):
+ """
+ This test verifies that QEMU does not accept to overload a socket.
+ The socket-id 0 on book-id 0 already contains CPUs 0 and 1 and can
+ not accept any new CPU while socket-id 0 on book-id 1 is free.
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.add_args('-smp',
+ '3,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.system_init()
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 2, 'socket-id': 0, 'book-id': 0})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 2, 'socket-id': 0, 'book-id': 1})
+ self.assertEqual(res['return'], {})
+
+ def test_dedicated_error(self):
+ """
+ This test verifies that QEMU refuses to lower the entitlement
+ of a dedicated CPU
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.system_init()
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'dedicated': True})
+ self.assertEqual(res['return'], {})
+
+ self.check_topology(0, 0, 0, 0, 'high', True)
+
+ self.guest_set_dispatching('1')
+
+ self.check_topology(0, 0, 0, 0, 'high', True)
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'low', 'dedicated': True})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'low'})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'medium', 'dedicated': True})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'medium'})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'low', 'dedicated': False})
+ self.assertEqual(res['return'], {})
+
+ res = self.vm.qmp('set-cpu-topology',
+ {'core-id': 0, 'entitlement': 'medium', 'dedicated': False})
+ self.assertEqual(res['return'], {})
+
+ def test_move_error(self):
+ """
+ This test verifies that QEMU refuses to move a CPU to an
+ nonexistent location
+ """
+ self.set_machine('s390-ccw-virtio')
+ self.kernel_init()
+ self.vm.launch()
+ self.wait_until_booted()
+
+ self.system_init()
+
+ res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'drawer-id': 1})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'book-id': 1})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'socket-id': 1})
+ self.assertEqual(res['error']['class'], 'GenericError')
+
+ self.check_topology(0, 0, 0, 0, 'medium', False)
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
diff --git a/tests/functional/s390x/test_tuxrun.py b/tests/functional/s390x/test_tuxrun.py
new file mode 100755
index 0000000..8df3c68
--- /dev/null
+++ b/tests/functional/s390x/test_tuxrun.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots known good tuxboot images the same way
+# that tuxrun (www.tuxrun.org) does. This tool is used by things like
+# the LKFT project to run regression tests on kernels.
+#
+# Copyright (c) 2023 Linaro Ltd.
+#
+# Author:
+# Alex Bennée <alex.bennee@linaro.org>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset
+from qemu_test.tuxruntest import TuxRunBaselineTest
+
+class TuxRunS390xTest(TuxRunBaselineTest):
+
+ ASSET_S390X_KERNEL = Asset(
+ 'https://storage.tuxboot.com/buildroot/20241119/s390/bzImage',
+ 'ee67e91db52a2aed104a7c72b2a08987c678f8179c029626789c35d6dd0fedf1')
+ ASSET_S390X_ROOTFS = Asset(
+ 'https://storage.tuxboot.com/buildroot/20241119/s390/rootfs.ext4.zst',
+ 'bff7971fc2fef56372d98afe4557b82fd0a785a241e44c29b058e577ad1bbb44')
+
+ def test_s390(self):
+ self.set_machine('s390-ccw-virtio')
+ self.wait_for_shutdown=False
+ self.common_tuxrun(kernel_asset=self.ASSET_S390X_KERNEL,
+ rootfs_asset=self.ASSET_S390X_ROOTFS,
+ drive="virtio-blk-ccw",
+ haltmsg="Requesting system halt")
+
+if __name__ == '__main__':
+ TuxRunBaselineTest.main()