diff options
Diffstat (limited to 'tests/avocado')
71 files changed, 13807 insertions, 0 deletions
diff --git a/tests/avocado/README.rst b/tests/avocado/README.rst new file mode 100644 index 0000000000..94488371bb --- /dev/null +++ b/tests/avocado/README.rst @@ -0,0 +1,10 @@ +============================================= +Integration tests using the Avocado Framework +============================================= + +This directory contains integration tests. They're usually higher +level, and may interact with external resources and with various +guest operating systems. + +For more information, please refer to ``docs/devel/testing.rst``, +section "Integration tests using the Avocado Framework". diff --git a/tests/avocado/acpi-bits.py b/tests/avocado/acpi-bits.py new file mode 100644 index 0000000000..efe4f52ee0 --- /dev/null +++ b/tests/avocado/acpi-bits.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python3 +# group: rw quick +# Exercise QEMU generated ACPI/SMBIOS tables using biosbits, +# https://biosbits.org/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +# Author: +# Ani Sinha <anisinha@redhat.com> + +# pylint: disable=invalid-name +# pylint: disable=consider-using-f-string + +""" +This is QEMU ACPI/SMBIOS avocado tests using biosbits. +Biosbits is available originally at https://biosbits.org/. +This test uses a fork of the upstream bits and has numerous fixes +including an upgraded acpica. The fork is located here: +https://gitlab.com/qemu-project/biosbits-bits . +""" + +import logging +import os +import platform +import re +import shutil +import subprocess +import tarfile +import tempfile +import time +import zipfile +from typing import ( + List, + Optional, + Sequence, +) +from qemu.machine import QEMUMachine +from avocado import skipIf +from avocado.utils import datadrainer as drainer +from avocado_qemu import QemuBaseTest + +deps = ["xorriso", "mformat"] # dependent tools needed in the test setup/box. +supported_platforms = ['x86_64'] # supported test platforms. + +# default timeout of 120 secs is sometimes not enough for bits test. +BITS_TIMEOUT = 200 + +def which(tool): + """ looks up the full path for @tool, returns None if not found + or if @tool does not have executable permissions. + """ + paths=os.getenv('PATH') + for p in paths.split(os.path.pathsep): + p = os.path.join(p, tool) + if os.path.exists(p) and os.access(p, os.X_OK): + return p + return None + +def missing_deps(): + """ returns True if any of the test dependent tools are absent. + """ + for dep in deps: + if which(dep) is None: + return True + return False + +def supported_platform(): + """ checks if the test is running on a supported platform. + """ + return platform.machine() in supported_platforms + +class QEMUBitsMachine(QEMUMachine): # pylint: disable=too-few-public-methods + """ + A QEMU VM, with isa-debugcon enabled and bits iso passed + using -cdrom to QEMU commandline. + + """ + def __init__(self, + binary: str, + args: Sequence[str] = (), + wrapper: Sequence[str] = (), + name: Optional[str] = None, + base_temp_dir: str = "/var/tmp", + debugcon_log: str = "debugcon-log.txt", + debugcon_addr: str = "0x403", + qmp_timer: Optional[float] = None): + # pylint: disable=too-many-arguments + + if name is None: + name = "qemu-bits-%d" % os.getpid() + super().__init__(binary, args, wrapper=wrapper, name=name, + base_temp_dir=base_temp_dir, + qmp_timer=qmp_timer) + self.debugcon_log = debugcon_log + self.debugcon_addr = debugcon_addr + self.base_temp_dir = base_temp_dir + + @property + def _base_args(self) -> List[str]: + args = super()._base_args + args.extend([ + '-chardev', + 'file,path=%s,id=debugcon' %os.path.join(self.base_temp_dir, + self.debugcon_log), + '-device', + 'isa-debugcon,iobase=%s,chardev=debugcon' %self.debugcon_addr, + ]) + return args + + def base_args(self): + """return the base argument to QEMU binary""" + return self._base_args + +@skipIf(not supported_platform() or missing_deps(), + 'unsupported platform or dependencies (%s) not installed' \ + % ','.join(deps)) +class AcpiBitsTest(QemuBaseTest): #pylint: disable=too-many-instance-attributes + """ + ACPI and SMBIOS tests using biosbits. + + :avocado: tags=arch:x86_64 + :avocado: tags=acpi + + """ + # in slower systems the test can take as long as 3 minutes to complete. + timeout = BITS_TIMEOUT + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._vm = None + self._workDir = None + self._baseDir = None + + # following are some standard configuration constants + self._bitsInternalVer = 2020 # gitlab CI does shallow clones of depth 20 + self._bitsCommitHash = 'c7920d2b' # commit hash must match + # the artifact tag below + self._bitsTag = "qemu-bits-10262023" # this is the latest bits + # release as of today. + self._bitsArtSHA1Hash = 'b22cdfcfc7453875297d06d626f5474ee36a343f' + self._bitsArtURL = ("https://gitlab.com/qemu-project/" + "biosbits-bits/-/jobs/artifacts/%s/" + "download?job=qemu-bits-build" %self._bitsTag) + self._debugcon_addr = '0x403' + self._debugcon_log = 'debugcon-log.txt' + logging.basicConfig(level=logging.INFO) + self.logger = logging.getLogger('acpi-bits') + + def _print_log(self, log): + self.logger.info('\nlogs from biosbits follows:') + self.logger.info('==========================================\n') + self.logger.info(log) + self.logger.info('==========================================\n') + + def copy_bits_config(self): + """ copies the bios bits config file into bits. + """ + config_file = 'bits-cfg.txt' + bits_config_dir = os.path.join(self._baseDir, 'acpi-bits', + 'bits-config') + target_config_dir = os.path.join(self._workDir, + 'bits-%d' %self._bitsInternalVer, + 'boot') + self.assertTrue(os.path.exists(bits_config_dir)) + self.assertTrue(os.path.exists(target_config_dir)) + self.assertTrue(os.access(os.path.join(bits_config_dir, + config_file), os.R_OK)) + shutil.copy2(os.path.join(bits_config_dir, config_file), + target_config_dir) + self.logger.info('copied config file %s to %s', + config_file, target_config_dir) + + def copy_test_scripts(self): + """copies the python test scripts into bits. """ + + bits_test_dir = os.path.join(self._baseDir, 'acpi-bits', + 'bits-tests') + target_test_dir = os.path.join(self._workDir, + 'bits-%d' %self._bitsInternalVer, + 'boot', 'python') + + self.assertTrue(os.path.exists(bits_test_dir)) + self.assertTrue(os.path.exists(target_test_dir)) + + for filename in os.listdir(bits_test_dir): + if os.path.isfile(os.path.join(bits_test_dir, filename)) and \ + filename.endswith('.py2'): + # all test scripts are named with extension .py2 so that + # avocado does not try to load them. These scripts are + # written for python 2.7 not python 3 and hence if avocado + # loaded them, it would complain about python 3 specific + # syntaxes. + newfilename = os.path.splitext(filename)[0] + '.py' + shutil.copy2(os.path.join(bits_test_dir, filename), + os.path.join(target_test_dir, newfilename)) + self.logger.info('copied test file %s to %s', + filename, target_test_dir) + + # now remove the pyc test file if it exists, otherwise the + # changes in the python test script won't be executed. + testfile_pyc = os.path.splitext(filename)[0] + '.pyc' + if os.access(os.path.join(target_test_dir, testfile_pyc), + os.F_OK): + os.remove(os.path.join(target_test_dir, testfile_pyc)) + self.logger.info('removed compiled file %s', + os.path.join(target_test_dir, + testfile_pyc)) + + def fix_mkrescue(self, mkrescue): + """ grub-mkrescue is a bash script with two variables, 'prefix' and + 'libdir'. They must be pointed to the right location so that the + iso can be generated appropriately. We point the two variables to + the directory where we have extracted our pre-built bits grub + tarball. + """ + grub_x86_64_mods = os.path.join(self._workDir, 'grub-inst-x86_64-efi') + grub_i386_mods = os.path.join(self._workDir, 'grub-inst') + + self.assertTrue(os.path.exists(grub_x86_64_mods)) + self.assertTrue(os.path.exists(grub_i386_mods)) + + new_script = "" + with open(mkrescue, 'r', encoding='utf-8') as filehandle: + orig_script = filehandle.read() + new_script = re.sub('(^prefix=)(.*)', + r'\1"%s"' %grub_x86_64_mods, + orig_script, flags=re.M) + new_script = re.sub('(^libdir=)(.*)', r'\1"%s/lib"' %grub_i386_mods, + new_script, flags=re.M) + + with open(mkrescue, 'w', encoding='utf-8') as filehandle: + filehandle.write(new_script) + + def generate_bits_iso(self): + """ Uses grub-mkrescue to generate a fresh bits iso with the python + test scripts + """ + bits_dir = os.path.join(self._workDir, + 'bits-%d' %self._bitsInternalVer) + iso_file = os.path.join(self._workDir, + 'bits-%d.iso' %self._bitsInternalVer) + mkrescue_script = os.path.join(self._workDir, + 'grub-inst-x86_64-efi', 'bin', + 'grub-mkrescue') + + self.assertTrue(os.access(mkrescue_script, + os.R_OK | os.W_OK | os.X_OK)) + + self.fix_mkrescue(mkrescue_script) + + self.logger.info('using grub-mkrescue for generating biosbits iso ...') + + try: + if os.getenv('V') or os.getenv('BITS_DEBUG'): + subprocess.check_call([mkrescue_script, '-o', iso_file, + bits_dir], stderr=subprocess.STDOUT) + else: + subprocess.check_call([mkrescue_script, '-o', + iso_file, bits_dir], + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL) + except Exception as e: # pylint: disable=broad-except + self.skipTest("Error while generating the bits iso. " + "Pass V=1 in the environment to get more details. " + + str(e)) + + self.assertTrue(os.access(iso_file, os.R_OK)) + + self.logger.info('iso file %s successfully generated.', iso_file) + + def setUp(self): # pylint: disable=arguments-differ + super().setUp('qemu-system-') + + self._baseDir = os.getenv('AVOCADO_TEST_BASEDIR') + + # workdir could also be avocado's own workdir in self.workdir. + # At present, I prefer to maintain my own temporary working + # directory. It gives us more control over the generated bits + # log files and also for debugging, we may chose not to remove + # this working directory so that the logs and iso can be + # inspected manually and archived if needed. + self._workDir = tempfile.mkdtemp(prefix='acpi-bits-', + suffix='.tmp') + self.logger.info('working dir: %s', self._workDir) + + prebuiltDir = os.path.join(self._workDir, 'prebuilt') + if not os.path.isdir(prebuiltDir): + os.mkdir(prebuiltDir, mode=0o775) + + bits_zip_file = os.path.join(prebuiltDir, 'bits-%d-%s.zip' + %(self._bitsInternalVer, + self._bitsCommitHash)) + grub_tar_file = os.path.join(prebuiltDir, + 'bits-%d-%s-grub.tar.gz' + %(self._bitsInternalVer, + self._bitsCommitHash)) + + bitsLocalArtLoc = self.fetch_asset(self._bitsArtURL, + asset_hash=self._bitsArtSHA1Hash) + self.logger.info("downloaded bits artifacts to %s", bitsLocalArtLoc) + + # extract the bits artifact in the temp working directory + with zipfile.ZipFile(bitsLocalArtLoc, 'r') as zref: + zref.extractall(prebuiltDir) + + # extract the bits software in the temp working directory + with zipfile.ZipFile(bits_zip_file, 'r') as zref: + zref.extractall(self._workDir) + + with tarfile.open(grub_tar_file, 'r', encoding='utf-8') as tarball: + tarball.extractall(self._workDir) + + self.copy_test_scripts() + self.copy_bits_config() + self.generate_bits_iso() + + def parse_log(self): + """parse the log generated by running bits tests and + check for failures. + """ + debugconf = os.path.join(self._workDir, self._debugcon_log) + log = "" + with open(debugconf, 'r', encoding='utf-8') as filehandle: + log = filehandle.read() + + matchiter = re.finditer(r'(.*Summary: )(\d+ passed), (\d+ failed).*', + log) + for match in matchiter: + # verify that no test cases failed. + try: + self.assertEqual(match.group(3).split()[0], '0', + 'Some bits tests seems to have failed. ' \ + 'Please check the test logs for more info.') + except AssertionError as e: + self._print_log(log) + raise e + else: + if os.getenv('V') or os.getenv('BITS_DEBUG'): + self._print_log(log) + + def tearDown(self): + """ + Lets do some cleanups. + """ + if self._vm: + self.assertFalse(not self._vm.is_running) + if not os.getenv('BITS_DEBUG') and self._workDir: + self.logger.info('removing the work directory %s', self._workDir) + shutil.rmtree(self._workDir) + else: + self.logger.info('not removing the work directory %s ' \ + 'as BITS_DEBUG is ' \ + 'passed in the environment', self._workDir) + super().tearDown() + + def test_acpi_smbios_bits(self): + """The main test case implementation.""" + + iso_file = os.path.join(self._workDir, + 'bits-%d.iso' %self._bitsInternalVer) + + self.assertTrue(os.access(iso_file, os.R_OK)) + + self._vm = QEMUBitsMachine(binary=self.qemu_bin, + base_temp_dir=self._workDir, + debugcon_log=self._debugcon_log, + debugcon_addr=self._debugcon_addr) + + self._vm.add_args('-cdrom', '%s' %iso_file) + # the vm needs to be run under icount so that TCG emulation is + # consistent in terms of timing. smilatency tests have consistent + # timing requirements. + self._vm.add_args('-icount', 'auto') + # currently there is no support in bits for recognizing 64-bit SMBIOS + # entry points. QEMU defaults to 64-bit entry points since the + # upstream commit bf376f3020 ("hw/i386/pc: Default to use SMBIOS 3.0 + # for newer machine models"). Therefore, enforce 32-bit entry point. + self._vm.add_args('-machine', 'smbios-entry-point-type=32') + + # enable console logging + self._vm.set_console() + self._vm.launch() + + self.logger.debug("Console output from bits VM follows ...") + c_drainer = drainer.LineLogger(self._vm.console_socket.fileno(), + logger=self.logger.getChild("console"), + stop_check=(lambda : + not self._vm.is_running())) + c_drainer.start() + + # biosbits has been configured to run all the specified test suites + # in batch mode and then automatically initiate a vm shutdown. + # Set timeout to BITS_TIMEOUT for SHUTDOWN event from bits VM at par + # with the avocado test timeout. + self._vm.event_wait('SHUTDOWN', timeout=BITS_TIMEOUT) + self._vm.wait(timeout=None) + self.parse_log() diff --git a/tests/avocado/acpi-bits/bits-config/bits-cfg.txt b/tests/avocado/acpi-bits/bits-config/bits-cfg.txt new file mode 100644 index 0000000000..8010804453 --- /dev/null +++ b/tests/avocado/acpi-bits/bits-config/bits-cfg.txt @@ -0,0 +1,18 @@ +# BITS configuration file +[bits] + +# To run BITS in batch mode, set batch to a list of one or more of the +# following keywords; BITS will then run all of the requested operations, then +# save the log file to disk. +# +# test: Run the full BITS testsuite. +# acpi: Dump all ACPI structures. +# smbios: Dump all SMBIOS structures. +# +# Leave batch set to an empty string to disable batch mode. +# batch = + +# Uncomment the following to run all available batch operations +# please take a look at boot/python/init.py in bits zip file +# to see how these options are parsed and used. +batch = test acpi smbios diff --git a/tests/avocado/acpi-bits/bits-tests/smbios.py2 b/tests/avocado/acpi-bits/bits-tests/smbios.py2 new file mode 100644 index 0000000000..5868a7137a --- /dev/null +++ b/tests/avocado/acpi-bits/bits-tests/smbios.py2 @@ -0,0 +1,2434 @@ +# Copyright (c) 2015, Intel Corporation +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This script runs only from the biosbits VM. + +"""SMBIOS/DMI module.""" + +import bits +import bitfields +import ctypes +import redirect +import struct +import uuid +import unpack +import ttypager +import sys + +class SMBIOS(unpack.Struct): + def __new__(cls): + if sys.platform == "BITS-EFI": + import efi + sm_ptr = efi.system_table.ConfigurationTableDict.get(efi.SMBIOS_TABLE_GUID) + else: + address = 0xF0000 + mem = bits.memory(0xF0000, 0x10000) + for offset in range(0, len(mem), 16): + signature = (ctypes.c_char * 4).from_address(address + offset).value + if signature == "_SM_": + entry_point_length = ctypes.c_ubyte.from_address(address + offset + 5).value + csum = sum(map(ord, mem[offset:offset + entry_point_length])) & 0xff + if csum == 0: + sm_ptr = address + offset + break + else: + return None + + if not sm_ptr: + return None + + sm = super(SMBIOS, cls).__new__(cls) + sm._header_memory = bits.memory(sm_ptr, 0x1f) + return sm + + def __init__(self): + super(SMBIOS, self).__init__() + u = unpack.Unpackable(self._header_memory) + self.add_field('header', Header(u)) + self._structure_memory = bits.memory(self.header.structure_table_address, self.header.structure_table_length) + u = unpack.Unpackable(self._structure_memory) + self.add_field('structures', unpack.unpack_all(u, _smbios_structures, self), unpack.format_each("\n\n{!r}")) + + def structure_type(self, num): + '''Dumps structure of given Type if present''' + try: + types_present = [self.structures[x].smbios_structure_type for x in range(len(self.structures))] + matrix = dict() + for index in range(len(types_present)): + if types_present.count(types_present[index]) == 1: + matrix[types_present[index]] = self.structures[index] + else: # if multiple structures of the same type, return a list of structures for the type number + if matrix.has_key(types_present[index]): + matrix[types_present[index]].append(self.structures[index]) + else: + matrix[types_present[index]] = [self.structures[index]] + return matrix[num] + except: + print "Failure: Type {} - not found".format(num) + +class Header(unpack.Struct): + def __new__(cls, u): + return super(Header, cls).__new__(cls) + + def __init__(self, u): + super(Header, self).__init__() + self.raw_data = u.unpack_rest() + u = unpack.Unpackable(self.raw_data) + self.add_field('anchor_string', u.unpack_one("4s")) + self.add_field('checksum', u.unpack_one("B")) + self.add_field('length', u.unpack_one("B")) + self.add_field('major_version', u.unpack_one("B")) + self.add_field('minor_version', u.unpack_one("B")) + self.add_field('max_structure_size', u.unpack_one("<H")) + self.add_field('entry_point_revision', u.unpack_one("B")) + self.add_field('formatted_area', u.unpack_one("5s")) + self.add_field('intermediate_anchor_string', u.unpack_one("5s")) + self.add_field('intermediate_checksum', u.unpack_one("B")) + self.add_field('structure_table_length', u.unpack_one("<H")) + self.add_field('structure_table_address', u.unpack_one("<I")) + self.add_field('number_structures', u.unpack_one("<H")) + self.add_field('bcd_revision', u.unpack_one("B")) + if not u.at_end(): + self.add_field('data', u.unpack_rest()) + +class SmbiosBaseStructure(unpack.Struct): + def __new__(cls, u, sm): + t = u.unpack_peek_one("B") + if cls.smbios_structure_type is not None and t != cls.smbios_structure_type: + return None + return super(SmbiosBaseStructure, cls).__new__(cls) + + def __init__(self, u, sm): + super(SmbiosBaseStructure, self).__init__() + self.start_offset = u.offset + length = u.unpack_peek_one("<xB") + self.raw_data = u.unpack_raw(length) + self.u = unpack.Unpackable(self.raw_data) + + self.strings_offset = u.offset + def unpack_string(): + return "".join(iter(lambda: u.unpack_one("c"), "\x00")) + strings = list(iter(unpack_string, "")) + if not strings: + u.skip(1) + + self.strings_length = u.offset - self.strings_offset + self.raw_strings = str(bits.memory(sm.header.structure_table_address + self.strings_offset, self.strings_length)) + + if len(strings): + self.strings = strings + + self.add_field('type', self.u.unpack_one("B")) + self.add_field('length', self.u.unpack_one("B")) + self.add_field('handle', self.u.unpack_one("<H")) + + def fini(self): + if not self.u.at_end(): + self.add_field('data', self.u.unpack_rest()) + del self.u + + def fmtstr(self, i): + """Format the specified index and the associated string""" + return "{} '{}'".format(i, self.getstr(i)) + + def getstr(self, i): + """Get the string associated with the given index""" + if i == 0: + return "(none)" + if not hasattr(self, "strings"): + return "(error: structure has no strings)" + if i > len(self.strings): + return "(error: string index out of range)" + return self.strings[i - 1] + +class BIOSInformation(SmbiosBaseStructure): + smbios_structure_type = 0 + + def __init__(self, u, sm): + super(BIOSInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('vendor', u.unpack_one("B"), self.fmtstr) + self.add_field('version', u.unpack_one("B"), self.fmtstr) + self.add_field('starting_address_segment', u.unpack_one("<H")) + self.add_field('release_date', u.unpack_one("B"), self.fmtstr) + self.add_field('rom_size', u.unpack_one("B")) + self.add_field('characteristics', u.unpack_one("<Q")) + minor_version_str = str(sm.header.minor_version) # 34 is .34, 4 is .4, 41 is .41; compare ASCIIbetically to compare initial digits rather than numeric value + if (sm.header.major_version, minor_version_str) >= (2,"4"): + characteristic_bytes = 2 + else: + characteristic_bytes = self.length - 0x12 + self.add_field('characteristics_extensions', [u.unpack_one("B") for b in range(characteristic_bytes)]) + if (sm.header.major_version, minor_version_str) >= (2,"4"): + self.add_field('major_release', u.unpack_one("B")) + self.add_field('minor_release', u.unpack_one("B")) + self.add_field('ec_major_release', u.unpack_one("B")) + self.add_field('ec_minor_release', u.unpack_one("B")) + except: + self.decode_failure = True + print "Error parsing BIOSInformation" + import traceback + traceback.print_exc() + self.fini() + +class SystemInformation(SmbiosBaseStructure): + smbios_structure_type = 1 + + def __init__(self, u, sm): + super(SystemInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('manufacturer', u.unpack_one("B"), self.fmtstr) + self.add_field('product_name', u.unpack_one("B"), self.fmtstr) + self.add_field('version', u.unpack_one("B"), self.fmtstr) + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0x8: + self.add_field('uuid', uuid.UUID(bytes_le=u.unpack_one("16s"))) + wakeup_types = { + 0: 'Reserved', + 1: 'Other', + 2: 'Unknown', + 3: 'APM Timer', + 4: 'Modem Ring', + 5: 'LAN Remote', + 6: 'Power Switch', + 7: 'PCI PME#', + 8: 'AC Power Restored' + } + self.add_field('wakeup_type', u.unpack_one("B"), unpack.format_table("{}", wakeup_types)) + if self.length > 0x19: + self.add_field('sku_number', u.unpack_one("B"), self.fmtstr) + self.add_field('family', u.unpack_one("B"), self.fmtstr) + except: + self.decode_failure = True + print "Error parsing SystemInformation" + import traceback + traceback.print_exc() + self.fini() + +_board_types = { + 1: 'Unknown', + 2: 'Other', + 3: 'Server Blade', + 4: 'Connectivity Switch', + 5: 'System Management Module', + 6: 'Processor Module', + 7: 'I/O Module', + 8: 'Memory Module', + 9: 'Daughter Board', + 0xA: 'Motherboard', + 0xB: 'Processor/Memory Module', + 0xC: 'Processor/IO Module', + 0xD: 'Interconnect Board' +} + +class BaseboardInformation(SmbiosBaseStructure): + smbios_structure_type = 2 + + def __init__(self, u, sm): + super(BaseboardInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('manufacturer', u.unpack_one("B"), self.fmtstr) + self.add_field('product', u.unpack_one("B"), self.fmtstr) + self.add_field('version', u.unpack_one("B"), self.fmtstr) + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + + if self.length > 0x8: + self.add_field('asset_tag', u.unpack_one("B"), self.fmtstr) + + if self.length > 0x9: + self.add_field('feature_flags', u.unpack_one("B")) + self.add_field('hosting_board', bool(bitfields.getbits(self.feature_flags, 0)), "feature_flags[0]={}") + self.add_field('requires_daughter_card', bool(bitfields.getbits(self.feature_flags, 1)), "feature_flags[1]={}") + self.add_field('removable', bool(bitfields.getbits(self.feature_flags, 2)), "feature_flags[2]={}") + self.add_field('replaceable', bool(bitfields.getbits(self.feature_flags, 3)), "feature_flags[3]={}") + self.add_field('hot_swappable', bool(bitfields.getbits(self.feature_flags, 4)), "feature_flags[4]={}") + + if self.length > 0xA: + self.add_field('location', u.unpack_one("B"), self.fmtstr) + + if self.length > 0xB: + self.add_field('chassis_handle', u.unpack_one("<H")) + + if self.length > 0xD: + self.add_field('board_type', u.unpack_one("B"), unpack.format_table("{}", _board_types)) + + if self.length > 0xE: + self.add_field('handle_count', u.unpack_one("B")) + if self.handle_count > 0: + self.add_field('contained_object_handles', tuple(u.unpack_one("<H") for i in range(self.handle_count))) + except: + self.decode_failure = True + print "Error parsing BaseboardInformation" + import traceback + traceback.print_exc() + self.fini() + +class SystemEnclosure(SmbiosBaseStructure): + smbios_structure_type = 3 + + def __init__(self, u, sm): + super(SystemEnclosure, self).__init__(u, sm) + u = self.u + try: + self.add_field('manufacturer', u.unpack_one("B"), self.fmtstr) + self.add_field('enumerated_type', u.unpack_one("B")) + self.add_field('chassis_lock_present', bool(bitfields.getbits(self.enumerated_type, 7)), "enumerated_type[7]={}") + board_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Desktop', + 0x04: 'Low Profile Desktop', + 0x05: 'Pizza Box', + 0x06: 'Mini Tower', + 0x07: 'Tower', + 0x08: 'Portable', + 0x09: 'Laptop', + 0x0A: 'Notebook', + 0x0B: 'Hand Held', + 0x0C: 'Docking Station', + 0x0D: 'All in One', + 0x0E: 'Sub Notebook', + 0x0F: 'Space-saving', + 0x10: 'Lunch Box', + 0x11: 'Main Server Chassis', + 0x12: 'Expansion Chassis', + 0x13: 'SubChassis', + 0x14: 'Bus Expansion Chassis', + 0x15: 'Peripheral Chassis', + 0x16: 'RAID Chassis', + 0x17: 'Rack Mount Chassis', + 0x18: 'Sealed-case PC', + 0x19: 'Multi-system chassis W', + 0x1A: 'Compact PCI', + 0x1B: 'Advanced TCA', + 0x1C: 'Blade', + 0x1D: 'Blade Enclosure', + } + self.add_field('system_enclosure_type', bitfields.getbits(self.enumerated_type, 6, 0), unpack.format_table("enumerated_type[6:0]={}", board_types)) + self.add_field('version', u.unpack_one("B"), self.fmtstr) + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + self.add_field('asset_tag', u.unpack_one("B"), self.fmtstr) + minor_version_str = str(sm.header.minor_version) # 34 is .34, 4 is .4, 41 is .41; compare ASCIIbetically to compare initial digits rather than numeric value + if self.length > 9: + chassis_states = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Safe', + 0x04: 'Warning', + 0x05: 'Critical', + 0x06: 'Non-recoverable', + } + self.add_field('bootup_state', u.unpack_one("B"), unpack.format_table("{}", chassis_states)) + self.add_field('power_supply_state', u.unpack_one("B"), unpack.format_table("{}", chassis_states)) + self.add_field('thermal_state', u.unpack_one("B"), unpack.format_table("{}", chassis_states)) + security_states = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'None', + 0x04: 'External interface locked out', + 0x05: 'External interface enabled', + } + self.add_field('security_status', u.unpack_one("B"), unpack.format_table("{}", security_states)) + if self.length > 0xd: + self.add_field('oem_defined', u.unpack_one("<I")) + if self.length > 0x11: + self.add_field('height', u.unpack_one("B")) + self.add_field('num_power_cords', u.unpack_one("B")) + self.add_field('contained_element_count', u.unpack_one("B")) + self.add_field('contained_element_length', u.unpack_one("B")) + if getattr(self, 'contained_element_count', 0): + self.add_field('contained_elements', tuple(SystemEnclosureContainedElement(u, self.contained_element_length) for i in range(self.contained_element_count))) + if self.length > (0x15 + (getattr(self, 'contained_element_count', 0) * getattr(self, 'contained_element_length', 0))): + self.add_field('sku_number', u.unpack_one("B"), self.fmtstr) + except: + self.decode_failure = True + print "Error parsing SystemEnclosure" + import traceback + traceback.print_exc() + self.fini() + +class SystemEnclosureContainedElement(unpack.Struct): + def __init__(self, u, length): + super(SystemEnclosureContainedElement, self).__init__() + self.start_offset = u.offset + self.raw_data = u.unpack_raw(length) + self.u = unpack.Unpackable(self.raw_data) + u = self.u + self.add_field('contained_element_type', u.unpack_one("B")) + type_selections = { + 0: 'SMBIOS baseboard type enumeration', + 1: 'SMBIOS structure type enumeration', + } + self.add_field('type_select', bitfields.getbits(self.contained_element_type, 7), unpack.format_table("contained_element_type[7]={}", type_selections)) + self.add_field('type', bitfields.getbits(self.contained_element_type, 6, 0)) + if self.type_select == 0: + self.add_field('smbios_board_type', self.type, unpack.format_table("{}", _board_types)) + else: + self.add_field('smbios_structure_type', self.type) + self.add_field('minimum', u.unpack_one("B")) + self.add_field('maximum', u.unpack_one("B")) + if not u.at_end(): + self.add_field('data', u.unpack_rest()) + del self.u + +class ProcessorInformation(SmbiosBaseStructure): + smbios_structure_type = 4 + + def __init__(self, u, sm): + super(ProcessorInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('socket_designation', u.unpack_one("B"), self.fmtstr) + processor_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Central Processor', + 0x04: 'Math Processor', + 0x05: 'DSP Processor', + 0x06: 'Video Processor', + } + self.add_field('processor_type', u.unpack_one("B"), unpack.format_table("{}", processor_types)) + self.add_field('processor_family', u.unpack_one("B")) + self.add_field('processor_manufacturer', u.unpack_one("B"), self.fmtstr) + self.add_field('processor_id', u.unpack_one("<Q")) + self.add_field('processor_version', u.unpack_one("B"), self.fmtstr) + self.add_field('voltage', u.unpack_one("B")) + self.add_field('external_clock', u.unpack_one("<H")) + self.add_field('max_speed', u.unpack_one("<H")) + self.add_field('current_speed', u.unpack_one("<H")) + self.add_field('status', u.unpack_one("B")) + processor_upgrades = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Daughter Board', + 0x04: 'ZIF Socket', + 0x05: 'Replaceable Piggy Back', + 0x06: 'None', + 0x07: 'LIF Socket', + 0x08: 'Slot 1', + 0x09: 'Slot 2', + 0x0A: '370-pin socket', + 0x0B: 'Slot A', + 0x0C: 'Slot M', + 0x0D: 'Socket 423', + 0x0E: 'Socket A (Socket 462)', + 0x0F: 'Socket 478', + 0x10: 'Socket 754', + 0x11: 'Socket 940', + 0x12: 'Socket 939', + 0x13: 'Socket mPGA604', + 0x14: 'Socket LGA771', + 0x15: 'Socket LGA775', + 0x16: 'Socket S1', + 0x17: 'Socket AM2', + 0x18: 'Socket F (1207)', + 0x19: 'Socket LGA1366', + 0x1A: 'Socket G34', + 0x1B: 'Socket AM3', + 0x1C: 'Socket C32', + 0x1D: 'Socket LGA1156', + 0x1E: 'Socket LGA1567', + 0x1F: 'Socket PGA988A', + 0x20: 'Socket BGA1288', + 0x21: 'Socket rPGA988B', + 0x22: 'Socket BGA1023', + 0x23: 'Socket BGA1224', + 0x24: 'Socket BGA1155', + 0x25: 'Socket LGA1356', + 0x26: 'Socket LGA2011', + 0x27: 'Socket FS1', + 0x28: 'Socket FS2', + 0x29: 'Socket FM1', + 0x2A: 'Socket FM2', + } + self.add_field('processor_upgrade', u.unpack_one("B"), unpack.format_table("{}", processor_upgrades)) + if self.length > 0x1A: + self.add_field('l1_cache_handle', u.unpack_one("<H")) + self.add_field('l2_cache_handle', u.unpack_one("<H")) + self.add_field('l3_cache_handle', u.unpack_one("<H")) + if self.length > 0x20: + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + self.add_field('asset_tag', u.unpack_one("B"), self.fmtstr) + self.add_field('part_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0x24: + self.add_field('core_count', u.unpack_one("B")) + self.add_field('core_enabled', u.unpack_one("B")) + self.add_field('thread_count', u.unpack_one("B")) + self.add_field('processor_characteristics', u.unpack_one("<H")) + if self.length > 0x28: + self.add_field('processor_family_2', u.unpack_one("<H")) + if self.length > 0x2A: + self.add_field('core_count2', u.unpack_one("<H")) + self.add_field('core_enabled2', u.unpack_one("<H")) + self.add_field('thread_count2', u.unpack_one("<H")) + except: + self.decode_failure = True + print "Error parsing Processor Information" + import traceback + traceback.print_exc() + self.fini() + +class MemoryControllerInformation(SmbiosBaseStructure): #obsolete starting with v2.1 + smbios_structure_type = 5 + + def __init__(self, u, sm): + super(MemoryControllerInformation, self).__init__(u, sm) + u = self.u + try: + _error_detecting_method = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'None', + 0x04: '8-bit Parity', + 0x05: '32-bit ECC', + 0x06: '64-bit ECC', + 0x07: '128-bit ECC', + 0x08: 'CRC' + } + self.add_field('error_detecting_method', u.unpack_one("B"), unpack.format_table("{}", _error_detecting_method)) + self.add_field('error_correcting_capability', u.unpack_one("B")) + _interleaves = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'One-Way Interleave', + 0x04: 'Two-Way Interleave', + 0x05: 'Four-Way Interleave', + 0x06: 'Eight-Way Interleave', + 0x07: 'Sixteen-Way Interleave' + } + self.add_field('supported_interleave', u.unpack_one("B"), unpack.format_table("{}", _interleaves)) + self.add_field('current_interleave', u.unpack_one("B"), unpack.format_table("{}", _interleaves)) + self.add_field('max_memory_module_size', u.unpack_one("B"), self.fmtstr) + self.add_field('supported_speeds', u.unpack_one("<H")) + self.add_field('supported_memory_types', u.unpack_one("<H")) + self.add_field('memory_module_voltage', u.unpack_one("B")) + self.add_field('req_voltage_b2', bitfields.getbits(self.memory_module_voltage, 2), "memory_module_voltage[2]={}") + self.add_field('req_voltage_b1', bitfields.getbits(self.memory_module_voltage, 1), "memory_module_voltage[1]={}") + self.add_field('req_voltage_b0', bitfields.getbits(self.memory_module_voltage, 0), "memory_module_voltage[0]={}") + self.add_field('num_associated_memory_slots', u.unpack_one("B")) + self.add_field('memory_module_configuration_handles', u.unpack_one("<(self.num_associated_memory_slots)H")) + self.add_field('enabled_error_correcting_capabilities', u.unpack_one("B")) + except: + self.decode_failure = True + print "Error parsing MemoryControllerInformation" + import traceback + traceback.print_exc() + self.fini() + +class MemoryModuleInformation(SmbiosBaseStructure): #obsolete starting with v2.1 + smbios_structure_type = 6 + + def __init__(self, u, sm): + super(MemoryModuleInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('socket_designation', u.unpack_one("B"), self.fmtstr) + self.add_field('bank_connections', u.unpack_one("B")) + self.add_field('current_speed', u.unpack_one("B")) + self.add_field('current_memory_type', u.unpack_one("<H")) + _mem_connection = { + 0: 'single', + 1: 'double-bank' + } + self.add_field('installed_mem', u.unpack_one("B")) + self.add_field('installed_size', bitfields.getbits(self.installed_mem, 6, 0), "installed_mem[6:0]={}") + self.add_field('installed_memory_module_connection', bitfields.getbits(self.installed_mem, 7), unpack.format_table("installed_mem[7]={}", _mem_connection)) + self.add_field('enabled_mem', u.unpack_one("B")) + self.add_field('enabled_size', bitfields.getbits(self.installed_mem, 6, 0), "enabled_mem[6:0]={}") + self.add_field('enabled_memory_module_connection', bitfields.getbits(self.installed_mem, 7), unpack.format_table("enabled_mem[7]={}", _mem_connection)) + self.add_field('error_status', u.unpack_one("B")) + self.add_field('error_status_info_obstained_from_event_log', bool(bitfields.getbits(self.error_status, 2)), unpack.format_table("error_status[2]={}", _mem_connection)) + self.add_field('correctable_errors_received', bool(bitfields.getbits(self.error_status, 1)), unpack.format_table("error_status[1]={}", _mem_connection)) + self.add_field('uncorrectable_errors_received', bool(bitfields.getbits(self.error_status, 0)), unpack.format_table("error_status[0]={}", _mem_connection)) + except: + self.decode_failure = True + print "Error parsing MemoryModuleInformation" + import traceback + traceback.print_exc() + self.fini() + +class CacheInformation(SmbiosBaseStructure): + smbios_structure_type = 7 + + def __init__(self, u, sm): + super(CacheInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('socket_designation', u.unpack_one("B"), self.fmtstr) + processor_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Central Processor', + 0x04: 'Math Processor', + 0x05: 'DSP Processor', + 0x06: 'Video Processor', + } + self.add_field('cache_configuration', u.unpack_one("<H")) + _operational_mode = { + 0b00: 'Write Through', + 0b01: 'Write Back', + 0b10: 'Varies with Memory Address', + 0b11: 'Unknown' + } + self.add_field('operational_mode', bitfields.getbits(self.cache_configuration, 9, 8), unpack.format_table("cache_configuration[9:8]={}", _operational_mode)) + self.add_field('enabled_at_boot_time', bool(bitfields.getbits(self.cache_configuration, 7)), "cache_configuration[7]={}") + _location = { + 0b00: 'Internal', + 0b01: 'External', + 0b10: 'Reserved', + 0b11: 'Unknown' + } + self.add_field('location_relative_to_cpu_module', bitfields.getbits(self.cache_configuration, 6, 5), unpack.format_table("cache_configuration[6:5]={}", _location)) + self.add_field('cache_socketed', bool(bitfields.getbits(self.cache_configuration, 3)), "cache_configuration[3]={}") + self.add_field('cache_level', bitfields.getbits(self.cache_configuration, 2, 0), "cache_configuration[2:0]={}") + self.add_field('max_cache_size', u.unpack_one("<H")) + _granularity = { + 0: '1K granularity', + 1: '64K granularity' + } + self.add_field('max_granularity', bitfields.getbits(self.cache_configuration, 15), unpack.format_table("max_cache_size[15]={}", _granularity)) + self.add_field('max_size_in_granularity', bitfields.getbits(self.cache_configuration, 14, 0), "max_cache_size[14, 0]={}") + self.add_field('installed_size', u.unpack_one("<H")) + if self.installed_size != 0: + self.add_field('installed_granularity', bitfields.getbits(self.cache_configuration, 15), unpack.format_table("installed_size[15]={}", _granularity)) + self.add_field('installed_size_in_granularity', bitfields.getbits(self.cache_configuration, 14, 0), "installed_size[14, 0]={}") + self.add_field('supported_sram_type', u.unpack_one("<H")) + self.add_field('current_sram_type', u.unpack_one("<H")) + if self.length > 0x0F: + self.add_field('cache_speed', u.unpack_one("B")) + if self.length > 0x10: + _error_correction = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'None', + 0x04: 'Parity', + 0x05: 'Single-bit ECC', + 0x06: 'Multi-bit ECC' + } + self.add_field('error_correction', u.unpack_one("B"), unpack.format_table("{}", _error_correction)) + if self.length > 0x10: + _system_cache_type = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Instruction', + 0x04: 'Data', + 0x05: 'Unified' + } + self.add_field('system_cache_type', u.unpack_one("B"), unpack.format_table("{}", _system_cache_type)) + if self.length > 0x12: + _associativity = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Direct Mapped', + 0x04: '2-way Set-Associative', + 0x05: '4-way Set-Associative', + 0x06: 'Fully Associative', + 0x07: '8-way Set-Associative', + 0x08: '16-way Set-Associative', + 0x09: '12-way Set-Associative', + 0x0A: '24-way Set-Associative', + 0x0B: '32-way Set-Associative', + 0x0C: '48-way Set-Associative', + 0x0D: '64-way Set-Associative', + 0x0E: '20-way Set-Associative' + } + self.add_field('associativity', u.unpack_one("B"), unpack.format_table("{}", _associativity)) + + except: + self.decode_failure = True + print "Error parsing CacheInformation" + import traceback + traceback.print_exc() + self.fini() + +class PortConnectorInfo(SmbiosBaseStructure): + smbios_structure_type = 8 + + def __init__(self, u, sm): + super(PortConnectorInfo, self).__init__(u, sm) + u = self.u + try: + self.add_field('internal_reference_designator', u.unpack_one("B"), self.fmtstr) + connector_types = { + 0x00: 'None', + 0x01: 'Centronics', + 0x02: 'Mini Centronics', + 0x03: 'Proprietary', + 0x04: 'DB-25 pin male', + 0x05: 'DB-25 pin female', + 0x06: 'DB-15 pin male', + 0x07: 'DB-15 pin female', + 0x08: 'DB-9 pin male', + 0x09: 'DB-9 pin female', + 0x0A: 'RJ-11', + 0x0B: 'RJ-45', + 0x0C: '50-pin MiniSCSI', + 0x0D: 'Mini-DIN', + 0x0E: 'Micro-DIN', + 0x0F: 'PS/2', + 0x10: 'Infrared', + 0x11: 'HP-HIL', + 0x12: 'Access Bus (USB)', + 0x13: 'SSA SCSI', + 0x14: 'Circular DIN-8 male', + 0x15: 'Circular DIN-8 female', + 0x16: 'On Board IDE', + 0x17: 'On Board Floppy', + 0x18: '9-pin Dual Inline (pin 10 cut)', + 0x19: '25-pin Dual Inline (pin 26 cut)', + 0x1A: '50-pin Dual Inline', + 0x1B: '68-pin Dual Inline', + 0x1C: 'On Board Sound Input from CD-ROM', + 0x1D: 'Mini-Centronics Type-14', + 0x1E: 'Mini-Centronics Type-26', + 0x1F: 'Mini-jack (headphones)', + 0x20: 'BNC', + 0x21: '1394', + 0x22: 'SAS/SATA Plug Receptacle', + 0xA0: 'PC-98', + 0xA1: 'PC-98Hireso', + 0xA2: 'PC-H98', + 0xA3: 'PC-98Note', + 0xA4: 'PC-98Full', + 0xFF: 'Other', + } + self.add_field('internal_connector_type', u.unpack_one("B"), unpack.format_table("{}", connector_types)) + self.add_field('external_reference_designator', u.unpack_one("B"), self.fmtstr) + self.add_field('external_connector_type', u.unpack_one("B"), unpack.format_table("{}", connector_types)) + port_types = { + 0x00: 'None', + 0x01: 'Parallel Port XT/AT Compatible', + 0x02: 'Parallel Port PS/2', + 0x03: 'Parallel Port ECP', + 0x04: 'Parallel Port EPP', + 0x05: 'Parallel Port ECP/EPP', + 0x06: 'Serial Port XT/AT Compatible', + 0x07: 'Serial Port 16450 Compatible', + 0x08: 'Serial Port 16550 Compatible', + 0x09: 'Serial Port 16550A Compatible', + 0x0A: 'SCSI Port', + 0x0B: 'MIDI Port', + 0x0C: 'Joy Stick Port', + 0x0D: 'Keyboard Port', + 0x0E: 'Mouse Port', + 0x0F: 'SSA SCSI', + 0x10: 'USB', + 0x11: 'FireWire (IEEE P1394)', + 0x12: 'PCMCIA Type I2', + 0x13: 'PCMCIA Type II', + 0x14: 'PCMCIA Type III', + 0x15: 'Cardbus', + 0x16: 'Access Bus Port', + 0x17: 'SCSI II', + 0x18: 'SCSI Wide', + 0x19: 'PC-98', + 0x1A: 'PC-98-Hireso', + 0x1B: 'PC-H98', + 0x1C: 'Video Port', + 0x1D: 'Audio Port', + 0x1E: 'Modem Port', + 0x1F: 'Network Port', + 0x20: 'SATA', + 0x21: 'SAS', + 0xA0: '8251 Compatible', + 0xA1: '8251 FIFO Compatible', + 0xFF: 'Other', + } + self.add_field('port_type', u.unpack_one("B"), unpack.format_table("{}", port_types)) + except: + self.decodeFailure = True + print "Error parsing PortConnectorInfo" + import traceback + traceback.print_exc() + self.fini() + +class SystemSlots(SmbiosBaseStructure): + smbios_structure_type = 9 + + def __init__(self, u, sm): + super(SystemSlots, self).__init__(u, sm) + u = self.u + try: + self.add_field('designation', u.unpack_one("B"), self.fmtstr) + _slot_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'ISA', + 0x04: 'MCA', + 0x05: 'EISA', + 0x06: 'PCI', + 0x07: 'PC Card (PCMCIA)', + 0x08: 'VL-VESA', + 0x09: 'Proprietary', + 0x0A: 'Processor Card Slot', + 0x0B: 'Proprietary Memory Card Slot', + 0x0C: 'I/O Riser Card Slot', + 0x0D: 'NuBus', + 0x0E: 'PCI 66MHz Capable', + 0x0F: 'AGP', + 0x10: 'AGP 2X', + 0x11: 'AGP 4X', + 0x12: 'PCI-X', + 0x13: 'AGP 8X', + 0xA0: 'PC-98/C20', + 0xA1: 'PC-98/C24', + 0xA2: 'PC-98/E', + 0xA3: 'PC-98/Local Bus', + 0xA4: 'PC-98/Card', + 0xA5: 'PCI Express', + 0xA6: 'PCI Express x1', + 0xA7: 'PCI Express x2', + 0xA8: 'PCI Express x4', + 0xA9: 'PCI Express x8', + 0xAA: 'PCI Express x16', + 0xAB: 'PCI Express Gen 2', + 0xAC: 'PCI Express Gen 2 x1', + 0xAD: 'PCI Express Gen 2 x2', + 0xAE: 'PCI Express Gen 2 x4', + 0xAF: 'PCI Express Gen 2 x8', + 0xB0: 'PCI Express Gen 2 x16', + 0xB1: 'PCI Express Gen 3', + 0xB2: 'PCI Express Gen 3 x1', + 0xB3: 'PCI Express Gen 3 x2', + 0xB4: 'PCI Express Gen 3 x4', + 0xB5: 'PCI Express Gen 3 x8', + 0xB6: 'PCI Express Gen 3 x16', + } + self.add_field('slot_type', u.unpack_one("B"), unpack.format_table("{}", _slot_types)) + _slot_data_bus_widths = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: '8 bit', + 0x04: '16 bit', + 0x05: '32 bit', + 0x06: '64 bit', + 0x07: '128 bit', + 0x08: '1x or x1', + 0x09: '2x or x2', + 0x0A: '4x or x4', + 0x0B: '8x or x8', + 0x0C: '12x or x12', + 0x0D: '16x or x16', + 0x0E: '32x or x32', + } + self.add_field('slot_data_bus_width', u.unpack_one('B'), unpack.format_table("{}", _slot_data_bus_widths)) + _current_usages = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Available', + 0x04: 'In use', + } + self.add_field('current_usage', u.unpack_one('B'), unpack.format_table("{}", _current_usages)) + _slot_lengths = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Short Length', + 0x04: 'Long Length', + } + self.add_field('slot_length', u.unpack_one('B'), unpack.format_table("{}", _slot_lengths)) + self.add_field('slot_id', u.unpack_one('<H')) + self.add_field('characteristics1', u.unpack_one('B')) + self.add_field('characteristics_unknown', bool(bitfields.getbits(self.characteristics1, 0)), "characteristics1[0]={}") + self.add_field('provides_5_0_volts', bool(bitfields.getbits(self.characteristics1, 1)), "characteristics1[1]={}") + self.add_field('provides_3_3_volts', bool(bitfields.getbits(self.characteristics1, 2)), "characteristics1[2]={}") + self.add_field('shared_slot', bool(bitfields.getbits(self.characteristics1, 3)), "characteristics1[3]={}") + self.add_field('supports_pc_card_16', bool(bitfields.getbits(self.characteristics1, 4)), "characteristics1[4]={}") + self.add_field('supports_cardbus', bool(bitfields.getbits(self.characteristics1, 5)), "characteristics1[5]={}") + self.add_field('supports_zoom_video', bool(bitfields.getbits(self.characteristics1, 6)), "characteristics1[6]={}") + self.add_field('supports_modem_ring_resume', bool(bitfields.getbits(self.characteristics1, 7)), "characteristics1[7]={}") + if self.length > 0x0C: + self.add_field('characteristics2', u.unpack_one('B')) + self.add_field('supports_PME', bool(bitfields.getbits(self.characteristics2, 0)), "characteristics2[0]={}") + self.add_field('supports_hot_plug', bool(bitfields.getbits(self.characteristics2, 1)), "characteristics2[1]={}") + self.add_field('supports_smbus', bool(bitfields.getbits(self.characteristics2, 2)), "characteristics2[2]={}") + if self.length > 0x0D: + self.add_field('segment_group_number', u.unpack_one('<H')) + self.add_field('bus_number', u.unpack_one('B')) + self.add_field('device_function_number', u.unpack_one('B')) + self.add_field('device_number', bitfields.getbits(self.device_function_number, 7, 3), "device_function_number[7:3]={}") + self.add_field('function_number', bitfields.getbits(self.device_function_number, 2, 0), "device_function_number[2:0]={}") + except: + self.decodeFailure = True + print "Error parsing SystemSlots" + import traceback + traceback.print_exc() + self.fini() + +class OnBoardDevicesInformation(SmbiosBaseStructure): + smbios_structure_type = 10 + + def __init__(self, u, sm): + super(OnBoardDevicesInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('device_type', u.unpack_one("B")) + self.add_field('device_enabled', bool(bitfields.getbits(self.device_type, 7)), "device_type[7]={}") + _device_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Video', + 0x04: 'SCSI Controller', + 0x05: 'Ethernet', + 0x06: 'Token Ring', + 0x07: 'Sound', + 0x08: 'PATA Controller', + 0x09: 'SATA Controller', + 0x0A: 'SAS Controller' + } + self.add_field('type_of_device', bitfields.getbits(self.device_type, 6, 0), unpack.format_table("device_type[6:0]={}", _device_types)) + self.add_field('description_string', u.unpack_one("B"), self.fmtstr) + except: + self.decodeFailure = True + print "Error parsing OnBoardDevicesInformation" + import traceback + traceback.print_exc() + self.fini() + +class OEMStrings(SmbiosBaseStructure): + smbios_structure_type = 11 + + def __init__(self, u, sm): + super(OEMStrings, self).__init__(u, sm) + u = self.u + try: + self.add_field('count', u.unpack_one("B")) + except: + self.decodeFailure = True + print "Error parsing OEMStrings" + import traceback + traceback.print_exc() + self.fini() + +class SystemConfigOptions(SmbiosBaseStructure): + smbios_structure_type = 12 + + def __init__(self, u, sm): + super(SystemConfigOptions, self).__init__(u, sm) + u = self.u + try: + self.add_field('count', u.unpack_one("B")) + except: + self.decodeFailure = True + print "Error parsing SystemConfigOptions" + import traceback + traceback.print_exc() + self.fini() + +class BIOSLanguageInformation(SmbiosBaseStructure): + smbios_structure_type = 13 + + def __init__(self, u, sm): + super(BIOSLanguageInformation, self).__init__(u, sm) + u = self.u + try: + self.add_field('installable_languages', u.unpack_one("B")) + if self.length > 0x05: + self.add_field('flags', u.unpack_one('B')) + self.add_field('abbreviated_format', bool(bitfields.getbits(self.flags, 0)), "flags[0]={}") + if self.length > 0x6: + u.skip(15) + self.add_field('current_language', u.unpack_one('B'), self.fmtstr) + except: + self.decodeFailure = True + print "Error parsing BIOSLanguageInformation" + import traceback + traceback.print_exc() + self.fini() + +class GroupAssociations(SmbiosBaseStructure): + smbios_structure_type = 14 + + def __init__(self, u, sm): + super(GroupAssociations, self).__init__(u, sm) + u = self.u + try: + self.add_field('group_name', u.unpack_one("B"), self.fmtstr) + self.add_field('item_type', u.unpack_one('B')) + self.add_field('item_handle', u.unpack_one('<H')) + except: + self.decodeFailure = True + print "Error parsing GroupAssociations" + import traceback + traceback.print_exc() + self.fini() + +class SystemEventLog(SmbiosBaseStructure): + smbios_structure_type = 15 + + def __init__(self, u, sm): + super(SystemEventLog, self).__init__(u, sm) + u = self.u + try: + self.add_field('log_area_length', u.unpack_one("<H")) + self.add_field('log_header_start_offset', u.unpack_one('<H')) + self.add_field('log_data_start_offset', u.unpack_one('<H')) + _access_method = { + 0x00: 'Indexed I/O: 1 8-bit index port, 1 8-bit data port', + 0x01: 'Indexed I/O: 2 8-bit index ports, 1 8-bit data port', + 0x02: 'Indexed I/O: 1 16-bit index port, 1 8-bit data port', + 0x03: 'Memory-mapped physical 32-bit address', + 0x04: 'Available through General-Purpose NonVolatile Data functions', + xrange(0x05, 0x07F): 'Available for future assignment', + xrange(0x80, 0xFF): 'BIOS Vendor/OEM-specific' + } + self.add_field('access_method', u.unpack_one('B'), unpack.format_table("{}", _access_method)) + self.add_field('log_status', u.unpack_one('B')) + self.add_field('log_area_full', bool(bitfields.getbits(self.log_status, 1)), "log_status[1]={}") + self.add_field('log_area_valid', bool(bitfields.getbits(self.log_status, 0)), "log_status[0]={}") + self.add_field('log_change_token', u.unpack_one('<I')) + self.add_field('access_method_address', u.unpack_one('<I')) + if self.length > 0x14: + _log_header_formats = { + 0: 'No header', + 1: 'Type 1 log header', + xrange(2, 0x7f): 'Available for future assignment', + xrange(0x80, 0xff): 'BIOS vendor or OEM-specific format' + } + self.add_field('log_header_format', u.unpack_one("B"), unpack.format_table("{}", _log_header_formats)) + if self.length > 0x15: + self.add_field('num_supported_log_type_descriptors', u.unpack_one('B')) + if self.length > 0x16: + self.add_field('length_log_type_descriptor', u.unpack_one('B')) + if self.length != (0x17 + (self.num_supported_log_type_descriptors * self.length_log_type_descriptor)): + print "Error: structure length ({}) != 0x17 + (num_supported_log_type_descriptors ({}) * length_log_type_descriptor({}))".format(self.length, self.num_supported_log_type_descriptors, self.length_log_type_descriptor) + print "structure length = {}".format(self.length) + print "num_supported_log_type_descriptors = {}".format(self.num_supported_log_type_descriptors) + print "length_log_type_descriptor = {}".format(self.length_log_type_descriptor) + self.decodeFailure = True + self.add_field('descriptors', tuple(EventLogDescriptor.unpack(u) for i in range(self.num_supported_log_type_descriptors)), unpack.format_each("\n{!r}")) + except: + self.decodeFailure = True + print "Error parsing SystemEventLog" + import traceback + traceback.print_exc() + self.fini() + +class EventLogDescriptor(unpack.Struct): + @staticmethod + def _unpack(u): + _event_log_type_descriptors = { + 0x00: 'Reserved', + 0x01: 'Single-bit ECC memory error', + 0x02: 'Multi-bit ECC memory error', + 0x03: 'Parity memory error', + 0x04: 'Bus time-out', + 0x05: 'I/O Channel Check', + 0x06: 'Software NMI', + 0x07: 'POST Memory Resize', + 0x08: 'POST Error', + 0x09: 'PCI Parity Error', + 0x0A: 'PCI System Error', + 0x0B: 'CPU Failure', + 0x0C: 'EISA FailSafe Timer time-out', + 0x0D: 'Correctable memory log disabled', + 0x0E: 'Logging disabled for a specific Event Type - too many errors of the same type received in a short amount of time', + 0x0F: 'Reserved', + 0x10: 'System Limit Exceeded', + 0x11: 'Asynchronous hardware timer expired and issued a system reset', + 0x12: 'System configuration information', + 0x13: 'Hard-disk information', + 0x14: 'System reconfigured', + 0x15: 'Uncorrectable CPU-complex error', + 0x16: 'Log Area Reset/Cleared', + 0x17: 'System boot', + xrange(0x18, 0x7F): 'Unused, available for assignment', + xrange(0x80, 0xFE): 'Available for system- and OEM-specific assignments', + 0xFF: 'End of log' + } + yield 'log_type', u.unpack_one('B'), unpack.format_table("{}", _event_log_type_descriptors) + _event_log_format = { + 0x00: 'None', + 0x01: 'Handle', + 0x02: 'Multiple-Event', + 0x03: 'Multiple-Event Handle', + 0x04: 'POST Results Bitmap', + 0x05: 'System Management Type', + 0x06: 'Multiple-Event System Management Type', + xrange(0x80, 0xFF): 'OEM assigned' + } + yield 'variable_data_format_type', u.unpack_one('B'), unpack.format_table("{}", _event_log_format) + +class PhysicalMemoryArray(SmbiosBaseStructure): + smbios_structure_type = 16 + + def __init__(self, u, sm): + super(PhysicalMemoryArray, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + _location_field = { + 0x01: "Other", + 0x02: "Unknown", + 0x03: "System board or motherboard", + 0x04: "ISA add-on card", + 0x05: "EISA add-on card", + 0x06: "PCI add-on card", + 0x07: "MCA add-on card", + 0x08: "PCMCIA add-on card", + 0x09: "Proprietary add-on card", + 0x0A: "NuBus", + 0xA0: "PC-98/C20 add-on card", + 0xA1: "PC-98/C24 add-on card", + 0xA2: "PC-98/E add-on card", + 0xA3: "PC-98/Local bus add-on card" + } + self.add_field('location', u.unpack_one("B"), unpack.format_table("{}", _location_field)) + if self.length > 0x05: + _use = { + 0x01: "Other", + 0x02: "Unknown", + 0x03: "System memory", + 0x04: "Video memory", + 0x05: "Flash memory", + 0x06: "Non-volatile RAM", + 0x07: "Cache memory" + } + self.add_field('use', u.unpack_one('B'), unpack.format_table("{}", _use)) + if self.length > 0x06: + _error_correction = { + 0x01: "Other", + 0x02: "Unknown", + 0x03: "None", + 0x04: "Parity", + 0x05: "Single-bit ECC", + 0x06: "Multi-bit ECC", + 0x07: "CRC" + } + self.add_field('memory_error_correction', u.unpack_one('B'), unpack.format_table("{}", _error_correction)) + if self.length > 0x07: + self.add_field('maximum_capacity', u.unpack_one('<I')) + if self.length > 0x0B: + self.add_field('memory_error_information_handle', u.unpack_one('<H')) + if self.length > 0x0D: + self.add_field('num_memory_devices', u.unpack_one('<H')) + if self.length > 0x0F: + self.add_field('extended_maximum_capacity', u.unpack_one('<Q')) + except: + self.decodeFailure = True + print "Error parsing PhysicalMemoryArray" + import traceback + traceback.print_exc() + self.fini() + +class MemoryDevice(SmbiosBaseStructure): + smbios_structure_type = 17 + + def __init__(self, u, sm): + super(MemoryDevice, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('physical_memory_array_handle', u.unpack_one("<H")) + if self.length > 0x6: + self.add_field('memory_error_information_handle', u.unpack_one("<H")) + if self.length > 0x8: + self.add_field('total_width', u.unpack_one("<H")) + if self.length > 0xA: + self.add_field('data_width', u.unpack_one("<H")) + if self.length > 0xC: + self.add_field('size', u.unpack_one("<H")) + if self.length > 0xE: + _form_factors = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'SIMM', + 0x04: 'SIP', + 0x05: 'Chip', + 0x06: 'DIP', + 0x07: 'ZIP', + 0x08: 'Proprietary Card', + 0x09: 'DIMM', + 0x0A: 'TSOP', + 0x0B: 'Row of chips', + 0x0C: 'RIMM', + 0x0D: 'SODIMM', + 0x0E: 'SRIMM', + 0x0F: 'FB-DIMM' + } + self.add_field('form_factor', u.unpack_one("B"), unpack.format_table("{}", _form_factors)) + if self.length > 0xF: + self.add_field('device_set', u.unpack_one("B")) + if self.length > 0x10: + self.add_field('device_locator', u.unpack_one("B"), self.fmtstr) + if self.length > 0x11: + self.add_field('bank_locator', u.unpack_one("B"), self.fmtstr) + if self.length > 0x12: + _memory_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'DRAM', + 0x04: 'EDRAM', + 0x05: 'VRAM', + 0x06: 'SRAM', + 0x07: 'RAM', + 0x08: 'ROM', + 0x09: 'FLASH', + 0x0A: 'EEPROM', + 0x0B: 'FEPROM', + 0x0C: 'EPROM', + 0x0D: 'CDRAM', + 0x0E: '3DRAM', + 0x0F: 'SDRAM', + 0x10: 'SGRAM', + 0x11: 'RDRAM', + 0x12: 'DDR', + 0x13: 'DDR2', + 0x14: 'DDR2 FB-DIMM', + xrange(0x15, 0x17): 'Reserved', + 0x18: 'DDR3', + 0x19: 'FBD2' + } + self.add_field('memory_type', u.unpack_one("B"), unpack.format_table("{}", _memory_types)) + if self.length > 0x13: + self.add_field('type_detail', u.unpack_one('<H')) + if self.length > 0x15: + self.add_field('speed', u.unpack_one("<H")) + if self.length > 0x17: + self.add_field('manufacturer', u.unpack_one("B"), self.fmtstr) + if self.length > 0x18: + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0x19: + self.add_field('asset_tag', u.unpack_one("B"), self.fmtstr) + if self.length > 0x1A: + self.add_field('part_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0x1B: + self.add_field('attributes', u.unpack_one("B")) + self.add_field('rank', bitfields.getbits(self.attributes, 3, 0), "attributes[3:0]={}") + if self.length > 0x1C: + if self.size == 0x7FFF: + self.add_field('extended_size', u.unpack_one('<I')) + self.add_field('mem_size', bitfields.getbits(self.type_detail, 30, 0), "type_detail[30:0]={}") + else: + u.skip(4) + if self.length > 0x20: + self.add_field('configured_memory_clock_speed', u.unpack_one("<H")) + if self.length > 0x22: + self.add_field('minimum_voltage', u.unpack_one("<H")) + if self.length > 0x24: + self.add_field('maximum_voltage', u.unpack_one("<H")) + if self.length > 0x26: + self.add_field('configured_voltage', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing MemoryDevice" + import traceback + traceback.print_exc() + self.fini() + +class MemoryErrorInfo32Bit(SmbiosBaseStructure): + smbios_structure_type = 18 + + def __init__(self, u, sm): + super(MemoryErrorInfo32Bit, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + _error_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'OK', + 0x04: 'Bad read', + 0x05: 'Parity error', + 0x06: 'Single-bit error', + 0x07: 'Double-bit error', + 0x08: 'Multi-bit error', + 0x09: 'Nibble error', + 0x0A: 'Checksum error', + 0x0B: 'CRC error', + 0x0C: 'Corrected single-bit error', + 0x0D: 'Corrected error', + 0x0E: 'Uncorrectable error' + } + self.add_field('error_type', u.unpack_one("B"), unpack.format_table("{}", _error_types)) + if self.length > 0x5: + _error_granularity_field = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Device level', + 0x04: 'Memory partition level' + } + self.add_field('error_granularity', u.unpack_one("B"), unpack.format_table("{}", _error_granularity_field)) + if self.length > 0x6: + _error_operation_field = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Read', + 0x04: 'Write', + 0x05: 'Partial write' + } + self.add_field('error_operation', u.unpack_one("B"), unpack.format_table("{}", _error_operation_field)) + if self.length > 0x7: + self.add_field('vendor_syndrome', u.unpack_one("<I")) + if self.length > 0xB: + self.add_field('memory_array_error_address', u.unpack_one("<I")) + if self.length > 0xF: + self.add_field('device_error_address', u.unpack_one("<I")) + if self.length > 0x13: + self.add_field('error_resolution', u.unpack_one("<I")) + except: + self.decodeFailure = True + print "Error parsing MemoryErrorInfo32Bit" + import traceback + traceback.print_exc() + self.fini() + +class MemoryArrayMappedAddress(SmbiosBaseStructure): + smbios_structure_type = 19 + + def __init__(self, u, sm): + super(MemoryArrayMappedAddress, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('starting_address', u.unpack_one("<I")) + # if FFFF FFFF: address stored in Extended Starting Address + if self.length > 0x8: + self.add_field('ending_address', u.unpack_one("<I")) + if self.length > 0xC: + self.add_field('memory_array_handle', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('partition_width', u.unpack_one("B")) + if self.length > 0xF: + # valid if starting_address = FFFF FFFF + if self.starting_address == 0xFFFFFFFF: + self.add_field('extended_starting_address', u.unpack_one("<Q")) + if self.length > 0x17: + self.add_field('extended_ending_address', u.unpack_one("<Q")) + else: + u.skip(16) + + except: + self.decodeFailure = True + print "Error parsing MemoryArrayMappedAddress" + import traceback + traceback.print_exc() + self.fini() + +class MemoryDeviceMappedAddress(SmbiosBaseStructure): + smbios_structure_type = 20 + + def __init__(self, u, sm): + super(MemoryDeviceMappedAddress, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('starting_address', u.unpack_one("<I")) + # if FFFF FFFF: address stored in Extended Starting Address + if self.length > 0x8: + self.add_field('ending_address', u.unpack_one("<I")) + if self.length > 0xC: + self.add_field('memory_device_handle', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('memory_array_mapped_address_handle', u.unpack_one("<H")) + if self.length > 0x10: + self.add_field('partition_row_position', u.unpack_one("B")) + if self.length > 0x11: + self.add_field('interleave_position', u.unpack_one("B")) + if self.length > 0x12: + self.add_field('interleave_data_depth', u.unpack_one("B")) + if self.length > 0x13: + # valid if starting_address = FFFF FFFF + if self.starting_address == 0xFFFFFFFF: + self.add_field('extended_starting_address', u.unpack_one("<Q")) + if self.length > 0x1B: + self.add_field('extended_ending_address', u.unpack_one("<Q")) + else: + u.skip(16) + except: + self.decodeFailure = True + print "Error parsing MemoryDeviceMappedAddress" + import traceback + traceback.print_exc() + self.fini() + +class BuiltInPointingDevice(SmbiosBaseStructure): + smbios_structure_type = 21 + + def __init__(self, u, sm): + super(BuiltInPointingDevice, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + _pointing_device_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Mouse', + 0x04: 'Track Ball', + 0x05: 'Track Point', + 0x06: 'Glide Point', + 0x07: 'Touch Pad', + 0x08: 'Touch Screen', + 0x09: 'Optical Sensor' + } + self.add_field('pointing_device_type', u.unpack_one("B"), unpack.format_table("{}", _pointing_device_types)) + if self.length > 0x5: + _interfaces = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Serial', + 0x04: 'PS/2', + 0x05: 'Infared', + 0x06: 'HP-HIL', + 0x07: 'Bus mouse', + 0x08: 'ADB (Apple Desktop Bus)', + 0x09: 'Bus mouse DB-9', + 0x0A: 'Bus mouse micro-DIN', + 0x0B: 'USB' + } + self.add_field('interface', u.unpack_one("B"), unpack.format_table("{}", _interfaces)) + if self.length > 0x6: + self.add_field('num_buttons', u.unpack_one("B")) + except: + self.decodeFailure = True + print "Error parsing BuiltInPointingDevice" + import traceback + traceback.print_exc() + self.fini() + +class PortableBattery(SmbiosBaseStructure): + smbios_structure_type = 22 + + def __init__(self, u, sm): + super(PortableBattery, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('location', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('manufacturer', u.unpack_one("B"), self.fmtstr) + if self.length > 0x6: + self.add_field('manufacturer_date', u.unpack_one("B"), self.fmtstr) + if self.length > 0x7: + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0x8: + self.add_field('device_name', u.unpack_one("B"), self.fmtstr) + if self.length > 0x9: + _device_chemistry = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Lead Acid', + 0x04: 'Nickel Cadmium', + 0x05: 'Nickel metal hydride', + 0x06: 'Lithium-ion', + 0x07: 'Zinc air', + 0x08: 'Lithium Polymer' + } + self.add_field('device_chemistry', u.unpack_one("B"), unpack.format_table("{}", _device_chemistry)) + if self.length > 0xA: + self.add_field('design_capacity', u.unpack_one("<H")) + if self.length > 0xC: + self.add_field('design_voltage', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('sbds_version_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0xF: + self.add_field('max_error_battery_data', u.unpack_one("B"), self.fmtstr) + if self.length > 0x10: + if self.serial_number == 0: + self.add_field('sbds_serial_number', u.unpack_one("<H")) + else: + u.skip(2) + if self.length > 0x12: + if self.manufacturer_date == 0: + self.add_field('sbds_manufacture_date', u.unpack_one("<H")) + self.add_field('year_biased_by_1980', bitfields.getbits(self.sbds_manufacture_date, 15, 9), "sbds_manufacture_date[15:9]={}") + self.add_field('month', bitfields.getbits(self.sbds_manufacture_date, 8, 5), "sbds_manufacture_date[8:5]={}") + self.add_field('date', bitfields.getbits(self.sbds_manufacture_date, 4, 0), "sbds_manufacture_date[4:0]={}") + else: + u.skip(2) + if self.length > 0x14: + if self.device_chemistry == 0x02: + self.add_field('sbds_device_chemistry', u.unpack_one("B"), self.fmtstr) + else: + u.skip(1) + if self.length > 0x15: + self.add_field('design_capacity_multiplier', u.unpack_one("B")) + if self.length > 0x16: + self.add_field('oem_specific', u.unpack_one("<I")) + except: + self.decodeFailure = True + print "Error parsing PortableBattery" + import traceback + traceback.print_exc() + self.fini() + +class SystemReset(SmbiosBaseStructure): + smbios_structure_type = 23 + + def __init__(self, u, sm): + super(SystemReset, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('capabilities', u.unpack_one("B")) + self.add_field('contains_watchdog_timer', bool(bitfields.getbits(self.capabilities, 5)), "capabilities[5]={}") + _boot_option = { + 0b00: 'Reserved, do not use', + 0b01: 'Operating System', + 0b10: 'System utilities', + 0b11: 'Do not reboot' + } + self.add_field('boot_option_on_limit', bitfields.getbits(self.capabilities, 4, 3), unpack.format_table("capabilities[4:3]={}", _boot_option)) + self.add_field('boot_option_after_watchdog_reset', bitfields.getbits(self.capabilities, 2, 1), unpack.format_table("capabilities[2:1]={}", _boot_option)) + self.add_field('system_reset_enabled_by_user', bool(bitfields.getbits(self.capabilities, 0)), "capabilities[0]={}") + if self.length > 0x5: + self.add_field('reset_count', u.unpack_one("<H")) + if self.length > 0x5: + self.add_field('reset_limit', u.unpack_one("<H")) + if self.length > 0x9: + self.add_field('timer_interval', u.unpack_one("<H")) + if self.length > 0xB: + self.add_field('timeout', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing SystemReset" + import traceback + traceback.print_exc() + self.fini() + +class HardwareSecurity(SmbiosBaseStructure): + smbios_structure_type = 24 + + def __init__(self, u, sm): + super(HardwareSecurity, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('hardware_security_settings', u.unpack_one("B")) + _status = { + 0x00: 'Disabled', + 0x01: 'Enabled', + 0x02: 'Not Implemented', + 0x03: 'Unknown' + } + self.add_field('power_on_password_status', bitfields.getbits(self.hardware_security_settings, 7, 6), unpack.format_table("hardware_security_settings[7:6]={}", _status)) + self.add_field('keyboard_password_status', bitfields.getbits(self.hardware_security_settings, 5, 4), unpack.format_table("hardware_security_settings[5:4]={}", _status)) + self.add_field('admin_password_status', bitfields.getbits(self.hardware_security_settings, 3, 2), unpack.format_table("hardware_security_settings0[3:2]={}", _status)) + self.add_field('front_panel_reset_status', bitfields.getbits(self.hardware_security_settings, 1, 0), unpack.format_table("hardware_security_settings[1:0]={}", _status)) + except: + self.decodeFailure = True + print "Error parsing HardwareSecurity" + import traceback + traceback.print_exc() + self.fini() + +class SystemPowerControls(SmbiosBaseStructure): + smbios_structure_type = 25 + + def __init__(self, u, sm): + super(SystemPowerControls, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('next_scheduled_poweron_month', u.unpack_one("B")) + self.add_field('next_scheduled_poweron_day_of_month', u.unpack_one("B")) + self.add_field('next_scheduled_poweron_hour', u.unpack_one("B")) + self.add_field('next_scheduled_poweron_minute', u.unpack_one("B")) + self.add_field('next_scheduled_poweron_second', u.unpack_one("B")) + except: + self.decodeFailure = True + print "Error parsing SystemPowerControls" + import traceback + traceback.print_exc() + self.fini() + +class VoltageProbe(SmbiosBaseStructure): + smbios_structure_type = 26 + + def __init__(self, u, sm): + super(VoltageProbe, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('description', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('location_and_status', u.unpack_one("B")) + _status = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'OK', + 0b100: 'Non-critical', + 0b101: 'Critical', + 0b110: 'Non-recoverable' + } + _location = { + 0b00001: 'Other', + 0b00010: 'Unknown', + 0b00011: 'Processor', + 0b00100: 'Disk', + 0b00101: 'Peripheral Bay', + 0b00110: 'System Management Module', + 0b00111: 'Motherboard', + 0b01000: 'Memory Module', + 0b01001: 'Processor Module', + 0b01010: 'Power Unit', + 0b01011: 'Add-in Card' + } + self.add_field('status', bitfields.getbits(self.location_and_status, 7, 5), unpack.format_table("location_and_status[7:5]={}", _status)) + self.add_field('location', bitfields.getbits(self.location_and_status, 4, 0), unpack.format_table("location_and_status[4:0]={}", _location)) + if self.length > 0x6: + self.add_field('max_value', u.unpack_one("<H")) + if self.length > 0x8: + self.add_field('min_value', u.unpack_one("<H")) + if self.length > 0xA: + self.add_field('resolution', u.unpack_one("<H")) + if self.length > 0xC: + self.add_field('tolerance', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('accuracy', u.unpack_one("<H")) + if self.length > 0x10: + self.add_field('oem_defined', u.unpack_one("<I")) + if self.length > 0x14: + self.add_field('nominal_value', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing VoltageProbe" + import traceback + traceback.print_exc() + self.fini() + +class CoolingDevice(SmbiosBaseStructure): + smbios_structure_type = 27 + + def __init__(self, u, sm): + super(CoolingDevice, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('temperature_probe_handle', u.unpack_one("<H")) + if self.length > 0x6: + self.add_field('device_type_and_status', u.unpack_one("B")) + _status = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'OK', + 0b100: 'Non-critical', + 0b101: 'Critical', + 0b110: 'Non-recoverable' + } + _type = { + 0b00001: 'Other', + 0b00010: 'Unknown', + 0b00011: 'Fan', + 0b00100: 'Centrifugal Blower', + 0b00101: 'Chip Fan', + 0b00110: 'Cabinet Fan', + 0b00111: 'Power Supply Fan', + 0b01000: 'Heat Pipe', + 0b01001: 'Integrated Refrigeration', + 0b10000: 'Active Cooling', + 0b10001: 'Passive Cooling' + } + self.add_field('status', bitfields.getbits(self.device_type_and_status, 7, 5), unpack.format_table("device_type_and_status[7:5]={}", _status)) + self.add_field('device_type', bitfields.getbits(self.device_type_and_status, 4, 0), unpack.format_table("device_type_and_status[4:0]={}", _type)) + if self.length > 0x7: + self.add_field('cooling_unit_group', u.unpack_one("B")) + if self.length > 0x8: + self.add_field('OEM_defined', u.unpack_one("<I")) + if self.length > 0xC: + self.add_field('nominal_speed', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('description', u.unpack_one("B"), self.fmtstr) + except: + self.decodeFailure = True + print "Error parsing CoolingDevice" + import traceback + traceback.print_exc() + self.fini() + +class TemperatureProbe(SmbiosBaseStructure): + smbios_structure_type = 28 + + def __init__(self, u, sm): + super(TemperatureProbe, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('description', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('location_and_status', u.unpack_one("B")) + _status = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'OK', + 0b100: 'Non-critical', + 0b101: 'Critical', + 0b110: 'Non-recoverable' + } + _location = { + 0b00001: 'Other', + 0b00010: 'Unknown', + 0b00011: 'Processor', + 0b00100: 'Disk', + 0b00101: 'Peripheral Bay', + 0b00110: 'System Management Module', + 0b00111: 'Motherboard', + 0b01000: 'Memory Module', + 0b01001: 'Processor Module', + 0b01010: 'Power Unit', + 0b01011: 'Add-in Card', + 0b01100: 'Front Panel Board', + 0b01101: 'Back Panel Board', + 0b01110: 'Power System Board', + 0b01111: 'Drive Back Plane' + } + self.add_field('status', bitfields.getbits(self.location_and_status, 7, 5), unpack.format_table("location_and_status[7:5]={}", _status)) + self.add_field('location', bitfields.getbits(self.location_and_status, 4, 0), unpack.format_table("location_and_status[4:0]={}", _location)) + if self.length > 0x6: + self.add_field('maximum_value', u.unpack_one("<H")) + if self.length > 0x8: + self.add_field('minimum_value', u.unpack_one("<H")) + if self.length > 0xA: + self.add_field('resolution', u.unpack_one("<H")) + if self.length > 0xC: + self.add_field('tolerance', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('accuracy', u.unpack_one("<H")) + if self.length > 0x10: + self.add_field('OEM_defined', u.unpack_one("<I")) + if self.length > 0x14: + self.add_field('nominal_value', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing TemperatureProbe" + import traceback + traceback.print_exc() + self.fini() + +class ElectricalCurrentProbe(SmbiosBaseStructure): + smbios_structure_type = 29 + + def __init__(self, u, sm): + super(ElectricalCurrentProbe, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('description', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('location_and_status', u.unpack_one("B")) + _status = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'OK', + 0b100: 'Non-critical', + 0b101: 'Critical', + 0b110: 'Non-recoverable' + } + _location = { + 0b00001: 'Other', + 0b00010: 'Unknown', + 0b00011: 'Processor', + 0b00100: 'Disk', + 0b00101: 'Peripheral Bay', + 0b00110: 'System Management Module', + 0b00111: 'Motherboard', + 0b01000: 'Memory Module', + 0b01001: 'Processor Module', + 0b01010: 'Power Unit', + 0b01011: 'Add-in Card', + 0b01100: 'Front Panel Board', + 0b01101: 'Back Panel Board', + 0b01110: 'Power System Board', + 0b01111: 'Drive Back Plane' + } + self.add_field('status', bitfields.getbits(self.location_and_status, 7, 5), unpack.format_table("location_and_status[7:5]={}", _status)) + self.add_field('location', bitfields.getbits(self.location_and_status, 4, 0), unpack.format_table("location_and_status[4:0]={}", _location)) + if self.length > 0x6: + self.add_field('maximum_value', u.unpack_one("<H")) + if self.length > 0x8: + self.add_field('minimum_value', u.unpack_one("<H")) + if self.length > 0xA: + self.add_field('resolution', u.unpack_one("<H")) + if self.length > 0xC: + self.add_field('tolerance', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('accuracy', u.unpack_one("<H")) + if self.length > 0x10: + self.add_field('OEM_defined', u.unpack_one("<I")) + if self.length > 0x14: + self.add_field('nominal_value', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing ElectricalCurrentProbe" + import traceback + traceback.print_exc() + self.fini() + +class OutOfBandRemoteAccess(SmbiosBaseStructure): + smbios_structure_type = 30 + + def __init__(self, u, sm): + super(OutOfBandRemoteAccess, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('manufacturer_name', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('connections', u.unpack_one("B")) + self.add_field('outbound_connection_enabled', bool(bitfields.getbits(self.connections, 1)), "connections[1]={}") + self.add_field('inbound_connection_enabled', bool(bitfields.getbits(self.connections, 0)), "connections[0]={}") + except: + self.decodeFailure = True + print "Error parsing OutOfBandRemoteAccess" + import traceback + traceback.print_exc() + self.fini() + +class BootIntegrityServicesEntryPoint(SmbiosBaseStructure): + smbios_structure_type = 31 + +class SystemBootInformation(SmbiosBaseStructure): + smbios_structure_type = 32 + + def __init__(self, u, sm): + super(SystemBootInformation, self).__init__(u, sm) + u = self.u + try: + if self.length > 0xA: + u.skip(6) + _boot_status = { + 0: 'No errors detected', + 1: 'No bootable media', + 2: '"normal" operating system failed to load', + 3: 'Firmware-detected hardware failure, including "unknown" failure types', + 4: 'Operating system-detected hardware failure', + 5: 'User-requested boot, usually through a keystroke', + 6: 'System security violation', + 7: 'Previously-requested image', + 8: 'System watchdog timer expired, causing the system to reboot', + xrange(9,127): 'Reserved for future assignment', + xrange(128, 191): 'Vendor/OEM-specific implementations', + xrange(192, 255): 'Product-specific implementations' + } + self.add_field('boot_status', u.unpack_one("B"), unpack.format_table("{}", _boot_status)) + except: + self.decodeFailure = True + print "Error parsing SystemBootInformation" + import traceback + traceback.print_exc() + self.fini() + +class MemoryErrorInfo64Bit(SmbiosBaseStructure): + smbios_structure_type = 33 + + def __init__(self, u, sm): + super(MemoryErrorInfo64Bit, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + _error_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'OK', + 0x04: 'Bad read', + 0x05: 'Parity error', + 0x06: 'Single-bit error', + 0x07: 'Double-bit error', + 0x08: 'Multi-bit error', + 0x09: 'Nibble error', + 0x0A: 'Checksum error', + 0x0B: 'CRC error', + 0x0C: 'Corrected single-bit error', + 0x0D: 'Corrected error', + 0x0E: 'Uncorrectable error' + } + self.add_field('error_type', u.unpack_one("B"), unpack.format_table("{}", _error_types)) + if self.length > 0x5: + _error_granularity_field = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Device level', + 0x04: 'Memory partition level' + } + self.add_field('error_granularity', u.unpack_one("B"), unpack.format_table("{}", _error_granularity_field)) + if self.length > 0x6: + _error_operation_field = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Read', + 0x04: 'Write', + 0x05: 'Partial write' + } + self.add_field('error_operation', u.unpack_one("B"), unpack.format_table("{}", _error_operation_field)) + if self.length > 0x7: + self.add_field('vendor_syndrome', u.unpack_one("<I")) + if self.length > 0xB: + self.add_field('memory_array_error_address', u.unpack_one("<Q")) + if self.length > 0xF: + self.add_field('device_error_address', u.unpack_one("<Q")) + if self.length > 0x13: + self.add_field('error_resolution', u.unpack_one("<Q")) + except: + self.decodeFailure = True + print "Error parsing MemoryErrorInfo64Bit" + import traceback + traceback.print_exc() + self.fini() + +class ManagementDevice(SmbiosBaseStructure): + smbios_structure_type = 34 + + def __init__(self, u, sm): + super(ManagementDevice, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('description', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + _type = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'National Semiconductor LM75', + 0x04: 'National Semiconductor LM78', + 0x05: 'National Semiconductor LM79', + 0x06: 'National Semiconductor LM80', + 0x07: 'National Semiconductor LM81', + 0x08: 'Analog Devices ADM9240', + 0x09: 'Dallas Semiconductor DS1780', + 0x0A: 'Maxim 1617', + 0x0B: 'Genesys GL518SM', + 0x0C: 'Winbond W83781D', + 0x0D: 'Holtek HT82H791' + } + self.add_field('device_type', u.unpack_one("B"), unpack.format_table("{}", _type)) + if self.length > 0x6: + self.add_field('address', u.unpack_one("<I")) + if self.length > 0xA: + _address_type = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'I/O Port', + 0x04: 'Memory', + 0x05: 'SM Bus' + } + self.add_field('address_type', u.unpack_one("B"), unpack.format_table("{}", _address_type)) + except: + self.decodeFailure = True + print "Error parsing ManagementDevice" + import traceback + traceback.print_exc() + self.fini() + +class ManagementDeviceComponent(SmbiosBaseStructure): + smbios_structure_type = 35 + + def __init__(self, u, sm): + super(ManagementDeviceComponent, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('description', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('management_device_handle', u.unpack_one("<H")) + if self.length > 0x7: + self.add_field('component_handle', u.unpack_one("<H")) + if self.length > 0x9: + self.add_field('threshold_handle', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing ManagementDeviceComponent" + import traceback + traceback.print_exc() + self.fini() + +class ManagementDeviceThresholdData(SmbiosBaseStructure): + smbios_structure_type = 36 + + def __init__(self, u, sm): + super(ManagementDeviceThresholdData, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('lower_threshold_noncritical', u.unpack_one("<H")) + if self.length > 0x6: + self.add_field('upper_threshold_noncritical', u.unpack_one("<H")) + if self.length > 0x8: + self.add_field('lower_threshold_critical', u.unpack_one("<H")) + if self.length > 0xA: + self.add_field('upper_threshold_critical', u.unpack_one("<H")) + if self.length > 0xC: + self.add_field('lower_threshold_nonrecoverable', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('upper_threshold_nonrecoverable', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing ManagementDeviceThresholdData" + import traceback + traceback.print_exc() + self.fini() + +class MemoryChannel(SmbiosBaseStructure): + smbios_structure_type = 37 + + def __init__(self, u, sm): + super(MemoryChannel, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + _channel_type = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'RamBus', + 0x04: 'SyncLink' + } + self.add_field('channel_type', u.unpack_one("B"), unpack.format_table("{}", _channel_type)) + if self.length > 0x6: + self.add_field('max_channel_load', u.unpack_one("B")) + if self.length > 0x8: + self.add_field('memory_device_count', u.unpack_one("B")) + if self.length > 0xA: + self.add_field('memory_device_load', u.unpack_one("B")) + if self.length > 0xC: + self.add_field('memory_device_handle', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing MemoryChannel" + import traceback + traceback.print_exc() + self.fini() + +class IPMIDeviceInformation(SmbiosBaseStructure): + smbios_structure_type = 38 + + def __init__(self, u, sm): + super(IPMIDeviceInformation, self).__init__(u, sm) + u = self.u + try: + _interface_type = { + 0x00: 'Unknown', + 0x01: 'KCS: Keyboard Controller Style', + 0x02: 'SMIC: Server Management Interface Chip', + 0x03: 'BT: Block Transfer', + xrange(0x04, 0xFF): 'Reserved' + } + self.add_field('interface_type', u.unpack_one("B"), unpack.format_table("{}", _interface_type)) + self.add_field('ipmi_specification_revision', u.unpack_one("B")) + self.add_field('msd_revision', bitfields.getbits(self.ipmi_specification_revision, 7, 4), "ipmi_specification_revision[7:4]={}") + self.add_field('lsd_revision', bitfields.getbits(self.ipmi_specification_revision, 3, 0), "ipmi_specification_revision[3:0]={}") + + self.add_field('i2c_slave_address', u.unpack_one("B")) + self.add_field('nv_storage_device_address', u.unpack_one("B")) + self.add_field('base_address', u.unpack_one("<Q")) + # if lsb is 1, address is in IO space. otherwise, memory-mapped + self.add_field('base_address_modifier_interrupt_info', u.unpack_one("B")) + _reg_spacing = { + 0b00: 'Interface registers are on successive byte boundaries', + 0b01: 'Interface registers are on 32-bit boundaries', + 0b10: 'Interface registers are on 16-byte boundaries', + 0b11: 'Reserved' + } + self.add_field('register_spacing', bitfields.getbits(self.base_address_modifier_interrupt_info, 7, 6), unpack.format_table("base_address_modifier_interrupt_info[7:6]={}", _reg_spacing)) + self.add_field('ls_bit_for_addresses', bitfields.getbits(self.base_address_modifier_interrupt_info, 4), "base_address_modifier_interrupt_info[4]={}") + self.add_field('interrupt_info_specified', bool(bitfields.getbits(self.base_address_modifier_interrupt_info, 3)), "base_address_modifier_interrupt_info[3]={}") + _polarity = { + 0: 'active low', + 1: 'active high' + } + self.add_field('interrupt_polarity', bitfields.getbits(self.base_address_modifier_interrupt_info, 1), unpack.format_table("base_address_modifier_interrupt_info[1]={}", _polarity)) + _interrupt_trigger = { + 0: 'edge', + 1: 'level' + } + self.add_field('interrupt_trigger_mode', bitfields.getbits(self.base_address_modifier_interrupt_info, 0), unpack.format_table("base_address_modifier_interrupt_info[0]={}", _interrupt_trigger)) + self.add_field('interrupt_number', u.unpack_one("B")) + except: + self.decodeFailure = True + print "Error parsing IPMIDeviceInformation" + import traceback + traceback.print_exc() + self.fini() + +class SystemPowerSupply(SmbiosBaseStructure): + smbios_structure_type = 39 + + def __init__(self, u, sm): + super(SystemPowerSupply, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('power_unit_group', u.unpack_one("B")) + if self.length > 0x5: + self.add_field('location', u.unpack_one("B"), self.fmtstr) + if self.length > 0x6: + self.add_field('device_name', u.unpack_one("B"), self.fmtstr) + if self.length > 0x7: + self.add_field('manufacturer', u.unpack_one("B"), self.fmtstr) + if self.length > 0x8: + self.add_field('serial_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0x9: + self.add_field('asset_tag', u.unpack_one("B"), self.fmtstr) + if self.length > 0xA: + self.add_field('model_part_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0xB: + self.add_field('revision_level', u.unpack_one("B"), self.fmtstr) + if self.length > 0xC: + self.add_field('max_power_capacity', u.unpack_one("<H")) + if self.length > 0xE: + self.add_field('power_supply_characteristics', u.unpack_one("<H")) + _dmtf_power_supply_type = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'Linear', + 0b100: 'Switching', + 0b101: 'Battery', + 0b110: 'UPS', + 0b111: 'Converter', + 0b1000: 'Regulator', + xrange(0b1001, 0b1111): 'Reserved' + } + self.add_field('dmtf_power_supply_type', bitfields.getbits(self.power_supply_characteristics, 13, 10), unpack.format_table("power_supply_characteristics[13:10]={}", _dmtf_power_supply_type)) + _status = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'OK', + 0b100: 'Non-critical', + 0b101: 'Critical; power supply has failed and has been taken off-line' + } + self.add_field('status', bitfields.getbits(self.power_supply_characteristics, 9, 7), unpack.format_table("power_supply_characteristics[9:7]={}", _status)) + _dmtf_input_voltage_range_switching = { + 0b001: 'Other', + 0b010: 'Unknown', + 0b011: 'Manual', + 0b100: 'Auto-switch', + 0b101: 'Wide range', + 0b110: 'Not applicable', + xrange(0b0111, 0b1111): 'Reserved' + } + self.add_field('dmtf_input_voltage_range_switching', bitfields.getbits(self.power_supply_characteristics, 6, 3), unpack.format_table("power_supply_characteristics[6:3]={}", _dmtf_input_voltage_range_switching)) + self.add_field('power_supply_unplugged', bool(bitfields.getbits(self.power_supply_characteristics, 2)), "power_supply_characteristics[2]={}") + self.add_field('power_supply_present', bool(bitfields.getbits(self.power_supply_characteristics, 1)), "power_supply_characteristics[1]={}") + self.add_field('power_supply_hot_replaceable', bool(bitfields.getbits(self.power_supply_characteristics, 0)), "power_supply_characteristics[0]={}") + if self.length > 0x10: + self.add_field('input_voltage_probe_handle', u.unpack_one("<H")) + if self.length > 0x12: + self.add_field('cooling_device_handle', u.unpack_one("<H")) + if self.length > 0x14: + self.add_field('input_current_probe_handle', u.unpack_one("<H")) + except: + self.decodeFailure = True + print "Error parsing SystemPowerSupply" + import traceback + traceback.print_exc() + self.fini() + +class AdditionalInformation(SmbiosBaseStructure): + smbios_structure_type = 40 + + def __init__(self, u, sm): + super(AdditionalInformation, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('num_additional_information_entries', u.unpack_one("B")) + if self.length > 0x5: + self.add_field('additional_information_entry_length', u.unpack_one("B")) + self.add_field('referenced_handle', u.unpack_one("<H")) + self.add_field('referenced_offset', u.unpack_one("B")) + self.add_field('string', u.unpack_one("B"), self.fmtstr) + self.add_field('value', u.unpack_rest()) + except: + self.decodeFailure = True + print "Error parsing AdditionalInformation" + import traceback + traceback.print_exc() + self.fini() + +class OnboardDevicesExtendedInformation(SmbiosBaseStructure): + smbios_structure_type = 41 + + def __init__(self, u, sm): + super(OnboardDevicesExtendedInformation, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + self.add_field('reference_designation', u.unpack_one("B"), self.fmtstr) + if self.length > 0x5: + self.add_field('device_type', u.unpack_one("B")) + self.add_field('device_enabled', bool(bitfields.getbits(self.device_type, 7)), "device_type[7]={}") + _device_types = { + 0x01: 'Other', + 0x02: 'Unknown', + 0x03: 'Video', + 0x04: 'SCSI Controller', + 0x05: 'Ethernet', + 0x06: 'Token Ring', + 0x07: 'Sound', + 0x08: 'PATA Controller', + 0x09: 'SATA Controller', + 0x0A: 'SAS Controller' + } + self.add_field('type_of_device', bitfields.getbits(self.device_type, 6, 0), unpack.format_table("device_type[6:0]={}", _device_types)) + if self.length > 0x6: + self.add_field('device_type_instance', u.unpack_one("B")) + if self.length > 0x7: + self.add_field('segment_group_number', u.unpack_one("<H")) + if self.length > 0x9: + self.add_field('bus_number', u.unpack_one("B"), self.fmtstr) + if self.length > 0xA: + self.add_field('device_and_function_number', u.unpack_one("B")) + self.add_field('device_number', bitfields.getbits(self.device_type, 7, 3), "device_and_function_number[7:3]={}") + self.add_field('function_number', bitfields.getbits(self.device_type, 2, 0), "device_and_function_number[2:0]={}") + except: + self.decodeFailure = True + print "Error parsing OnboardDevicesExtendedInformation" + import traceback + traceback.print_exc() + self.fini() + +class ManagementControllerHostInterface(SmbiosBaseStructure): + smbios_structure_type = 42 + + def __init__(self, u, sm): + super(ManagementControllerHostInterface, self).__init__(u, sm) + u = self.u + try: + if self.length > 0x4: + _interface_types = { + 0x00: 'Reserved', + 0x01: 'Reserved', + 0x02: 'KCS: Keyboard Controller Style', + 0x03: '8250 UART Register Compatible', + 0x04: '16450 UART Register Compatible', + 0x05: '16550/16550A UART Register Compatible', + 0x06: '16650/16650A UART Register Compatible', + 0x07: '16750/16750A UART Register Compatible', + 0x08: '16850/16850A UART Register Compatible', + 0xF0: 'OEM' + } + self.add_field('interface_type', u.unpack_one("B"), unpack.format_table("{}", _interface_types)) + if self.length > 0x5: + self.add_field('mc_host_interface_data', u.unpack_rest(), self.fmtstr) + except: + self.decodeFailure = True + print "Error parsing ManagementControllerHostInterface" + import traceback + traceback.print_exc() + self.fini() + +class Inactive(SmbiosBaseStructure): + smbios_structure_type = 126 + + def __init__(self, u, sm): + super(Inactive, self).__init__(u, sm) + self.fini() + +class EndOfTable(SmbiosBaseStructure): + smbios_structure_type = 127 + + def __init__(self, u, sm): + super(EndOfTable, self).__init__(u, sm) + self.fini() + +class SmbiosStructureUnknown(SmbiosBaseStructure): + smbios_structure_type = None + + def __init__(self, u, sm): + super(SmbiosStructureUnknown, self).__init__(u, sm) + self.fini() + +_smbios_structures = [ + BIOSInformation, + SystemInformation, + BaseboardInformation, + SystemEnclosure, + ProcessorInformation, + MemoryControllerInformation, + MemoryModuleInformation, + CacheInformation, + PortConnectorInfo, + SystemSlots, + OnBoardDevicesInformation, + OEMStrings, + SystemConfigOptions, + BIOSLanguageInformation, + GroupAssociations, + SystemEventLog, + PhysicalMemoryArray, + MemoryDevice, + MemoryErrorInfo32Bit, + MemoryArrayMappedAddress, + MemoryDeviceMappedAddress, + BuiltInPointingDevice, + PortableBattery, + SystemReset, + HardwareSecurity, + SystemPowerControls, + VoltageProbe, + CoolingDevice, + TemperatureProbe, + ElectricalCurrentProbe, + OutOfBandRemoteAccess, + BootIntegrityServicesEntryPoint, + SystemBootInformation, + MemoryErrorInfo64Bit, + ManagementDevice, + ManagementDeviceComponent, + ManagementDeviceThresholdData, + MemoryChannel, + IPMIDeviceInformation, + SystemPowerSupply, + AdditionalInformation, + OnboardDevicesExtendedInformation, + ManagementControllerHostInterface, + Inactive, + EndOfTable, + SmbiosStructureUnknown, # Must always come last +] + +def log_smbios_info(): + with redirect.logonly(): + try: + sm = SMBIOS() + print + if sm is None: + print "No SMBIOS structures found" + return + output = {} + known_types = (0, 1) + for sm_struct in sm.structures: + if sm_struct.type in known_types: + output.setdefault(sm_struct.type, []).append(sm_struct) + if len(output) == len(known_types): + break + + print "SMBIOS information:" + for key in sorted(known_types): + for s in output.get(key, ["No structure of type {} found".format(key)]): + print ttypager._wrap("{}: {}".format(key, s)) + except: + print "Error parsing SMBIOS information:" + import traceback + traceback.print_exc() + +def dump_raw(): + try: + sm = SMBIOS() + if sm: + s = "SMBIOS -- Raw bytes and structure decode.\n\n" + + s += str(sm.header) + '\n' + s += bits.dumpmem(sm._header_memory) + '\n' + + s += "Raw bytes for the SMBIOS structures\n" + s += bits.dumpmem(sm._structure_memory) + '\n' + + for sm_struct in sm.structures: + s += str(sm_struct) + '\n' + s += bits.dumpmem(sm_struct.raw_data) + + s += "Strings:\n" + for n in range(1, len(getattr(sm_struct, "strings", [])) + 1): + s += str(sm_struct.fmtstr(n)) + '\n' + s += bits.dumpmem(sm_struct.raw_strings) + '\n' + else: + s = "No SMBIOS structures found" + ttypager.ttypager_wrap(s, indent=False) + except: + print "Error parsing SMBIOS information:" + import traceback + traceback.print_exc() + +def dump(): + try: + sm = SMBIOS() + if sm: + s = str(sm) + else: + s = "No SMBIOS structures found" + ttypager.ttypager_wrap(s, indent=False) + except: + print "Error parsing SMBIOS information:" + import traceback + traceback.print_exc() + +def annex_a_conformance(): + try: + sm = SMBIOS() + + # check: 1. The table anchor string "_SM_" is present in the address range 0xF0000 to 0xFFFFF on a 16-byte bound + + def table_entry_point_verification(): + ''' Verify table entry-point''' + if (sm.header.length < 0x1F): + print "Failure: Table entry-point - The entry-point Length must be at least 0x1F" + if sm.header.checksum != 0: + print "Failure: Table entry-point - The entry-point checksum must evaluate to 0" + if ((sm.header.major_version < 2) and (sm.header.minor_version < 4)): + print "Failure: Table entry-point - SMBIOS version must be at least 2.4" + if (sm.header.intermediate_anchor_string == '_DMI_'): + print "Failure: Table entry-point - The Intermediate Anchor String must be '_DMI_'" + if (sm.header.intermediate_checksum != 0): + print "Failure: Table entry-point - The Intermediate checksum must evaluate to 0" + + #check: 3. The structure-table is traversable and conforms to the entry-point specifications: + + def req_structures(): + '''Checks for required structures and corresponding data''' + types_present = [sm.structures[x].smbios_structure_type for x in range(len(sm.structures))] + required = [0, 1, 4, 7, 9, 16, 17, 19, 31, 32] + for s in required: + if s not in set(types_present): + print "Failure: Type {} required but not found".format(s) + + else: + if s == 0: + if types_present.count(s) > 1: + print "Failure: Type {} - One and only one structure of this type must be present.".format(s) + if sm.structure_type(s).length < 0x18: + print "Failure: Type {} - The structure Length field must be at least 0x18".format(s) + if sm.structure_type(s).version is None: + print "Failure: Type {} - BIOS Version string must be present and non-null.".format(s) + if sm.structure_type(s).release_date is None: + print "Failure: Type {} - BIOS Release Date string must be present, non-null, and include a 4-digit year".format(s) + if bitfields.getbits(sm.structure_type(s).characteristics, 3, 0) != 0 or bitfields.getbits(sm.structure_type(s).characteristics, 31, 4) == 0: + print "Failure: Type {} - BIOS Characteristics: bits 3:0 must all be 0, and at least one of bits 31:4 must be set to 1.".format(s) + elif s == 1: + if types_present.count(s) > 1: + print "Failure: Type {} - One and only one structure of this type must be present.".format(s) + if sm.structure_type(s).length < 0x1B: + print "Failure: Type {} - The structure Length field must be at least 0x1B".format(s) + if sm.structure_type(s).manufacturer == None: + print "Failure: Type {} - Manufacturer string must be present and non-null.".format(s) + if sm.structure_type(s).product_name == None: + print "Failure: Type {} - Product Name string must be present and non-null".format(s) + if sm.structure_type(s).uuid == '00000000 00000000' and sm.structure_type(s).uuid == 'FFFFFFFF FFFFFFFF': + print "Failure: Type {} - UUID field must be neither 00000000 00000000 nor FFFFFFFF FFFFFFFF.".format(s) + if sm.structure_type(s).wakeup_type == 00 and sm.structure_type(s).wakeup_type == 0x02: + print "Failure: Type {} - Wake-up Type field must be neither 00h (Reserved) nor 02h (Unknown).".format(s) + # continue for remaining required types + + # check remaining conformance guidelines + + table_entry_point_verification() + req_structures() + except: + print "Error checking ANNEX A conformance guidelines" + import traceback + traceback.print_exc() diff --git a/tests/avocado/acpi-bits/bits-tests/smilatency.py2 b/tests/avocado/acpi-bits/bits-tests/smilatency.py2 new file mode 100644 index 0000000000..405af67e19 --- /dev/null +++ b/tests/avocado/acpi-bits/bits-tests/smilatency.py2 @@ -0,0 +1,107 @@ +# Copyright (c) 2015, Intel Corporation +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This script runs only from the biosbits VM. + +"""SMI latency test.""" + +import bits +from collections import namedtuple +import testsuite +import time +import usb + +def register_tests(): + pass +# testsuite.add_test("SMI latency test", smi_latency); +# testsuite.add_test("SMI latency test with USB disabled via BIOS handoff", test_with_usb_disabled, runall=False); + +def smi_latency(): + MSR_SMI_COUNT = 0x34 + + print "Warning: touching the keyboard can affect the results of this test." + + tsc_per_sec = bits.tsc_per_sec() + tsc_per_usec = tsc_per_sec / (1000 * 1000) + bins = [long(tsc_per_usec * 10**i) for i in range(9)] + bin_descs = [ + "0 < t <= 1us", + "1us < t <= 10us", + "10us < t <= 100us", + "100us < t <= 1ms", + "1ms < t <= 10ms", + "10ms < t <= 100ms", + "100ms < t <= 1s ", + "1s < t <= 10s ", + "10s < t <= 100s ", + "100s < t ", + ] + + print "Starting test. Wait here, I will be back in 15 seconds." + (max_latency, smi_count_delta, bins) = bits.smi_latency(long(15 * tsc_per_sec), bins) + BinType = namedtuple('BinType', ("max", "total", "count", "times")) + bins = [BinType(*b) for b in bins] + + testsuite.test("SMI latency < 150us to minimize risk of OS timeouts", max_latency / tsc_per_usec <= 150) + if not testsuite.show_detail(): + return + + for bin, desc in zip(bins, bin_descs): + if bin.count == 0: + continue + testsuite.print_detail("{}; average = {}; count = {}".format(desc, bits.format_tsc(bin.total/bin.count), bin.count)) + deltas = (bits.format_tsc(t2 - t1) for t1,t2 in zip(bin.times, bin.times[1:])) + testsuite.print_detail(" Times between first few observations: {}".format(" ".join("{:>6}".format(delta) for delta in deltas))) + + if smi_count_delta is not None: + testsuite.print_detail("{} SMI detected using MSR_SMI_COUNT (MSR {:#x})".format(smi_count_delta, MSR_SMI_COUNT)) + + testsuite.print_detail("Summary of impact: observed maximum latency = {}".format(bits.format_tsc(max_latency))) + +def test_with_usb_disabled(): + if usb.handoff_to_os(): + smi_latency() + +def average_io_smi(port, value, count): + def f(): + tsc_start = bits.rdtsc() + bits.outb(port, value) + return bits.rdtsc() - tsc_start + counts = [f() for i in range(count)] + return sum(counts)/len(counts) + +def time_io_smi(port=0xb2, value=0, count=1000): + count_for_estimate = 10 + start = time.time() + average_io_smi(port, value, count_for_estimate) + avg10 = time.time() - start + estimate = avg10 * count/count_for_estimate + if estimate > 1: + print "Running test, estimated time: {}s".format(int(estimate)) + average = average_io_smi(port, value, count) + print "Average of {} SMIs (via outb, port={:#x}, value={:#x}): {}".format(count, port, value, bits.format_tsc(average)) diff --git a/tests/avocado/acpi-bits/bits-tests/testacpi.py2 b/tests/avocado/acpi-bits/bits-tests/testacpi.py2 new file mode 100644 index 0000000000..7bf9075c1b --- /dev/null +++ b/tests/avocado/acpi-bits/bits-tests/testacpi.py2 @@ -0,0 +1,287 @@ +# Copyright (c) 2015, Intel Corporation +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This script runs only from the biosbits VM. + +"""Tests for ACPI""" + +import acpi +import bits +import bits.mwait +import struct +import testutil +import testsuite +import time + +def register_tests(): + testsuite.add_test("ACPI _MAT (Multiple APIC Table Entry) under Processor objects", test_mat, submenu="ACPI Tests") +# testsuite.add_test("ACPI _PSS (Pstate) table conformance tests", test_pss, submenu="ACPI Tests") +# testsuite.add_test("ACPI _PSS (Pstate) runtime tests", test_pstates, submenu="ACPI Tests") + testsuite.add_test("ACPI DSDT (Differentiated System Description Table)", test_dsdt, submenu="ACPI Tests") + testsuite.add_test("ACPI FACP (Fixed ACPI Description Table)", test_facp, submenu="ACPI Tests") + testsuite.add_test("ACPI HPET (High Precision Event Timer Table)", test_hpet, submenu="ACPI Tests") + testsuite.add_test("ACPI MADT (Multiple APIC Description Table)", test_apic, submenu="ACPI Tests") + testsuite.add_test("ACPI MPST (Memory Power State Table)", test_mpst, submenu="ACPI Tests") + testsuite.add_test("ACPI RSDP (Root System Description Pointer Structure)", test_rsdp, submenu="ACPI Tests") + testsuite.add_test("ACPI XSDT (Extended System Description Table)", test_xsdt, submenu="ACPI Tests") + +def test_mat(): + cpupaths = acpi.get_cpupaths() + apic = acpi.parse_apic() + procid_apicid = apic.procid_apicid + uid_x2apicid = apic.uid_x2apicid + for cpupath in cpupaths: + # Find the ProcId defined by the processor object + processor = acpi.evaluate(cpupath) + # Find the UID defined by the processor object's _UID method + uid = acpi.evaluate(cpupath + "._UID") + mat_buffer = acpi.evaluate(cpupath + "._MAT") + if mat_buffer is None: + continue + # Process each _MAT subtable + mat = acpi._MAT(mat_buffer) + for index, subtable in enumerate(mat): + if subtable.subtype == acpi.MADT_TYPE_LOCAL_APIC: + if subtable.flags.bits.enabled: + testsuite.test("{} Processor declaration ProcId = _MAT ProcId".format(cpupath), processor.ProcId == subtable.proc_id) + testsuite.print_detail("{} ProcId ({:#02x}) != _MAT ProcId ({:#02x})".format(cpupath, processor.ProcId, subtable.proc_id)) + testsuite.print_detail("Processor Declaration: {}".format(processor)) + testsuite.print_detail("_MAT entry[{}]: {}".format(index, subtable)) + if testsuite.test("{} with local APIC in _MAT has local APIC in MADT".format(cpupath), processor.ProcId in procid_apicid): + testsuite.test("{} ApicId derived using Processor declaration ProcId = _MAT ApicId".format(cpupath), procid_apicid[processor.ProcId] == subtable.apic_id) + testsuite.print_detail("{} ApicId derived from MADT ({:#02x}) != _MAT ApicId ({:#02x})".format(cpupath, procid_apicid[processor.ProcId], subtable.apic_id)) + testsuite.print_detail("Processor Declaration: {}".format(processor)) + testsuite.print_detail("_MAT entry[{}]: {}".format(index, subtable)) + if subtable.subtype == acpi.MADT_TYPE_LOCAL_X2APIC: + if subtable.flags.bits.enabled: + if testsuite.test("{} with x2Apic in _MAT has _UID".format(cpupath), uid is not None): + testsuite.test("{}._UID = _MAT UID".format(cpupath), uid == subtable.uid) + testsuite.print_detail("{}._UID ({:#x}) != _MAT UID ({:#x})".format(cpupath, uid, subtable.uid)) + testsuite.print_detail("_MAT entry[{}]: {}".format(index, subtable)) + if testsuite.test("{} with _MAT x2Apic has x2Apic in MADT".format(cpupath), subtable.uid in uid_x2apicid): + testsuite.test("{} x2ApicId derived from MADT using UID = _MAT x2ApicId".format(cpupath), uid_x2apicid[subtable.uid] == subtable.x2apicid) + testsuite.print_detail("{} x2ApicId derived from MADT ({:#02x}) != _MAT x2ApicId ({:#02x})".format(cpupath, uid_x2apicid[subtable.uid], subtable.x2apicid)) + testsuite.print_detail("_MAT entry[{}]: {}".format(index, subtable)) + +def test_pss(): + uniques = acpi.parse_cpu_method("_PSS") + # We special-case None here to avoid a double-failure for CPUs without a _PSS + testsuite.test("_PSS must be identical for all CPUs", len(uniques) <= 1 or (len(uniques) == 2 and None in uniques)) + for pss, cpupaths in uniques.iteritems(): + if not testsuite.test("_PSS must exist", pss is not None): + testsuite.print_detail(acpi.factor_commonprefix(cpupaths)) + testsuite.print_detail('No _PSS exists') + continue + + if not testsuite.test("_PSS must not be empty", pss.pstates): + testsuite.print_detail(acpi.factor_commonprefix(cpupaths)) + testsuite.print_detail('_PSS is empty') + continue + + testsuite.print_detail(acpi.factor_commonprefix(cpupaths)) + for index, pstate in enumerate(pss.pstates): + testsuite.print_detail("P[{}]: {}".format(index, pstate)) + + testsuite.test("_PSS must contain at most 16 Pstates", len(pss.pstates) <= 16) + testsuite.test("_PSS must have no duplicate Pstates", len(pss.pstates) == len(set(pss.pstates))) + + frequencies = [p.core_frequency for p in pss.pstates] + testsuite.test("_PSS must list Pstates in descending order of frequency", frequencies == sorted(frequencies, reverse=True)) + + testsuite.test("_PSS must have Pstates with no duplicate frequencies", len(frequencies) == len(set(frequencies))) + + dissipations = [p.power for p in pss.pstates] + testsuite.test("_PSS must list Pstates in descending order of power dissipation", dissipations == sorted(dissipations, reverse=True)) + +def test_pstates(): + """Execute and verify frequency for each Pstate in the _PSS""" + IA32_PERF_CTL = 0x199 + with bits.mwait.use_hint(), bits.preserve_msr(IA32_PERF_CTL): + cpupath_procid = acpi.find_procid() + cpupath_uid = acpi.find_uid() + apic = acpi.parse_apic() + procid_apicid = apic.procid_apicid + uid_x2apicid = apic.uid_x2apicid + def cpupath_apicid(cpupath): + if procid_apicid is not None: + procid = cpupath_procid.get(cpupath, None) + if procid is not None: + apicid = procid_apicid.get(procid, None) + if apicid is not None: + return apicid + if uid_x2apicid is not None: + uid = cpupath_uid.get(cpupath, None) + if uid is not None: + apicid = uid_x2apicid.get(uid, None) + if apicid is not None: + return apicid + return bits.cpus()[0] + + bclk = testutil.adjust_to_nearest(bits.bclk(), 100.0/12) * 1000000 + + uniques = acpi.parse_cpu_method("_PSS") + for pss, cpupaths in uniques.iteritems(): + if not testsuite.test("_PSS must exist", pss is not None): + testsuite.print_detail(acpi.factor_commonprefix(cpupaths)) + testsuite.print_detail('No _PSS exists') + continue + + for n, pstate in enumerate(pss.pstates): + for cpupath in cpupaths: + apicid = cpupath_apicid(cpupath) + if apicid is None: + print 'Failed to find apicid for cpupath {}'.format(cpupath) + continue + bits.wrmsr(apicid, IA32_PERF_CTL, pstate.control) + + # Detecting Turbo frequency requires at least 2 pstates + # since turbo frequency = max non-turbo frequency + 1 + turbo = False + if len(pss.pstates) >= 2: + turbo = (n == 0 and pstate.core_frequency == (pss.pstates[1].core_frequency + 1)) + if turbo: + # Needs to busywait, not sleep + start = time.time() + while (time.time() - start < 2): + pass + + for duration in (0.1, 1.0): + frequency_data = bits.cpu_frequency(duration) + # Abort the test if no cpu frequency is not available + if frequency_data is None: + continue + aperf = frequency_data[1] + aperf = testutil.adjust_to_nearest(aperf, bclk/2) + aperf = int(aperf / 1000000) + if turbo: + if aperf >= pstate.core_frequency: + break + else: + if aperf == pstate.core_frequency: + break + + if turbo: + testsuite.test("P{}: Turbo measured frequency {} >= expected {} MHz".format(n, aperf, pstate.core_frequency), aperf >= pstate.core_frequency) + else: + testsuite.test("P{}: measured frequency {} MHz == expected {} MHz".format(n, aperf, pstate.core_frequency), aperf == pstate.core_frequency) + +def test_psd_thread_scope(): + uniques = acpi.parse_cpu_method("_PSD") + if not testsuite.test("_PSD (P-State Dependency) must exist for each processor", None not in uniques): + testsuite.print_detail(acpi.factor_commonprefix(uniques[None])) + testsuite.print_detail('No _PSD exists') + return + unique_num_dependencies = {} + unique_num_entries = {} + unique_revision = {} + unique_domain = {} + unique_coordination_type = {} + unique_num_processors = {} + for value, cpupaths in uniques.iteritems(): + unique_num_dependencies.setdefault(len(value.dependencies), []).extend(cpupaths) + unique_num_entries.setdefault(value.dependencies[0].num_entries, []).extend(cpupaths) + unique_revision.setdefault(value.dependencies[0].revision, []).extend(cpupaths) + unique_domain.setdefault(value.dependencies[0].domain, []).extend(cpupaths) + unique_coordination_type.setdefault(value.dependencies[0].coordination_type, []).extend(cpupaths) + unique_num_processors.setdefault(value.dependencies[0].num_processors, []).extend(cpupaths) + def detail(d, fmt): + for value, cpupaths in sorted(d.iteritems(), key=(lambda (k,v): v)): + testsuite.print_detail(acpi.factor_commonprefix(cpupaths)) + testsuite.print_detail(fmt.format(value)) + + testsuite.test('Dependency count for each processor must be 1', unique_num_dependencies.keys() == [1]) + detail(unique_num_dependencies, 'Dependency count for each processor = {} (Expected 1)') + testsuite.test('_PSD.num_entries must be 5', unique_num_entries.keys() == [5]) + detail(unique_num_entries, 'num_entries = {} (Expected 5)') + testsuite.test('_PSD.revision must be 0', unique_revision.keys() == [0]) + detail(unique_revision, 'revision = {}') + testsuite.test('_PSD.coordination_type must be 0xFE (HW_ALL)', unique_coordination_type.keys() == [0xfe]) + detail(unique_coordination_type, 'coordination_type = {:#x} (Expected 0xFE HW_ALL)') + testsuite.test('_PSD.domain must be unique (thread-scoped) for each processor', len(unique_domain) == len(acpi.get_cpupaths())) + detail(unique_domain, 'domain = {:#x} (Expected a unique value for each processor)') + testsuite.test('_PSD.num_processors must be 1', unique_num_processors.keys() == [1]) + detail(unique_num_processors, 'num_processors = {} (Expected 1)') + +def test_table_checksum(data): + csum = sum(ord(c) for c in data) % 0x100 + testsuite.test('ACPI table cumulative checksum must equal 0', csum == 0) + testsuite.print_detail("Cumulative checksum = {} (Expected 0)".format(csum)) + +def test_apic(): + data = acpi.get_table("APIC") + if data is None: + return + test_table_checksum(data) + apic = acpi.parse_apic() + +def test_dsdt(): + data = acpi.get_table("DSDT") + if data is None: + return + test_table_checksum(data) + +def test_facp(): + data = acpi.get_table("FACP") + if data is None: + return + test_table_checksum(data) + facp = acpi.parse_facp() + +def test_hpet(): + data = acpi.get_table("HPET") + if data is None: + return + test_table_checksum(data) + hpet = acpi.parse_hpet() + +def test_mpst(): + data = acpi.get_table("MPST") + if data is None: + return + test_table_checksum(data) + mpst = acpi.MPST(data) + +def test_rsdp(): + data = acpi.get_table("RSD PTR ") + if data is None: + return + + # Checksum the first 20 bytes per ACPI 1.0 + csum = sum(ord(c) for c in data[:20]) % 0x100 + testsuite.test('ACPI 1.0 table first 20 bytes cumulative checksum must equal 0', csum == 0) + testsuite.print_detail("Cumulative checksum = {} (Expected 0)".format(csum)) + + test_table_checksum(data) + rsdp = acpi.parse_rsdp() + +def test_xsdt(): + data = acpi.get_table("XSDT") + if data is None: + return + test_table_checksum(data) + xsdt = acpi.parse_xsdt() diff --git a/tests/avocado/acpi-bits/bits-tests/testcpuid.py2 b/tests/avocado/acpi-bits/bits-tests/testcpuid.py2 new file mode 100644 index 0000000000..7adefbe355 --- /dev/null +++ b/tests/avocado/acpi-bits/bits-tests/testcpuid.py2 @@ -0,0 +1,87 @@ +# Copyright (c) 2012, Intel Corporation +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This script runs only from the biosbits VM. + +"""Tests and helpers for CPUID.""" + +import bits +import testsuite +import testutil + +def cpuid_helper(function, index=None, shift=0, mask=~0, eax_mask=~0, ebx_mask=~0, ecx_mask=~0, edx_mask=~0): + if index is None: + index = 0 + indexdesc = "" + else: + indexdesc = " index {0:#x}".format(index) + + def find_mask(m): + if m == ~0: + return mask + return m + masks = map(find_mask, [eax_mask, ebx_mask, ecx_mask, edx_mask]) + + uniques = {} + for cpu in bits.cpus(): + regs = bits.cpuid_result(*[(r >> shift) & m for r, m in zip(bits.cpuid(cpu, function, index), masks)]) + uniques.setdefault(regs, []).append(cpu) + + desc = ["CPUID function {:#x}{}".format(function, indexdesc)] + + if shift != 0: + desc.append("Register values have been shifted by {}".format(shift)) + if mask != ~0 or eax_mask != ~0 or ebx_mask != ~0 or ecx_mask != ~0 or edx_mask != ~0: + desc.append("Register values have been masked:") + shifted_masks = bits.cpuid_result(*[m << shift for m in masks]) + desc.append("Masks: eax={eax:#010x} ebx={ebx:#010x} ecx={ecx:#010x} edx={edx:#010x}".format(**shifted_masks._asdict())) + + if len(uniques) > 1: + regvalues = zip(*uniques.iterkeys()) + common_masks = bits.cpuid_result(*map(testutil.find_common_mask, regvalues)) + common_values = bits.cpuid_result(*[v[0] & m for v, m in zip(regvalues, common_masks)]) + desc.append('Register values are not unique across all logical processors') + desc.append("Common bits: eax={eax:#010x} ebx={ebx:#010x} ecx={ecx:#010x} edx={edx:#010x}".format(**common_values._asdict())) + desc.append("Mask of common bits: {eax:#010x} {ebx:#010x} {ecx:#010x} {edx:#010x}".format(**common_masks._asdict())) + + for regs in sorted(uniques.iterkeys()): + cpus = uniques[regs] + desc.append("Register value: eax={eax:#010x} ebx={ebx:#010x} ecx={ecx:#010x} edx={edx:#010x}".format(**regs._asdict())) + desc.append("On {0} CPUs: {1}".format(len(cpus), testutil.apicid_list(cpus))) + + return uniques, desc + +def test_cpuid_consistency(text, function, index=None, shift=0, mask=~0, eax_mask=~0, ebx_mask=~0, ecx_mask=~0, edx_mask=~0): + uniques, desc = cpuid_helper(function, index, shift, mask, eax_mask, ebx_mask, ecx_mask, edx_mask) + desc[0] += " Consistency Check" + if text: + desc.insert(0, text) + status = testsuite.test(desc[0], len(uniques) == 1) + for line in desc[1:]: + testsuite.print_detail(line) + return status diff --git a/tests/avocado/avocado_qemu/__init__.py b/tests/avocado/avocado_qemu/__init__.py new file mode 100644 index 0000000000..304c428168 --- /dev/null +++ b/tests/avocado/avocado_qemu/__init__.py @@ -0,0 +1,681 @@ +# Test class and utilities for functional tests +# +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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 logging +import os +import shutil +import subprocess +import sys +import tempfile +import time +import uuid + +import avocado +from avocado.utils import cloudinit, datadrainer, process, ssh, vmimage +from avocado.utils.path import find_command + +from qemu.machine import QEMUMachine +from qemu.utils import (get_info_usernet_hostfwd_port, kvm_available, + tcg_available) + + +#: The QEMU build root directory. It may also be the source directory +#: if building from the source dir, but it's safer to use BUILD_DIR for +#: that purpose. Be aware that if this code is moved outside of a source +#: and build tree, it will not be accurate. +BUILD_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + +if os.path.islink(os.path.dirname(os.path.dirname(__file__))): + # The link to the avocado tests dir in the source code directory + lnk = os.path.dirname(os.path.dirname(__file__)) + #: The QEMU root source directory + SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk))) +else: + SOURCE_DIR = BUILD_DIR + + +def has_cmd(name, args=None): + """ + This function is for use in a @avocado.skipUnless decorator, e.g.: + + @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true'))) + def test_something_that_needs_sudo(self): + ... + """ + + if args is None: + args = ('which', name) + + try: + _, stderr, exitcode = run_cmd(args) + except Exception as e: + exitcode = -1 + stderr = str(e) + + if exitcode != 0: + cmd_line = ' '.join(args) + err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}' + return (False, err) + else: + return (True, '') + +def has_cmds(*cmds): + """ + This function is for use in a @avocado.skipUnless decorator and + allows checking for the availability of multiple commands, e.g.: + + @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')), + 'cmd2', 'cmd3')) + def test_something_that_needs_cmd1_and_cmd2(self): + ... + """ + + for cmd in cmds: + if isinstance(cmd, str): + cmd = (cmd,) + + ok, errstr = has_cmd(*cmd) + if not ok: + return (False, errstr) + + return (True, '') + +def run_cmd(args): + subp = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + stdout, stderr = subp.communicate() + ret = subp.returncode + + return (stdout, stderr, ret) + +def is_readable_executable_file(path): + return os.path.isfile(path) and os.access(path, os.R_OK | os.X_OK) + + +def pick_default_qemu_bin(bin_prefix='qemu-system-', arch=None): + """ + Picks the path of a QEMU binary, starting either in the current working + directory or in the source tree root directory. + + :param arch: the arch to use when looking for a QEMU binary (the target + will match the arch given). If None (the default), arch + will be the current host system arch (as given by + :func:`os.uname`). + :type arch: str + :returns: the path to the default QEMU binary or None if one could not + be found + :rtype: str or None + """ + if arch is None: + arch = os.uname()[4] + # qemu binary path does not match arch for powerpc, handle it + if 'ppc64le' in arch: + arch = 'ppc64' + qemu_bin_name = bin_prefix + arch + qemu_bin_paths = [ + os.path.join(".", qemu_bin_name), + os.path.join(BUILD_DIR, qemu_bin_name), + os.path.join(BUILD_DIR, "build", qemu_bin_name), + ] + for path in qemu_bin_paths: + if is_readable_executable_file(path): + return path + return None + + +def _console_interaction(test, success_message, failure_message, + send_string, keep_sending=False, vm=None): + assert not keep_sending or send_string + if vm is None: + vm = test.vm + console = vm.console_file + console_logger = logging.getLogger('console') + while True: + if send_string: + vm.console_socket.sendall(send_string.encode()) + if not keep_sending: + send_string = None # send only once + try: + msg = console.readline().decode().strip() + except UnicodeDecodeError: + msg = None + if not msg: + continue + console_logger.debug(msg) + if success_message is None or success_message in msg: + break + if failure_message and failure_message in msg: + console.close() + fail = 'Failure message found in console: "%s". Expected: "%s"' % \ + (failure_message, success_message) + test.fail(fail) + +def interrupt_interactive_console_until_pattern(test, success_message, + failure_message=None, + interrupt_string='\r'): + """ + Keep sending a string to interrupt a console prompt, while logging the + console output. Typical use case is to break a boot loader prompt, such: + + Press a key within 5 seconds to interrupt boot process. + 5 + 4 + 3 + 2 + 1 + Booting default image... + + :param test: an Avocado test containing a VM that will have its console + read and probed for a success or failure message + :type test: :class:`avocado_qemu.QemuSystemTest` + :param success_message: if this message appears, test succeeds + :param failure_message: if this message appears, test fails + :param interrupt_string: a string to send to the console before trying + to read a new line + """ + _console_interaction(test, success_message, failure_message, + interrupt_string, True) + +def wait_for_console_pattern(test, success_message, failure_message=None, + vm=None): + """ + Waits for messages to appear on the console, while logging the content + + :param test: an Avocado test containing a VM that will have its console + read and probed for a success or failure message + :type test: :class:`avocado_qemu.QemuSystemTest` + :param success_message: if this message appears, test succeeds + :param failure_message: if this message appears, test fails + """ + _console_interaction(test, success_message, failure_message, None, vm=vm) + +def exec_command(test, command): + """ + Send a command to a console (appending CRLF characters), while logging + the content. + + :param test: an Avocado test containing a VM. + :type test: :class:`avocado_qemu.QemuSystemTest` + :param command: the command to send + :type command: str + """ + _console_interaction(test, None, None, command + '\r') + +def exec_command_and_wait_for_pattern(test, command, + success_message, failure_message=None): + """ + Send a command to a console (appending CRLF characters), then wait + for success_message to appear on the console, while logging the. + content. Mark the test as failed if failure_message is found instead. + + :param test: an Avocado test containing a VM that will have its console + read and probed for a success or failure message + :type test: :class:`avocado_qemu.QemuSystemTest` + :param command: the command to send + :param success_message: if this message appears, test succeeds + :param failure_message: if this message appears, test fails + """ + _console_interaction(test, success_message, failure_message, command + '\r') + +class QemuBaseTest(avocado.Test): + + # default timeout for all tests, can be overridden + timeout = 120 + + def _get_unique_tag_val(self, tag_name): + """ + Gets a tag value, if unique for a key + """ + vals = self.tags.get(tag_name, []) + if len(vals) == 1: + return vals.pop() + return None + + def setUp(self, bin_prefix): + self.arch = self.params.get('arch', + default=self._get_unique_tag_val('arch')) + + self.cpu = self.params.get('cpu', + default=self._get_unique_tag_val('cpu')) + + default_qemu_bin = pick_default_qemu_bin(bin_prefix, arch=self.arch) + self.qemu_bin = self.params.get('qemu_bin', + default=default_qemu_bin) + if self.qemu_bin is None: + self.cancel("No QEMU binary defined or found in the build tree") + + def fetch_asset(self, name, + asset_hash, algorithm=None, + locations=None, expire=None, + find_only=False, cancel_on_missing=True): + return super().fetch_asset(name, + asset_hash=asset_hash, + algorithm=algorithm, + locations=locations, + expire=expire, + find_only=find_only, + cancel_on_missing=cancel_on_missing) + + +class QemuSystemTest(QemuBaseTest): + """Facilitates system emulation tests.""" + + def setUp(self): + self._vms = {} + + super().setUp('qemu-system-') + + accel_required = self._get_unique_tag_val('accel') + if accel_required: + self.require_accelerator(accel_required) + + self.machine = self.params.get('machine', + default=self._get_unique_tag_val('machine')) + + def require_accelerator(self, accelerator): + """ + Requires an accelerator to be available for the test to continue + + It takes into account the currently set qemu binary. + + If the check fails, the test is canceled. If the check itself + for the given accelerator is not available, the test is also + canceled. + + :param accelerator: name of the accelerator, such as "kvm" or "tcg" + :type accelerator: str + """ + checker = {'tcg': tcg_available, + 'kvm': kvm_available}.get(accelerator) + if checker is None: + self.cancel("Don't know how to check for the presence " + "of accelerator %s" % accelerator) + if not checker(qemu_bin=self.qemu_bin): + self.cancel("%s accelerator does not seem to be " + "available" % accelerator) + + def require_netdev(self, netdevname): + netdevhelp = run_cmd([self.qemu_bin, + '-M', 'none', '-netdev', 'help'])[0]; + if netdevhelp.find('\n' + netdevname + '\n') < 0: + self.cancel('no support for user networking') + + def require_multiprocess(self): + """ + Test for the presence of the x-pci-proxy-dev which is required + to support multiprocess. + """ + devhelp = run_cmd([self.qemu_bin, + '-M', 'none', '-device', 'help'])[0]; + if devhelp.find('x-pci-proxy-dev') < 0: + self.cancel('no support for multiprocess device emulation') + + def _new_vm(self, name, *args): + self._sd = tempfile.TemporaryDirectory(prefix="qemu_") + vm = QEMUMachine(self.qemu_bin, base_temp_dir=self.workdir, + log_dir=self.logdir) + self.log.debug('QEMUMachine "%s" created', name) + self.log.debug('QEMUMachine "%s" temp_dir: %s', name, vm.temp_dir) + self.log.debug('QEMUMachine "%s" log_dir: %s', name, vm.log_dir) + if args: + vm.add_args(*args) + return vm + + def get_qemu_img(self): + self.log.debug('Looking for and selecting a qemu-img binary') + + # If qemu-img has been built, use it, otherwise the system wide one + # will be used. + qemu_img = os.path.join(BUILD_DIR, 'qemu-img') + if not os.path.exists(qemu_img): + qemu_img = find_command('qemu-img', False) + if qemu_img is False: + self.cancel('Could not find "qemu-img"') + + return qemu_img + + @property + def vm(self): + return self.get_vm(name='default') + + def get_vm(self, *args, name=None): + if not name: + name = str(uuid.uuid4()) + if self._vms.get(name) is None: + self._vms[name] = self._new_vm(name, *args) + if self.cpu is not None: + self._vms[name].add_args('-cpu', self.cpu) + if self.machine is not None: + self._vms[name].set_machine(self.machine) + return self._vms[name] + + def set_vm_arg(self, arg, value): + """ + Set an argument to list of extra arguments to be given to the QEMU + binary. If the argument already exists then its value is replaced. + + :param arg: the QEMU argument, such as "-cpu" in "-cpu host" + :type arg: str + :param value: the argument value, such as "host" in "-cpu host" + :type value: str + """ + if not arg or not value: + return + if arg not in self.vm.args: + self.vm.args.extend([arg, value]) + else: + idx = self.vm.args.index(arg) + 1 + if idx < len(self.vm.args): + self.vm.args[idx] = value + else: + self.vm.args.append(value) + + def tearDown(self): + for vm in self._vms.values(): + vm.shutdown() + self._sd = None + super().tearDown() + + +class QemuUserTest(QemuBaseTest): + """Facilitates user-mode emulation tests.""" + + def setUp(self): + self._ldpath = [] + super().setUp('qemu-') + + def add_ldpath(self, ldpath): + self._ldpath.append(os.path.abspath(ldpath)) + + def run(self, bin_path, args=[]): + qemu_args = " ".join(["-L %s" % ldpath for ldpath in self._ldpath]) + bin_args = " ".join(args) + return process.run("%s %s %s %s" % (self.qemu_bin, qemu_args, + bin_path, bin_args)) + + +class LinuxSSHMixIn: + """Contains utility methods for interacting with a guest via SSH.""" + + def ssh_connect(self, username, credential, credential_is_key=True): + self.ssh_logger = logging.getLogger('ssh') + res = self.vm.cmd('human-monitor-command', + command_line='info usernet') + port = get_info_usernet_hostfwd_port(res) + self.assertIsNotNone(port) + self.assertGreater(port, 0) + self.log.debug('sshd listening on port: %d', port) + if credential_is_key: + self.ssh_session = ssh.Session('127.0.0.1', port=port, + user=username, key=credential) + else: + self.ssh_session = ssh.Session('127.0.0.1', port=port, + user=username, password=credential) + for i in range(10): + try: + self.ssh_session.connect() + return + except: + time.sleep(i) + self.fail('ssh connection timeout') + + def ssh_command(self, command): + self.ssh_logger.info(command) + result = self.ssh_session.cmd(command) + stdout_lines = [line.rstrip() for line + in result.stdout_text.splitlines()] + for line in stdout_lines: + self.ssh_logger.info(line) + stderr_lines = [line.rstrip() for line + in result.stderr_text.splitlines()] + for line in stderr_lines: + self.ssh_logger.warning(line) + + self.assertEqual(result.exit_status, 0, + f'Guest command failed: {command}') + return stdout_lines, stderr_lines + + def ssh_command_output_contains(self, cmd, exp): + stdout, _ = self.ssh_command(cmd) + for line in stdout: + if exp in line: + break + else: + self.fail('"%s" output does not contain "%s"' % (cmd, exp)) + +class LinuxDistro: + """Represents a Linux distribution + + Holds information of known distros. + """ + #: A collection of known distros and their respective image checksum + KNOWN_DISTROS = { + 'fedora': { + '31': { + 'x86_64': + {'checksum': ('e3c1b309d9203604922d6e255c2c5d09' + '8a309c2d46215d8fc026954f3c5c27a0'), + 'pxeboot_url': ('https://archives.fedoraproject.org/' + 'pub/archive/fedora/linux/releases/31/' + 'Everything/x86_64/os/images/pxeboot/'), + 'kernel_params': ('root=UUID=b1438b9b-2cab-4065-a99a-' + '08a96687f73c ro no_timer_check ' + 'net.ifnames=0 console=tty1 ' + 'console=ttyS0,115200n8'), + }, + 'aarch64': + {'checksum': ('1e18d9c0cf734940c4b5d5ec592facae' + 'd2af0ad0329383d5639c997fdf16fe49'), + 'pxeboot_url': 'https://archives.fedoraproject.org/' + 'pub/archive/fedora/linux/releases/31/' + 'Everything/aarch64/os/images/pxeboot/', + 'kernel_params': ('root=UUID=b6950a44-9f3c-4076-a9c2-' + '355e8475b0a7 ro earlyprintk=pl011,0x9000000' + ' ignore_loglevel no_timer_check' + ' printk.time=1 rd_NO_PLYMOUTH' + ' console=ttyAMA0'), + }, + 'ppc64': + {'checksum': ('7c3528b85a3df4b2306e892199a9e1e4' + '3f991c506f2cc390dc4efa2026ad2f58')}, + 's390x': + {'checksum': ('4caaab5a434fd4d1079149a072fdc789' + '1e354f834d355069ca982fdcaf5a122d')}, + }, + '32': { + 'aarch64': + {'checksum': ('b367755c664a2d7a26955bbfff985855' + 'adfa2ca15e908baf15b4b176d68d3967'), + 'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/' + 'releases/32/Server/aarch64/os/images/' + 'pxeboot/'), + 'kernel_params': ('root=UUID=3df75b65-be8d-4db4-8655-' + '14d95c0e90c5 ro no_timer_check net.ifnames=0' + ' console=tty1 console=ttyS0,115200n8'), + }, + }, + '33': { + 'aarch64': + {'checksum': ('e7f75cdfd523fe5ac2ca9eeece68edc1' + 'a81f386a17f969c1d1c7c87031008a6b'), + 'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/' + 'releases/33/Server/aarch64/os/images/' + 'pxeboot/'), + 'kernel_params': ('root=UUID=d20b3ffa-6397-4a63-a734-' + '1126a0208f8a ro no_timer_check net.ifnames=0' + ' console=tty1 console=ttyS0,115200n8' + ' console=tty0'), + }, + }, + } + } + + def __init__(self, name, version, arch): + self.name = name + self.version = version + self.arch = arch + try: + info = self.KNOWN_DISTROS.get(name).get(version).get(arch) + except AttributeError: + # Unknown distro + info = None + self._info = info or {} + + @property + def checksum(self): + """Gets the cloud-image file checksum""" + return self._info.get('checksum', None) + + @checksum.setter + def checksum(self, value): + self._info['checksum'] = value + + @property + def pxeboot_url(self): + """Gets the repository url where pxeboot files can be found""" + return self._info.get('pxeboot_url', None) + + @property + def default_kernel_params(self): + """Gets the default kernel parameters""" + return self._info.get('kernel_params', None) + + +class LinuxTest(LinuxSSHMixIn, QemuSystemTest): + """Facilitates having a cloud-image Linux based available. + + For tests that intend to interact with guests, this is a better choice + to start with than the more vanilla `QemuSystemTest` class. + """ + + distro = None + username = 'root' + password = 'password' + smp = '2' + memory = '1024' + + def _set_distro(self): + distro_name = self.params.get( + 'distro', + default=self._get_unique_tag_val('distro')) + if not distro_name: + distro_name = 'fedora' + + distro_version = self.params.get( + 'distro_version', + default=self._get_unique_tag_val('distro_version')) + if not distro_version: + distro_version = '31' + + self.distro = LinuxDistro(distro_name, distro_version, self.arch) + + # The distro checksum behaves differently than distro name and + # version. First, it does not respect a tag with the same + # name, given that it's not expected to be used for filtering + # (distro name versions are the natural choice). Second, the + # order of precedence is: parameter, attribute and then value + # from KNOWN_DISTROS. + distro_checksum = self.params.get('distro_checksum', + default=None) + if distro_checksum: + self.distro.checksum = distro_checksum + + def setUp(self, ssh_pubkey=None, network_device_type='virtio-net'): + super().setUp() + self.require_netdev('user') + self._set_distro() + self.vm.add_args('-smp', self.smp) + self.vm.add_args('-m', self.memory) + # The following network device allows for SSH connections + self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22', + '-device', '%s,netdev=vnet' % network_device_type) + self.set_up_boot() + if ssh_pubkey is None: + ssh_pubkey, self.ssh_key = self.set_up_existing_ssh_keys() + self.set_up_cloudinit(ssh_pubkey) + + def set_up_existing_ssh_keys(self): + ssh_public_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa.pub') + source_private_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa') + ssh_dir = os.path.join(self.workdir, '.ssh') + os.mkdir(ssh_dir, mode=0o700) + ssh_private_key = os.path.join(ssh_dir, + os.path.basename(source_private_key)) + shutil.copyfile(source_private_key, ssh_private_key) + os.chmod(ssh_private_key, 0o600) + return (ssh_public_key, ssh_private_key) + + def download_boot(self): + # Set the qemu-img binary. + # If none is available, the test will cancel. + vmimage.QEMU_IMG = super().get_qemu_img() + + self.log.info('Downloading/preparing boot image') + # Fedora 31 only provides ppc64le images + image_arch = self.arch + if self.distro.name == 'fedora': + if image_arch == 'ppc64': + image_arch = 'ppc64le' + + try: + boot = vmimage.get( + self.distro.name, arch=image_arch, version=self.distro.version, + checksum=self.distro.checksum, + algorithm='sha256', + cache_dir=self.cache_dirs[0], + snapshot_dir=self.workdir) + except: + self.cancel('Failed to download/prepare boot image') + return boot.path + + def prepare_cloudinit(self, ssh_pubkey=None): + self.log.info('Preparing cloudinit image') + try: + cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso') + pubkey_content = None + if ssh_pubkey: + with open(ssh_pubkey) as pubkey: + pubkey_content = pubkey.read() + cloudinit.iso(cloudinit_iso, self.name, + username=self.username, + password=self.password, + # QEMU's hard coded usermode router address + phone_home_host='10.0.2.2', + phone_home_port=self.phone_server.server_port, + authorized_key=pubkey_content) + except Exception: + self.cancel('Failed to prepare the cloudinit image') + return cloudinit_iso + + def set_up_boot(self): + path = self.download_boot() + self.vm.add_args('-drive', 'file=%s' % path) + + def set_up_cloudinit(self, ssh_pubkey=None): + self.phone_server = cloudinit.PhoneHomeServer(('0.0.0.0', 0), + self.name) + cloudinit_iso = self.prepare_cloudinit(ssh_pubkey) + self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso) + + def launch_and_wait(self, set_up_ssh_connection=True): + self.vm.set_console() + self.vm.launch() + console_drainer = datadrainer.LineLogger(self.vm.console_socket.fileno(), + logger=self.log.getChild('console')) + console_drainer.start() + self.log.info('VM launched, waiting for boot confirmation from guest') + while not self.phone_server.instance_phoned_back: + self.phone_server.handle_request() + + if set_up_ssh_connection: + self.log.info('Setting up the SSH connection') + self.ssh_connect(self.username, self.ssh_key) diff --git a/tests/avocado/boot_linux.py b/tests/avocado/boot_linux.py new file mode 100644 index 0000000000..cdce4cbcba --- /dev/null +++ b/tests/avocado/boot_linux.py @@ -0,0 +1,131 @@ +# Functional test that boots a complete Linux system via a cloud image +# +# Copyright (c) 2018-2020 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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 + +from avocado_qemu import LinuxTest, BUILD_DIR + +from avocado import skipUnless + + +class BootLinuxX8664(LinuxTest): + """ + :avocado: tags=arch:x86_64 + """ + timeout = 480 + + def test_pc_i440fx_tcg(self): + """ + :avocado: tags=machine:pc + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.add_args("-accel", "tcg") + self.launch_and_wait(set_up_ssh_connection=False) + + def test_pc_i440fx_kvm(self): + """ + :avocado: tags=machine:pc + :avocado: tags=accel:kvm + """ + self.require_accelerator("kvm") + self.vm.add_args("-accel", "kvm") + self.launch_and_wait(set_up_ssh_connection=False) + + def test_pc_q35_tcg(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.add_args("-accel", "tcg") + self.launch_and_wait(set_up_ssh_connection=False) + + def test_pc_q35_kvm(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=accel:kvm + """ + self.require_accelerator("kvm") + self.vm.add_args("-accel", "kvm") + self.launch_and_wait(set_up_ssh_connection=False) + + +# For Aarch64 we only boot KVM tests in CI as booting the current +# Fedora OS in TCG tests is very heavyweight. There are lighter weight +# distros which we use in the machine_aarch64_virt.py tests. +class BootLinuxAarch64(LinuxTest): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + """ + timeout = 720 + + def test_virt_kvm(self): + """ + :avocado: tags=accel:kvm + :avocado: tags=cpu:host + """ + self.require_accelerator("kvm") + self.vm.add_args("-accel", "kvm") + self.vm.add_args("-machine", "virt,gic-version=host") + self.vm.add_args('-bios', + os.path.join(BUILD_DIR, 'pc-bios', + 'edk2-aarch64-code.fd')) + self.vm.add_args('-device', 'virtio-rng-pci,rng=rng0') + self.vm.add_args('-object', 'rng-random,id=rng0,filename=/dev/urandom') + self.launch_and_wait(set_up_ssh_connection=False) + + +# See the tux_baseline.py tests for almost the same coverage in a lot +# less time. +class BootLinuxPPC64(LinuxTest): + """ + :avocado: tags=arch:ppc64 + """ + + timeout = 360 + + @skipUnless(os.getenv('SPEED') == 'slow', 'runtime limited') + def test_pseries_tcg(self): + """ + :avocado: tags=machine:pseries + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.add_args("-accel", "tcg") + self.launch_and_wait(set_up_ssh_connection=False) + + def test_pseries_kvm(self): + """ + :avocado: tags=machine:pseries + :avocado: tags=accel:kvm + """ + self.require_accelerator("kvm") + self.vm.add_args("-accel", "kvm") + self.vm.add_args("-machine", "cap-ccf-assist=off") + self.launch_and_wait(set_up_ssh_connection=False) + +class BootLinuxS390X(LinuxTest): + """ + :avocado: tags=arch:s390x + """ + + timeout = 240 + + @skipUnless(os.getenv('SPEED') == 'slow', 'runtime limited') + def test_s390_ccw_virtio_tcg(self): + """ + :avocado: tags=machine:s390-ccw-virtio + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.add_args("-accel", "tcg") + self.launch_and_wait(set_up_ssh_connection=False) diff --git a/tests/avocado/boot_linux_console.py b/tests/avocado/boot_linux_console.py new file mode 100644 index 0000000000..180ac17326 --- /dev/null +++ b/tests/avocado/boot_linux_console.py @@ -0,0 +1,1547 @@ +# Functional test that boots a Linux kernel and checks the console +# +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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 lzma +import gzip +import shutil + +from avocado import skip +from avocado import skipUnless +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import interrupt_interactive_console_until_pattern +from avocado_qemu import wait_for_console_pattern +from avocado.utils import process +from avocado.utils import archive + +""" +Round up to next power of 2 +""" +def pow2ceil(x): + return 1 if x == 0 else 2**(x - 1).bit_length() + +def file_truncate(path, size): + if size != os.path.getsize(path): + with open(path, 'ab+') as fd: + fd.truncate(size) + +""" +Expand file size to next power of 2 +""" +def image_pow2ceil_expand(path): + size = os.path.getsize(path) + size_aligned = pow2ceil(size) + if size != size_aligned: + with open(path, 'ab+') as fd: + fd.truncate(size_aligned) + +class LinuxKernelTest(QemuSystemTest): + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + + 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 extract_from_deb(self, deb, path): + """ + Extracts a file from a deb package into the test workdir + + :param deb: path to the deb archive + :param path: path within the deb archive of the file to be extracted + :returns: path of the extracted file + """ + cwd = os.getcwd() + os.chdir(self.workdir) + file_path = process.run("ar t %s" % deb).stdout_text.split()[2] + process.run("ar x %s %s" % (deb, file_path)) + archive.extract(file_path, self.workdir) + os.chdir(cwd) + # Return complete path to extracted file. Because callers to + # extract_from_deb() specify 'path' with a leading slash, it is + # necessary to use os.path.relpath() as otherwise os.path.join() + # interprets it as an absolute path and drops the self.workdir part. + return os.path.normpath(os.path.join(self.workdir, + os.path.relpath(path, '/'))) + + def extract_from_rpm(self, rpm, path): + """ + Extracts a file from an RPM package into the test workdir. + + :param rpm: path to the rpm archive + :param path: path within the rpm archive of the file to be extracted + needs to be a relative path (starting with './') because + cpio(1), which is used to extract the file, expects that. + :returns: path of the extracted file + """ + cwd = os.getcwd() + os.chdir(self.workdir) + process.run("rpm2cpio %s | cpio -id %s" % (rpm, path), shell=True) + os.chdir(cwd) + return os.path.normpath(os.path.join(self.workdir, path)) + +class BootLinuxConsole(LinuxKernelTest): + """ + Boots a Linux kernel and checks that the console is operational and the + kernel command line is properly passed from QEMU to the kernel + """ + timeout = 90 + + def test_x86_64_pc(self): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:pc + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/29/Everything/x86_64/os/images/pxeboot' + '/vmlinuz') + kernel_hash = '23bebd2680757891cf7adedb033532163a792495' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_mips_malta(self): + """ + :avocado: tags=arch:mips + :avocado: tags=machine:malta + :avocado: tags=endian:big + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20130217T032700Z/pool/main/l/linux-2.6/' + 'linux-image-2.6.32-5-4kc-malta_2.6.32-48_mips.deb') + deb_hash = 'a8cfc28ad8f45f54811fc6cf74fc43ffcfe0ba04' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-2.6.32-5-4kc-malta') + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_mips64el_malta(self): + """ + This test requires the ar tool to extract "data.tar.gz" from + the Debian package. + + The kernel can be rebuilt using this Debian kernel source [1] and + following the instructions on [2]. + + [1] http://snapshot.debian.org/package/linux-2.6/2.6.32-48/ + #linux-source-2.6.32_2.6.32-48 + [2] https://kernel-team.pages.debian.net/kernel-handbook/ + ch-common-tasks.html#s-common-official + + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20130217T032700Z/pool/main/l/linux-2.6/' + 'linux-image-2.6.32-5-5kc-malta_2.6.32-48_mipsel.deb') + deb_hash = '1aaec92083bf22fda31e0d27fa8d9a388e5fc3d5' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-2.6.32-5-5kc-malta') + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_mips64el_fuloong2e(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:fuloong2e + :avocado: tags=endian:little + """ + deb_url = ('http://archive.debian.org/debian/pool/main/l/linux/' + 'linux-image-3.16.0-6-loongson-2e_3.16.56-1+deb8u1_mipsel.deb') + deb_hash = 'd04d446045deecf7b755ef576551de0c4184dd44' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-3.16.0-6-loongson-2e') + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_mips_malta_cpio(self): + """ + :avocado: tags=arch:mips + :avocado: tags=machine:malta + :avocado: tags=endian:big + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20160601T041800Z/pool/main/l/linux/' + 'linux-image-4.5.0-2-4kc-malta_4.5.5-1_mips.deb') + deb_hash = 'a3c84f3e88b54e06107d65a410d1d1e8e0f340f8' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-4.5.0-2-4kc-malta') + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '8584a59ed9e5eb5ee7ca91f6d74bbb06619205b8/rootfs/' + 'mips/rootfs.cpio.gz') + initrd_hash = 'bf806e17009360a866bf537f6de66590de349a99' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = self.workdir + "rootfs.cpio" + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 console=tty ' + + 'rdinit=/sbin/init noreboot') + self.vm.add_args('-kernel', kernel_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'BogoMIPS') + exec_command_and_wait_for_pattern(self, 'uname -a', + 'Debian') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_mips64el_malta_5KEc_cpio(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:5KEc + """ + kernel_url = ('https://github.com/philmd/qemu-testing-blob/' + 'raw/9ad2df38/mips/malta/mips64el/' + 'vmlinux-3.19.3.mtoman.20150408') + kernel_hash = '00d1d268fb9f7d8beda1de6bebcc46e884d71754' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + initrd_url = ('https://github.com/groeck/linux-build-test/' + 'raw/8584a59e/rootfs/' + 'mipsel64/rootfs.mipsel64r1.cpio.gz') + initrd_hash = '1dbb8a396e916847325284dbe2151167' + initrd_path_gz = self.fetch_asset(initrd_url, algorithm='md5', + asset_hash=initrd_hash) + initrd_path = self.workdir + "rootfs.cpio" + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 console=tty ' + + 'rdinit=/sbin/init noreboot') + self.vm.add_args('-kernel', kernel_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + wait_for_console_pattern(self, 'Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'MIPS 5KE') + exec_command_and_wait_for_pattern(self, 'uname -a', + '3.19.3.mtoman.20150408') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + def do_test_mips_malta32el_nanomips(self, kernel_url, kernel_hash): + kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + kernel_path = self.workdir + "kernel" + with lzma.open(kernel_path_xz, 'rb') as f_in: + with open(kernel_path, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'mem=256m@@0x0 ' + + 'console=ttyS0') + self.vm.add_args('-no-reboot', + '-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_mips_malta32el_nanomips_4k(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:I7200 + """ + kernel_url = ('https://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page4k.xz') + kernel_hash = '477456aafd2a0f1ddc9482727f20fe9575565dd6' + self.do_test_mips_malta32el_nanomips(kernel_url, kernel_hash) + + def test_mips_malta32el_nanomips_16k_up(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:I7200 + """ + kernel_url = ('https://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page16k_up.xz') + kernel_hash = 'e882868f944c71c816e832e2303b7874d044a7bc' + self.do_test_mips_malta32el_nanomips(kernel_url, kernel_hash) + + def test_mips_malta32el_nanomips_64k_dbg(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:I7200 + """ + kernel_url = ('https://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page64k_dbg.xz') + kernel_hash = '18d1c68f2e23429e266ca39ba5349ccd0aeb7180' + self.do_test_mips_malta32el_nanomips(kernel_url, kernel_hash) + + def test_aarch64_xlnx_versal_virt(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:xlnx-versal-virt + :avocado: tags=device:pl011 + :avocado: tags=device:arm_gicv3 + :avocado: tags=accel:tcg + """ + images_url = ('http://ports.ubuntu.com/ubuntu-ports/dists/' + 'bionic-updates/main/installer-arm64/' + '20101020ubuntu543.19/images/') + kernel_url = images_url + 'netboot/ubuntu-installer/arm64/linux' + kernel_hash = 'e167757620640eb26de0972f578741924abb3a82' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + initrd_url = images_url + 'netboot/ubuntu-installer/arm64/initrd.gz' + initrd_hash = 'cab5cb3fcefca8408aa5aae57f24574bfce8bdb9' + initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + + self.vm.set_console() + self.vm.add_args('-m', '2G', + '-accel', 'tcg', + '-kernel', kernel_path, + '-initrd', initrd_path) + self.vm.launch() + self.wait_for_console_pattern('Checked W+X mappings: passed') + + def test_arm_virt(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:virt + :avocado: tags=accel:tcg + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/29/Everything/armhfp/os/images/pxeboot' + '/vmlinuz') + kernel_hash = 'e9826d741b4fb04cadba8d4824d1ed3b7fb8b4d4' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_arm_emcraft_sf2(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:emcraft-sf2 + :avocado: tags=endian:little + :avocado: tags=u-boot + :avocado: tags=accel:tcg + """ + self.require_netdev('user') + + uboot_url = ('https://raw.githubusercontent.com/' + 'Subbaraya-Sundeep/qemu-test-binaries/' + 'fe371d32e50ca682391e1e70ab98c2942aeffb01/u-boot') + uboot_hash = 'cbb8cbab970f594bf6523b9855be209c08374ae2' + uboot_path = self.fetch_asset(uboot_url, asset_hash=uboot_hash) + spi_url = ('https://raw.githubusercontent.com/' + 'Subbaraya-Sundeep/qemu-test-binaries/' + 'fe371d32e50ca682391e1e70ab98c2942aeffb01/spi.bin') + spi_hash = '65523a1835949b6f4553be96dec1b6a38fb05501' + spi_path = self.fetch_asset(spi_url, asset_hash=spi_hash) + + file_truncate(spi_path, 16 << 20) # Spansion S25FL128SDPBHICO is 16 MiB + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + self.vm.add_args('-kernel', uboot_path, + '-append', kernel_command_line, + '-drive', 'file=' + spi_path + ',if=mtd,format=raw', + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Enter \'help\' for a list') + + exec_command_and_wait_for_pattern(self, 'ifconfig eth0 10.0.2.15', + 'eth0: link becomes ready') + exec_command_and_wait_for_pattern(self, 'ping -c 3 10.0.2.2', + '3 packets transmitted, 3 packets received, 0% packet loss') + + def do_test_arm_raspi2(self, uart_id): + """ + :avocado: tags=accel:tcg + + The kernel can be rebuilt using the kernel source referenced + and following the instructions on the on: + https://www.raspberrypi.org/documentation/linux/kernel/building.md + """ + serial_kernel_cmdline = { + 0: 'earlycon=pl011,0x3f201000 console=ttyAMA0', + } + deb_url = ('http://archive.raspberrypi.org/debian/' + 'pool/main/r/raspberrypi-firmware/' + 'raspberrypi-kernel_1.20190215-1_armhf.deb') + deb_hash = 'cd284220b32128c5084037553db3c482426f3972' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, '/boot/kernel7.img') + dtb_path = self.extract_from_deb(deb_path, '/boot/bcm2709-rpi-2-b.dtb') + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + serial_kernel_cmdline[uart_id] + + ' root=/dev/mmcblk0p2 rootwait ' + + 'dwc_otg.fiq_fsm_enable=0') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-append', kernel_command_line, + '-device', 'usb-kbd') + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + console_pattern = 'Product: QEMU USB Keyboard' + self.wait_for_console_pattern(console_pattern) + + def test_arm_raspi2_uart0(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:raspi2b + :avocado: tags=device:pl011 + :avocado: tags=accel:tcg + """ + self.do_test_arm_raspi2(0) + + def test_arm_raspi2_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:raspi2b + """ + deb_url = ('http://archive.raspberrypi.org/debian/' + 'pool/main/r/raspberrypi-firmware/' + 'raspberrypi-kernel_1.20190215-1_armhf.deb') + deb_hash = 'cd284220b32128c5084037553db3c482426f3972' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, '/boot/kernel7.img') + dtb_path = self.extract_from_deb(deb_path, '/boot/bcm2709-rpi-2-b.dtb') + + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv7a.cpio.gz') + initrd_hash = '604b2e45cdf35045846b8bbfbf2129b1891bdc9c' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'earlycon=pl011,0x3f201000 console=ttyAMA0 ' + 'panic=-1 noreboot ' + + 'dwc_otg.fiq_fsm_enable=0') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'BCM2835') + exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', + '/soc/cprman@7e101000') + exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted') + # Wait for VM to shut down gracefully + self.vm.wait() + + def test_arm_raspi4(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:raspi4b + :avocado: tags=device:pl011 + :avocado: tags=accel:tcg + :avocado: tags=rpi4b + + The kernel can be rebuilt using the kernel source referenced + and following the instructions on the on: + https://www.raspberrypi.org/documentation/linux/kernel/building.md + """ + + deb_url = ('http://archive.raspberrypi.org/debian/' + 'pool/main/r/raspberrypi-firmware/' + 'raspberrypi-kernel_1.20230106-1_arm64.deb') + deb_hash = '08dc55696535b18a6d4fe6fa10d4c0d905cbb2ed' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, '/boot/kernel8.img') + dtb_path = self.extract_from_deb(deb_path, '/boot/bcm2711-rpi-4-b.dtb') + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'earlycon=pl011,mmio32,0xfe201000 ' + + 'console=ttyAMA0,115200 ' + + 'root=/dev/mmcblk1p2 rootwait ' + + 'dwc_otg.fiq_fsm_enable=0') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-append', kernel_command_line) + # When PCI is supported we can add a USB controller: + # '-device', 'qemu-xhci,bus=pcie.1,id=xhci', + # '-device', 'usb-kbd,bus=xhci.0', + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + # When USB is enabled we can look for this + # console_pattern = 'Product: QEMU USB Keyboard' + # self.wait_for_console_pattern(console_pattern) + console_pattern = 'Waiting for root device' + self.wait_for_console_pattern(console_pattern) + + + def test_arm_raspi4_initrd(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:raspi4b + :avocado: tags=device:pl011 + :avocado: tags=accel:tcg + :avocado: tags=rpi4b + + The kernel can be rebuilt using the kernel source referenced + and following the instructions on the on: + https://www.raspberrypi.org/documentation/linux/kernel/building.md + """ + deb_url = ('http://archive.raspberrypi.org/debian/' + 'pool/main/r/raspberrypi-firmware/' + 'raspberrypi-kernel_1.20230106-1_arm64.deb') + deb_hash = '08dc55696535b18a6d4fe6fa10d4c0d905cbb2ed' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, '/boot/kernel8.img') + dtb_path = self.extract_from_deb(deb_path, '/boot/bcm2711-rpi-4-b.dtb') + + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '86b2be1384d41c8c388e63078a847f1e1c4cb1de/rootfs/' + 'arm64/rootfs.cpio.gz') + initrd_hash = 'f3d4f9fa92a49aa542f1b44d34be77bbf8ca5b9d' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'earlycon=pl011,mmio32,0xfe201000 ' + + 'console=ttyAMA0,115200 ' + + 'panic=-1 noreboot ' + + 'dwc_otg.fiq_fsm_enable=0') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + # When PCI is supported we can add a USB controller: + # '-device', 'qemu-xhci,bus=pcie.1,id=xhci', + # '-device', 'usb-kbd,bus=xhci.0', + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'BCM2835') + exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', + 'cprman@7e101000') + exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted') + # TODO: Raspberry Pi4 doesn't shut down properly with recent kernels + # Wait for VM to shut down gracefully + #self.vm.wait() + + def test_arm_exynos4210_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:smdkc210 + :avocado: tags=accel:tcg + """ + deb_url = ('https://snapshot.debian.org/archive/debian/' + '20190928T224601Z/pool/main/l/linux/' + 'linux-image-4.19.0-6-armmp_4.19.67-2+deb10u1_armhf.deb') + deb_hash = 'fa9df4a0d38936cb50084838f2cb933f570d7d82' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-4.19.0-6-armmp') + dtb_path = '/usr/lib/linux-image-4.19.0-6-armmp/exynos4210-smdkv310.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv5.cpio.gz') + initrd_hash = '2b50f1873e113523967806f4da2afe385462ff9b' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'earlycon=exynos4210,0x13800000 earlyprintk ' + + 'console=ttySAC0,115200n8 ' + + 'random.trust_cpu=off cryptomgr.notests ' + + 'cpuidle.off=1 panic=-1 noreboot') + + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + + self.wait_for_console_pattern('Boot successful.') + # TODO user command, for now the uart is stuck + + def test_arm_cubieboard_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:cubieboard + :avocado: tags=accel:tcg + """ + deb_url = ('https://apt.armbian.com/pool/main/l/' + 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = '/usr/lib/linux-image-current-sunxi/sun4i-a10-cubieboard.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv5.cpio.gz') + initrd_hash = '2b50f1873e113523967806f4da2afe385462ff9b' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'usbcore.nousb ' + 'panic=-1 noreboot') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun4i/sun5i') + exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', + 'system-control@1c00000') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + def test_arm_cubieboard_sata(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:cubieboard + :avocado: tags=accel:tcg + """ + deb_url = ('https://apt.armbian.com/pool/main/l/' + 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = '/usr/lib/linux-image-current-sunxi/sun4i-a10-cubieboard.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + rootfs_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv5.ext2.gz') + rootfs_hash = '093e89d2b4d982234bf528bc9fb2f2f17a9d1f93' + rootfs_path_gz = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash) + rootfs_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(rootfs_path_gz, rootfs_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'usbcore.nousb ' + 'root=/dev/sda ro ' + 'panic=-1 noreboot') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-drive', 'if=none,format=raw,id=disk0,file=' + + rootfs_path, + '-device', 'ide-hd,bus=ide.0,drive=disk0', + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun4i/sun5i') + exec_command_and_wait_for_pattern(self, 'cat /proc/partitions', + 'sda') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + @skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited') + def test_arm_cubieboard_openwrt_22_03_2(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:cubieboard + :avocado: tags=device:sd + """ + + # This test download a 7.5 MiB compressed image and expand it + # to 126 MiB. + image_url = ('https://downloads.openwrt.org/releases/22.03.2/targets/' + 'sunxi/cortexa8/openwrt-22.03.2-sunxi-cortexa8-' + 'cubietech_a10-cubieboard-ext4-sdcard.img.gz') + image_hash = ('94b5ecbfbc0b3b56276e5146b899eafa' + '2ac5dc2d08733d6705af9f144f39f554') + image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + image_path = archive.extract(image_path_gz, self.workdir) + image_pow2ceil_expand(image_path) + + self.vm.set_console() + self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw', + '-nic', 'user', + '-no-reboot') + self.vm.launch() + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'usbcore.nousb ' + 'noreboot') + + self.wait_for_console_pattern('U-Boot SPL') + + interrupt_interactive_console_until_pattern( + self, 'Hit any key to stop autoboot:', '=>') + exec_command_and_wait_for_pattern(self, "setenv extraargs '" + + kernel_command_line + "'", '=>') + exec_command_and_wait_for_pattern(self, 'boot', 'Starting kernel ...'); + + self.wait_for_console_pattern( + 'Please press Enter to activate this console.') + + exec_command_and_wait_for_pattern(self, ' ', 'root@') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun4i/sun5i') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + @skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') + def test_arm_quanta_gsj(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:quanta-gsj + :avocado: tags=accel:tcg + """ + # 25 MiB compressed, 32 MiB uncompressed. + image_url = ( + 'https://github.com/hskinnemoen/openbmc/releases/download/' + '20200711-gsj-qemu-0/obmc-phosphor-image-gsj.static.mtd.gz') + image_hash = '14895e634923345cb5c8776037ff7876df96f6b1' + image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash) + image_name = 'obmc.mtd' + image_path = os.path.join(self.workdir, image_name) + archive.gzip_uncompress(image_path_gz, image_path) + + self.vm.set_console() + drive_args = 'file=' + image_path + ',if=mtd,bus=0,unit=0' + self.vm.add_args('-drive', drive_args) + self.vm.launch() + + # Disable drivers and services that stall for a long time during boot, + # to avoid running past the 90-second timeout. These may be removed + # as the corresponding device support is added. + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + ( + 'console=${console} ' + 'mem=${mem} ' + 'initcall_blacklist=npcm_i2c_bus_driver_init ' + 'systemd.mask=systemd-random-seed.service ' + 'systemd.mask=dropbearkey.service ' + ) + + self.wait_for_console_pattern('> BootBlock by Nuvoton') + self.wait_for_console_pattern('>Device: Poleg BMC NPCM730') + self.wait_for_console_pattern('>Skip DDR init.') + self.wait_for_console_pattern('U-Boot ') + interrupt_interactive_console_until_pattern( + self, 'Hit any key to stop autoboot:', 'U-Boot>') + exec_command_and_wait_for_pattern( + self, "setenv bootargs ${bootargs} " + kernel_command_line, + 'U-Boot>') + exec_command_and_wait_for_pattern( + self, 'run romboot', 'Booting Kernel from flash') + self.wait_for_console_pattern('Booting Linux on physical CPU 0x0') + self.wait_for_console_pattern('CPU1: thread -1, cpu 1, socket 0') + self.wait_for_console_pattern('OpenBMC Project Reference Distro') + self.wait_for_console_pattern('gsj login:') + + def test_arm_quanta_gsj_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:quanta-gsj + :avocado: tags=accel:tcg + """ + initrd_url = ( + 'https://github.com/hskinnemoen/openbmc/releases/download/' + '20200711-gsj-qemu-0/obmc-phosphor-initramfs-gsj.cpio.xz') + initrd_hash = '98fefe5d7e56727b1eb17d5c00311b1b5c945300' + initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + kernel_url = ( + 'https://github.com/hskinnemoen/openbmc/releases/download/' + '20200711-gsj-qemu-0/uImage-gsj.bin') + kernel_hash = 'fa67b2f141d56d39b3c54305c0e8a899c99eb2c7' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + dtb_url = ( + 'https://github.com/hskinnemoen/openbmc/releases/download/' + '20200711-gsj-qemu-0/nuvoton-npcm730-gsj.dtb') + dtb_hash = '18315f7006d7b688d8312d5c727eecd819aa36a4' + dtb_path = self.fetch_asset(dtb_url, asset_hash=dtb_hash) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200n8 ' + 'earlycon=uart8250,mmio32,0xf0001000') + self.vm.add_args('-kernel', kernel_path, + '-initrd', initrd_path, + '-dtb', dtb_path, + '-append', kernel_command_line) + self.vm.launch() + + self.wait_for_console_pattern('Booting Linux on physical CPU 0x0') + self.wait_for_console_pattern('CPU1: thread -1, cpu 1, socket 0') + self.wait_for_console_pattern( + 'Give root password for system maintenance') + + def test_arm_bpim2u(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:bpim2u + :avocado: tags=accel:tcg + """ + deb_url = ('https://apt.armbian.com/pool/main/l/linux-5.10.16-sunxi/' + 'linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = ('/usr/lib/linux-image-current-sunxi/' + 'sun8i-r40-bananapi-m2-ultra.dtb') + dtb_path = self.extract_from_deb(deb_path, dtb_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200n8 ' + 'earlycon=uart,mmio32,0x1c28000') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_arm_bpim2u_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=accel:tcg + :avocado: tags=machine:bpim2u + """ + deb_url = ('https://apt.armbian.com/pool/main/l/linux-5.10.16-sunxi/' + 'linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = ('/usr/lib/linux-image-current-sunxi/' + 'sun8i-r40-bananapi-m2-ultra.dtb') + dtb_path = self.extract_from_deb(deb_path, dtb_path) + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv7a.cpio.gz') + initrd_hash = '604b2e45cdf35045846b8bbfbf2129b1891bdc9c' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'panic=-1 noreboot') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun8i Family') + exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', + 'system-control@1c00000') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + def test_arm_bpim2u_gmac(self): + """ + :avocado: tags=arch:arm + :avocado: tags=accel:tcg + :avocado: tags=machine:bpim2u + :avocado: tags=device:sd + """ + self.require_netdev('user') + + deb_url = ('https://apt.armbian.com/pool/main/l/linux-5.10.16-sunxi/' + 'linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = ('/usr/lib/linux-image-current-sunxi/' + 'sun8i-r40-bananapi-m2-ultra.dtb') + dtb_path = self.extract_from_deb(deb_path, dtb_path) + rootfs_url = ('http://storage.kernelci.org/images/rootfs/buildroot/' + 'buildroot-baseline/20221116.0/armel/rootfs.ext2.xz') + rootfs_hash = 'fae32f337c7b87547b10f42599acf109da8b6d9a' + rootfs_path_xz = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash) + rootfs_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.lzma_uncompress(rootfs_path_xz, rootfs_path) + image_pow2ceil_expand(rootfs_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'root=b300 rootwait rw ' + 'panic=-1 noreboot') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-drive', 'file=' + rootfs_path + ',if=sd,format=raw', + '-net', 'nic,model=gmac,netdev=host_gmac', + '-netdev', 'user,id=host_gmac', + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + shell_ready = "/bin/sh: can't access tty; job control turned off" + self.wait_for_console_pattern(shell_ready) + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun8i Family') + exec_command_and_wait_for_pattern(self, 'cat /proc/partitions', + 'mmcblk') + exec_command_and_wait_for_pattern(self, 'ifconfig eth0 up', + 'eth0: Link is Up') + exec_command_and_wait_for_pattern(self, 'udhcpc eth0', + 'udhcpc: lease of 10.0.2.15 obtained') + exec_command_and_wait_for_pattern(self, 'ping -c 3 10.0.2.2', + '3 packets transmitted, 3 packets received, 0% packet loss') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + @skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited') + def test_arm_bpim2u_openwrt_22_03_3(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:bpim2u + :avocado: tags=device:sd + """ + + # This test download a 8.9 MiB compressed image and expand it + # to 127 MiB. + image_url = ('https://downloads.openwrt.org/releases/22.03.3/targets/' + 'sunxi/cortexa7/openwrt-22.03.3-sunxi-cortexa7-' + 'sinovoip_bananapi-m2-ultra-ext4-sdcard.img.gz') + image_hash = ('5b41b4e11423e562c6011640f9a7cd3b' + 'dd0a3d42b83430f7caa70a432e6cd82c') + image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + image_path = archive.extract(image_path_gz, self.workdir) + image_pow2ceil_expand(image_path) + + self.vm.set_console() + self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw', + '-nic', 'user', + '-no-reboot') + self.vm.launch() + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'usbcore.nousb ' + 'noreboot') + + self.wait_for_console_pattern('U-Boot SPL') + + interrupt_interactive_console_until_pattern( + self, 'Hit any key to stop autoboot:', '=>') + exec_command_and_wait_for_pattern(self, "setenv extraargs '" + + kernel_command_line + "'", '=>') + exec_command_and_wait_for_pattern(self, 'boot', 'Starting kernel ...'); + + self.wait_for_console_pattern( + 'Please press Enter to activate this console.') + + exec_command_and_wait_for_pattern(self, ' ', 'root@') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun8i Family') + exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', + 'system-control@1c00000') + + def test_arm_orangepi(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:orangepi-pc + :avocado: tags=accel:tcg + """ + deb_url = ('https://apt.armbian.com/pool/main/l/' + 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = '/usr/lib/linux-image-current-sunxi/sun8i-h3-orangepi-pc.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200n8 ' + 'earlycon=uart,mmio32,0x1c28000') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_arm_orangepi_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=accel:tcg + :avocado: tags=machine:orangepi-pc + """ + deb_url = ('https://apt.armbian.com/pool/main/l/' + 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = '/usr/lib/linux-image-current-sunxi/sun8i-h3-orangepi-pc.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv7a.cpio.gz') + initrd_hash = '604b2e45cdf35045846b8bbfbf2129b1891bdc9c' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'panic=-1 noreboot') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-initrd', initrd_path, + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + self.wait_for_console_pattern('Boot successful.') + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun8i Family') + exec_command_and_wait_for_pattern(self, 'cat /proc/iomem', + 'system-control@1c00000') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + def test_arm_orangepi_sd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=accel:tcg + :avocado: tags=machine:orangepi-pc + :avocado: tags=device:sd + """ + self.require_netdev('user') + + deb_url = ('https://apt.armbian.com/pool/main/l/' + 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = '/usr/lib/linux-image-current-sunxi/sun8i-h3-orangepi-pc.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + rootfs_url = ('http://storage.kernelci.org/images/rootfs/buildroot/' + 'buildroot-baseline/20221116.0/armel/rootfs.ext2.xz') + rootfs_hash = 'fae32f337c7b87547b10f42599acf109da8b6d9a' + rootfs_path_xz = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash) + rootfs_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.lzma_uncompress(rootfs_path_xz, rootfs_path) + image_pow2ceil_expand(rootfs_path) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'root=/dev/mmcblk0 rootwait rw ' + 'panic=-1 noreboot') + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-drive', 'file=' + rootfs_path + ',if=sd,format=raw', + '-append', kernel_command_line, + '-no-reboot') + self.vm.launch() + shell_ready = "/bin/sh: can't access tty; job control turned off" + self.wait_for_console_pattern(shell_ready) + + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'Allwinner sun8i Family') + exec_command_and_wait_for_pattern(self, 'cat /proc/partitions', + 'mmcblk0') + exec_command_and_wait_for_pattern(self, 'ifconfig eth0 up', + 'eth0: Link is Up') + exec_command_and_wait_for_pattern(self, 'udhcpc eth0', + 'udhcpc: lease of 10.0.2.15 obtained') + exec_command_and_wait_for_pattern(self, 'ping -c 3 10.0.2.2', + '3 packets transmitted, 3 packets received, 0% packet loss') + exec_command_and_wait_for_pattern(self, 'reboot', + 'reboot: Restarting system') + # Wait for VM to shut down gracefully + self.vm.wait() + + @skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited') + def test_arm_orangepi_bionic_20_08(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:orangepi-pc + :avocado: tags=device:sd + """ + + # This test download a 275 MiB compressed image and expand it + # to 1036 MiB, but the underlying filesystem is 1552 MiB... + # As we expand it to 2 GiB we are safe. + + image_url = ('https://archive.armbian.com/orangepipc/archive/' + 'Armbian_20.08.1_Orangepipc_bionic_current_5.8.5.img.xz') + image_hash = ('b4d6775f5673486329e45a0586bf06b6' + 'dbe792199fd182ac6b9c7bb6c7d3e6dd') + image_path_xz = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + image_path = archive.extract(image_path_xz, self.workdir) + image_pow2ceil_expand(image_path) + + self.vm.set_console() + self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw', + '-nic', 'user', + '-no-reboot') + self.vm.launch() + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'loglevel=7 ' + 'nosmp ' + 'systemd.default_timeout_start_sec=9000 ' + 'systemd.mask=armbian-zram-config.service ' + 'systemd.mask=armbian-ramlog.service') + + self.wait_for_console_pattern('U-Boot SPL') + self.wait_for_console_pattern('Autoboot in ') + exec_command_and_wait_for_pattern(self, ' ', '=>') + exec_command_and_wait_for_pattern(self, "setenv extraargs '" + + kernel_command_line + "'", '=>') + exec_command_and_wait_for_pattern(self, 'boot', 'Starting kernel ...'); + + self.wait_for_console_pattern('systemd[1]: Set hostname ' + + 'to <orangepipc>') + self.wait_for_console_pattern('Starting Load Kernel Modules...') + + @skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited') + def test_arm_orangepi_uboot_netbsd9(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:orangepi-pc + :avocado: tags=device:sd + :avocado: tags=os:netbsd + """ + # This test download a 304MB compressed image and expand it to 2GB + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20200108T145233Z/pool/main/u/u-boot/' + 'u-boot-sunxi_2020.01%2Bdfsg-1_armhf.deb') + deb_hash = 'f67f404a80753ca3d1258f13e38f2b060e13db99' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + # We use the common OrangePi PC 'plus' build of U-Boot for our secondary + # program loader (SPL). We will then set the path to the more specific + # OrangePi "PC" device tree blob with 'setenv fdtfile' in U-Boot prompt, + # before to boot NetBSD. + uboot_path = '/usr/lib/u-boot/orangepi_plus/u-boot-sunxi-with-spl.bin' + uboot_path = self.extract_from_deb(deb_path, uboot_path) + image_url = ('https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.0/' + 'evbarm-earmv7hf/binary/gzimg/armv7.img.gz') + image_hash = '2babb29d36d8360adcb39c09e31060945259917a' + image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash) + image_path = os.path.join(self.workdir, 'armv7.img') + archive.gzip_uncompress(image_path_gz, image_path) + image_pow2ceil_expand(image_path) + image_drive_args = 'if=sd,format=raw,snapshot=on,file=' + image_path + + # dd if=u-boot-sunxi-with-spl.bin of=armv7.img bs=1K seek=8 conv=notrunc + with open(uboot_path, 'rb') as f_in: + with open(image_path, 'r+b') as f_out: + f_out.seek(8 * 1024) + shutil.copyfileobj(f_in, f_out) + + self.vm.set_console() + self.vm.add_args('-nic', 'user', + '-drive', image_drive_args, + '-global', 'allwinner-rtc.base-year=2000', + '-no-reboot') + self.vm.launch() + wait_for_console_pattern(self, 'U-Boot 2020.01+dfsg-1') + interrupt_interactive_console_until_pattern(self, + 'Hit any key to stop autoboot:', + 'switch to partitions #0, OK') + + exec_command_and_wait_for_pattern(self, '', '=>') + cmd = 'setenv bootargs root=ld0a' + exec_command_and_wait_for_pattern(self, cmd, '=>') + cmd = 'setenv kernel netbsd-GENERIC.ub' + exec_command_and_wait_for_pattern(self, cmd, '=>') + cmd = 'setenv fdtfile dtb/sun8i-h3-orangepi-pc.dtb' + exec_command_and_wait_for_pattern(self, cmd, '=>') + cmd = ("setenv bootcmd 'fatload mmc 0:1 ${kernel_addr_r} ${kernel}; " + "fatload mmc 0:1 ${fdt_addr_r} ${fdtfile}; " + "fdt addr ${fdt_addr_r}; " + "bootm ${kernel_addr_r} - ${fdt_addr_r}'") + exec_command_and_wait_for_pattern(self, cmd, '=>') + + exec_command_and_wait_for_pattern(self, 'boot', + 'Booting kernel from Legacy Image') + wait_for_console_pattern(self, 'Starting kernel ...') + wait_for_console_pattern(self, 'NetBSD 9.0 (GENERIC)') + # Wait for user-space + wait_for_console_pattern(self, 'Starting root file system check') + + def test_aarch64_raspi3_atf(self): + """ + :avocado: tags=accel:tcg + :avocado: tags=arch:aarch64 + :avocado: tags=machine:raspi3b + :avocado: tags=cpu:cortex-a53 + :avocado: tags=device:pl011 + :avocado: tags=atf + """ + zip_url = ('https://github.com/pbatard/RPi3/releases/download/' + 'v1.15/RPi3_UEFI_Firmware_v1.15.zip') + zip_hash = '74b3bd0de92683cadb14e008a7575e1d0c3cafb9' + zip_path = self.fetch_asset(zip_url, asset_hash=zip_hash) + + archive.extract(zip_path, self.workdir) + efi_fd = os.path.join(self.workdir, 'RPI_EFI.fd') + + self.vm.set_console(console_index=1) + self.vm.add_args('-nodefaults', + '-device', 'loader,file=%s,force-raw=true' % efi_fd) + self.vm.launch() + self.wait_for_console_pattern('version UEFI Firmware v1.15') + + def test_s390x_s390_ccw_virtio(self): + """ + :avocado: tags=arch:s390x + :avocado: tags=machine:s390-ccw-virtio + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/29/Everything/s390x/os/images' + '/kernel.img') + kernel_hash = 'e8e8439103ef8053418ef062644ffd46a7919313' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=sclp0' + self.vm.add_args('-nodefaults', + '-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_alpha_clipper(self): + """ + :avocado: tags=arch:alpha + :avocado: tags=machine:clipper + """ + kernel_url = ('http://archive.debian.org/debian/dists/lenny/main/' + 'installer-alpha/20090123lenny10/images/cdrom/vmlinuz') + kernel_hash = '3a943149335529e2ed3e74d0d787b85fb5671ba3' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + uncompressed_kernel = archive.uncompress(kernel_path, self.workdir) + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + self.vm.add_args('-nodefaults', + '-kernel', uncompressed_kernel, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + + def test_m68k_q800(self): + """ + :avocado: tags=arch:m68k + :avocado: tags=machine:q800 + """ + deb_url = ('https://snapshot.debian.org/archive/debian-ports' + '/20191021T083923Z/pool-m68k/main' + '/l/linux/kernel-image-5.3.0-1-m68k-di_5.3.7-1_m68k.udeb') + deb_hash = '044954bb9be4160a3ce81f8bc1b5e856b75cccd1' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-5.3.0-1-m68k') + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 vga=off') + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + console_pattern = 'No filesystem could mount root' + self.wait_for_console_pattern(console_pattern) + + def do_test_advcal_2018(self, day, tar_hash, kernel_name, console=0): + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day' + day + '.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console(console_index=console) + self.vm.add_args('-kernel', + self.workdir + '/day' + day + '/' + kernel_name) + self.vm.launch() + self.wait_for_console_pattern('QEMU advent calendar') + + def test_arm_vexpressa9(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:vexpress-a9 + """ + tar_hash = '32b7677ce8b6f1471fb0059865f451169934245b' + self.vm.add_args('-dtb', self.workdir + '/day16/vexpress-v2p-ca9.dtb') + self.do_test_advcal_2018('16', tar_hash, 'winter.zImage') + + def test_arm_ast2600_debian(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:rainier-bmc + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20220606T211338Z/' + 'pool/main/l/linux/' + 'linux-image-5.17.0-2-armmp_5.17.6-1%2Bb1_armhf.deb') + deb_hash = '8acb2b4439faedc2f3ed4bdb2847ad4f6e0491f73debaeb7f660c8abe4dcdc0e' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash, + algorithm='sha256') + kernel_path = self.extract_from_deb(deb_path, '/boot/vmlinuz-5.17.0-2-armmp') + dtb_path = self.extract_from_deb(deb_path, + '/usr/lib/linux-image-5.17.0-2-armmp/aspeed-bmc-ibm-rainier.dtb') + + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-net', 'nic') + self.vm.launch() + self.wait_for_console_pattern("Booting Linux on physical CPU 0xf00") + self.wait_for_console_pattern("SMP: Total of 2 processors activated") + self.wait_for_console_pattern("No filesystem could mount root") + + def test_m68k_mcf5208evb(self): + """ + :avocado: tags=arch:m68k + :avocado: tags=machine:mcf5208evb + """ + tar_hash = 'ac688fd00561a2b6ce1359f9ff6aa2b98c9a570c' + self.do_test_advcal_2018('07', tar_hash, 'sanity-clause.elf') + + def test_or1k_sim(self): + """ + :avocado: tags=arch:or1k + :avocado: tags=machine:or1k-sim + """ + tar_hash = '20334cdaf386108c530ff0badaecc955693027dd' + self.do_test_advcal_2018('20', tar_hash, 'vmlinux') + + def test_ppc64_e500(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:ppce500 + :avocado: tags=cpu:e5500 + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + tar_hash = '6951d86d644b302898da2fd701739c9406527fe1' + self.do_test_advcal_2018('19', tar_hash, 'uImage') + + def do_test_ppc64_powernv(self, proc): + self.require_accelerator("tcg") + images_url = ('https://github.com/open-power/op-build/releases/download/v2.7/') + + kernel_url = images_url + 'zImage.epapr' + kernel_hash = '0ab237df661727e5392cee97460e8674057a883c5f74381a128fa772588d45cd' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash, + algorithm='sha256') + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path, + '-append', 'console=tty0 console=hvc0', + '-device', 'pcie-pci-bridge,id=bridge1,bus=pcie.1,addr=0x0', + '-device', 'nvme,bus=pcie.2,addr=0x0,serial=1234', + '-device', 'e1000e,bus=bridge1,addr=0x3', + '-device', 'nec-usb-xhci,bus=bridge1,addr=0x2') + self.vm.launch() + + self.wait_for_console_pattern("CPU: " + proc + " generation processor") + self.wait_for_console_pattern("zImage starting: loaded") + self.wait_for_console_pattern("Run /init as init process") + # Device detection output driven by udev probing is sometimes cut off + # from console output, suspect S14silence-console init script. + + def test_ppc_powernv8(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv8 + :avocado: tags=accel:tcg + """ + self.do_test_ppc64_powernv('P8') + + def test_ppc_powernv9(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv9 + :avocado: tags=accel:tcg + """ + self.do_test_ppc64_powernv('P9') + + def test_ppc_powernv10(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv10 + :avocado: tags=accel:tcg + """ + self.do_test_ppc64_powernv('P10') + + def test_ppc_g3beige(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:g3beige + :avocado: tags=accel:tcg + """ + # TODO: g3beige works with kvm_pr but we don't have a + # reliable way ATM (e.g. looking at /proc/modules) to detect + # whether we're running kvm_hv or kvm_pr. For now let's + # disable this test if we don't have TCG support. + self.require_accelerator("tcg") + tar_hash = 'e0b872a5eb8fdc5bed19bd43ffe863900ebcedfc' + self.vm.add_args('-M', 'graphics=off') + self.do_test_advcal_2018('15', tar_hash, 'invaders.elf') + + def test_ppc_mac99(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:mac99 + :avocado: tags=accel:tcg + """ + # TODO: mac99 works with kvm_pr but we don't have a + # reliable way ATM (e.g. looking at /proc/modules) to detect + # whether we're running kvm_hv or kvm_pr. For now let's + # disable this test if we don't have TCG support. + self.require_accelerator("tcg") + tar_hash = 'e0b872a5eb8fdc5bed19bd43ffe863900ebcedfc' + self.vm.add_args('-M', 'graphics=off') + self.do_test_advcal_2018('15', tar_hash, 'invaders.elf') + + # This test has a 6-10% failure rate on various hosts that look + # like issues with a buggy kernel. As a result we don't want it + # gating releases on Gitlab. + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_sh4_r2d(self): + """ + :avocado: tags=arch:sh4 + :avocado: tags=machine:r2d + :avocado: tags=flaky + """ + tar_hash = 'fe06a4fd8ccbf2e27928d64472939d47829d4c7e' + self.vm.add_args('-append', 'console=ttySC1') + self.do_test_advcal_2018('09', tar_hash, 'zImage', console=1) + + def test_sparc_ss20(self): + """ + :avocado: tags=arch:sparc + :avocado: tags=machine:SS-20 + """ + tar_hash = 'b18550d5d61c7615d989a06edace051017726a9f' + self.do_test_advcal_2018('11', tar_hash, 'zImage.elf') + + def test_xtensa_lx60(self): + """ + :avocado: tags=arch:xtensa + :avocado: tags=machine:lx60 + :avocado: tags=cpu:dc233c + """ + tar_hash = '49e88d9933742f0164b60839886c9739cb7a0d34' + self.do_test_advcal_2018('02', tar_hash, 'santas-sleigh-ride.elf') diff --git a/tests/avocado/boot_xen.py b/tests/avocado/boot_xen.py new file mode 100644 index 0000000000..fc2faeedb5 --- /dev/null +++ b/tests/avocado/boot_xen.py @@ -0,0 +1,116 @@ +# Functional test that boots a Xen hypervisor with a domU kernel and +# checks the console output is vaguely sane . +# +# Copyright (c) 2020 Linaro +# +# Author: +# Alex Bennée <alex.bennee@linaro.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# 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 + +from avocado_qemu import wait_for_console_pattern +from boot_linux_console import LinuxKernelTest + + +class BootXenBase(LinuxKernelTest): + """ + Boots a Xen hypervisor with a Linux DomU kernel. + """ + + timeout = 90 + XEN_COMMON_COMMAND_LINE = 'dom0_mem=128M loglvl=all guest_loglvl=all' + + def fetch_guest_kernel(self): + # Using my own built kernel - which works + kernel_url = ('https://fileserver.linaro.org/' + 's/JSsewXGZ6mqxPr5/download?path=%2F&files=' + 'linux-5.9.9-arm64-ajb') + kernel_sha1 = '4f92bc4b9f88d5ab792fa7a43a68555d344e1b83' + kernel_path = self.fetch_asset(kernel_url, + asset_hash=kernel_sha1) + + return kernel_path + + def launch_xen(self, xen_path): + """ + Launch Xen with a dom0 guest kernel + """ + self.log.info("launch with xen_path: %s", xen_path) + kernel_path = self.fetch_guest_kernel() + + self.vm.set_console() + + xen_command_line = self.XEN_COMMON_COMMAND_LINE + self.vm.add_args('-machine', 'virtualization=on', + '-m', '768', + '-kernel', xen_path, + '-append', xen_command_line, + '-device', + 'guest-loader,addr=0x47000000,kernel=%s,bootargs=console=hvc0' + % (kernel_path)) + + self.vm.launch() + + console_pattern = 'VFS: Cannot open root device' + wait_for_console_pattern(self, console_pattern, "Panic on CPU 0:") + + +class BootXen(BootXenBase): + + def test_arm64_xen_411_and_dom0(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=accel:tcg + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + """ + + # archive of file from https://deb.debian.org/debian/pool/main/x/xen/ + xen_url = ('https://fileserver.linaro.org/s/JSsewXGZ6mqxPr5/' + 'download?path=%2F&files=' + 'xen-hypervisor-4.11-arm64_4.11.4%2B37-g3263f257ca-1_arm64.deb') + xen_sha1 = '034e634d4416adbad1212d59b62bccdcda63e62a' + xen_deb = self.fetch_asset(xen_url, asset_hash=xen_sha1) + xen_path = self.extract_from_deb(xen_deb, "/boot/xen-4.11-arm64") + + self.launch_xen(xen_path) + + def test_arm64_xen_414_and_dom0(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=accel:tcg + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + """ + + # archive of file from https://deb.debian.org/debian/pool/main/x/xen/ + xen_url = ('https://fileserver.linaro.org/s/JSsewXGZ6mqxPr5/' + 'download?path=%2F&files=' + 'xen-hypervisor-4.14-arm64_4.14.0%2B80-gd101b417b7-1_arm64.deb') + xen_sha1 = 'b9d209dd689ed2b393e625303a225badefec1160' + xen_deb = self.fetch_asset(xen_url, asset_hash=xen_sha1) + xen_path = self.extract_from_deb(xen_deb, "/boot/xen-4.14-arm64") + + self.launch_xen(xen_path) + + def test_arm64_xen_415_and_dom0(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=accel:tcg + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + """ + + xen_url = ('https://fileserver.linaro.org/' + 's/JSsewXGZ6mqxPr5/download' + '?path=%2F&files=xen-upstream-4.15-unstable.deb') + xen_sha1 = 'fc191172b85cf355abb95d275a24cc0f6d6579d8' + xen_deb = self.fetch_asset(xen_url, asset_hash=xen_sha1) + xen_path = self.extract_from_deb(xen_deb, "/boot/xen-4.15-unstable") + + self.launch_xen(xen_path) diff --git a/tests/avocado/cpu_queries.py b/tests/avocado/cpu_queries.py new file mode 100644 index 0000000000..d3faa14720 --- /dev/null +++ b/tests/avocado/cpu_queries.py @@ -0,0 +1,35 @@ +# Sanity check of query-cpu-* results +# +# Copyright (c) 2019 Red Hat, Inc. +# +# Author: +# Eduardo Habkost <ehabkost@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. + +from avocado_qemu import QemuSystemTest + +class QueryCPUModelExpansion(QemuSystemTest): + """ + Run query-cpu-model-expansion for each CPU model, and validate results + """ + + def test(self): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:none + """ + self.vm.add_args('-S') + self.vm.launch() + + cpus = self.vm.cmd('query-cpu-definitions') + for c in cpus: + self.log.info("Checking CPU: %s", c) + self.assertNotIn('', c['unavailable-features'], c['name']) + + for c in cpus: + model = {'name': c['name']} + e = self.vm.cmd('query-cpu-model-expansion', model=model, + type='full') + self.assertEqual(e['model']['name'], c['name']) diff --git a/tests/avocado/empty_cpu_model.py b/tests/avocado/empty_cpu_model.py new file mode 100644 index 0000000000..d906ef3d3c --- /dev/null +++ b/tests/avocado/empty_cpu_model.py @@ -0,0 +1,19 @@ +# Check for crash when using empty -cpu option +# +# Copyright (c) 2019 Red Hat, Inc. +# +# Author: +# Eduardo Habkost <ehabkost@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. +from avocado_qemu import QemuSystemTest + +class EmptyCPUModel(QemuSystemTest): + def test(self): + self.vm.add_args('-S', '-display', 'none', '-machine', 'none', '-cpu', '') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'-cpu option cannot be empty') diff --git a/tests/avocado/hotplug_cpu.py b/tests/avocado/hotplug_cpu.py new file mode 100644 index 0000000000..292bb43e4d --- /dev/null +++ b/tests/avocado/hotplug_cpu.py @@ -0,0 +1,37 @@ +# Functional test that hotplugs a CPU and checks it on a Linux guest +# +# Copyright (c) 2021 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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. + +from avocado_qemu import LinuxTest + + +class HotPlugCPU(LinuxTest): + + def test(self): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:q35 + :avocado: tags=accel:kvm + """ + self.require_accelerator('kvm') + self.vm.add_args('-accel', 'kvm') + self.vm.add_args('-cpu', 'Haswell') + self.vm.add_args('-smp', '1,sockets=1,cores=2,threads=1,maxcpus=2') + self.launch_and_wait() + + self.ssh_command('test -e /sys/devices/system/cpu/cpu0') + with self.assertRaises(AssertionError): + self.ssh_command('test -e /sys/devices/system/cpu/cpu1') + + self.vm.cmd('device_add', + driver='Haswell-x86_64-cpu', + socket_id=0, + core_id=1, + thread_id=0) + self.ssh_command('test -e /sys/devices/system/cpu/cpu1') diff --git a/tests/avocado/info_usernet.py b/tests/avocado/info_usernet.py new file mode 100644 index 0000000000..e1aa7a6e0a --- /dev/null +++ b/tests/avocado/info_usernet.py @@ -0,0 +1,33 @@ +# Test for the hmp command "info usernet" +# +# Copyright (c) 2021 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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. + +from avocado_qemu import QemuSystemTest + +from qemu.utils import get_info_usernet_hostfwd_port + + +class InfoUsernet(QemuSystemTest): + """ + :avocado: tags=machine:none + """ + + def test_hostfwd(self): + self.require_netdev('user') + self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22') + self.vm.launch() + res = self.vm.cmd('human-monitor-command', + command_line='info usernet') + port = get_info_usernet_hostfwd_port(res) + self.assertIsNotNone(port, + ('"info usernet" output content does not seem to ' + 'contain the redirected port')) + self.assertGreater(port, 0, + ('Found a redirected port that is not greater than' + ' zero')) diff --git a/tests/avocado/intel_iommu.py b/tests/avocado/intel_iommu.py new file mode 100644 index 0000000000..f04ee1cf9d --- /dev/null +++ b/tests/avocado/intel_iommu.py @@ -0,0 +1,123 @@ +# INTEL_IOMMU Functional tests +# +# Copyright (c) 2021 Red Hat, Inc. +# +# Author: +# Eric Auger <eric.auger@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 + +from avocado import skipUnless +from avocado_qemu import LinuxTest + +@skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + +class IntelIOMMU(LinuxTest): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=distro:fedora + :avocado: tags=distro_version:31 + :avocado: tags=machine:q35 + :avocado: tags=accel:kvm + :avocado: tags=intel_iommu + :avocado: tags=flaky + """ + + IOMMU_ADDON = ',iommu_platform=on,disable-modern=off,disable-legacy=on' + kernel_path = None + initrd_path = None + kernel_params = None + + def set_up_boot(self): + path = self.download_boot() + self.vm.add_args('-device', 'virtio-blk-pci,bus=pcie.0,scsi=off,' + + 'drive=drv0,id=virtio-disk0,bootindex=1,' + 'werror=stop,rerror=stop' + self.IOMMU_ADDON) + self.vm.add_args('-device', 'virtio-gpu-pci' + self.IOMMU_ADDON) + self.vm.add_args('-drive', + 'file=%s,if=none,cache=writethrough,id=drv0' % path) + + def setUp(self): + super(IntelIOMMU, self).setUp(None, 'virtio-net-pci' + self.IOMMU_ADDON) + + def add_common_args(self): + self.vm.add_args('-device', 'virtio-rng-pci,rng=rng0') + self.vm.add_args('-object', + 'rng-random,id=rng0,filename=/dev/urandom') + + def common_vm_setup(self, custom_kernel=None): + self.require_accelerator("kvm") + self.add_common_args() + self.vm.add_args("-accel", "kvm") + + if custom_kernel is None: + return + + kernel_url = self.distro.pxeboot_url + 'vmlinuz' + kernel_hash = '5b6f6876e1b5bda314f93893271da0d5777b1f3c' + initrd_url = self.distro.pxeboot_url + 'initrd.img' + initrd_hash = 'dd0340a1b39bd28f88532babd4581c67649ec5b1' + self.kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + self.initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + + def run_and_check(self): + if self.kernel_path: + self.vm.add_args('-kernel', self.kernel_path, + '-append', self.kernel_params, + '-initrd', self.initrd_path) + self.launch_and_wait() + self.ssh_command('cat /proc/cmdline') + self.ssh_command('dmesg | grep -e DMAR -e IOMMU') + self.ssh_command('find /sys/kernel/iommu_groups/ -type l') + self.ssh_command('dnf -y install numactl-devel') + + def test_intel_iommu(self): + """ + :avocado: tags=intel_iommu_intremap + """ + + self.common_vm_setup(True) + self.vm.add_args('-device', 'intel-iommu,intremap=on') + self.vm.add_args('-machine', 'kernel_irqchip=split') + + self.kernel_params = (self.distro.default_kernel_params + + ' quiet intel_iommu=on') + self.run_and_check() + + def test_intel_iommu_strict(self): + """ + :avocado: tags=intel_iommu_strict + """ + + self.common_vm_setup(True) + self.vm.add_args('-device', 'intel-iommu,intremap=on') + self.vm.add_args('-machine', 'kernel_irqchip=split') + self.kernel_params = (self.distro.default_kernel_params + + ' quiet intel_iommu=on,strict') + self.run_and_check() + + def test_intel_iommu_strict_cm(self): + """ + :avocado: tags=intel_iommu_strict_cm + """ + + self.common_vm_setup(True) + self.vm.add_args('-device', 'intel-iommu,intremap=on,caching-mode=on') + self.vm.add_args('-machine', 'kernel_irqchip=split') + self.kernel_params = (self.distro.default_kernel_params + + ' quiet intel_iommu=on,strict') + self.run_and_check() + + def test_intel_iommu_pt(self): + """ + :avocado: tags=intel_iommu_pt + """ + + self.common_vm_setup(True) + self.vm.add_args('-device', 'intel-iommu,intremap=on') + self.vm.add_args('-machine', 'kernel_irqchip=split') + self.kernel_params = (self.distro.default_kernel_params + + ' quiet intel_iommu=on iommu=pt') + self.run_and_check() diff --git a/tests/avocado/kvm_xen_guest.py b/tests/avocado/kvm_xen_guest.py new file mode 100644 index 0000000000..f8cb458d5d --- /dev/null +++ b/tests/avocado/kvm_xen_guest.py @@ -0,0 +1,171 @@ +# KVM Xen guest functional tests +# +# Copyright © 2021 Red Hat, Inc. +# Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Author: +# David Woodhouse <dwmw2@infradead.org> +# Alex Bennée <alex.bennee@linaro.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os + +from qemu.machine import machine + +from avocado_qemu import LinuxSSHMixIn +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class KVMXenGuest(QemuSystemTest, LinuxSSHMixIn): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:q35 + :avocado: tags=accel:kvm + :avocado: tags=kvm_xen_guest + """ + + KERNEL_DEFAULT = 'printk.time=0 root=/dev/xvda console=ttyS0' + + kernel_path = None + kernel_params = None + + # Fetch assets from the kvm-xen-guest subdir of my shared test + # images directory on fileserver.linaro.org where you can find + # build instructions for how they where assembled. + def get_asset(self, name, sha1): + base_url = ('https://fileserver.linaro.org/s/' + 'kE4nCFLdQcoBF9t/download?' + 'path=%2Fkvm-xen-guest&files=' ) + url = base_url + name + # use explicit name rather than failing to neatly parse the + # URL into a unique one + return self.fetch_asset(name=name, locations=(url), asset_hash=sha1) + + def common_vm_setup(self): + # We also catch lack of KVM_XEN support if we fail to launch + self.require_accelerator("kvm") + + self.vm.set_console() + + self.vm.add_args("-accel", "kvm,xen-version=0x4000a,kernel-irqchip=split") + self.vm.add_args("-smp", "2") + + self.kernel_path = self.get_asset("bzImage", + "367962983d0d32109998a70b45dcee4672d0b045") + self.rootfs = self.get_asset("rootfs.ext4", + "f1478401ea4b3fa2ea196396be44315bab2bb5e4") + + def run_and_check(self): + self.vm.add_args('-kernel', self.kernel_path, + '-append', self.kernel_params, + '-drive', f"file={self.rootfs},if=none,snapshot=on,format=raw,id=drv0", + '-device', 'xen-disk,drive=drv0,vdev=xvda', + '-device', 'virtio-net-pci,netdev=unet', + '-netdev', 'user,id=unet,hostfwd=:127.0.0.1:0-:22') + + try: + self.vm.launch() + except machine.VMLaunchFailure as e: + if "Xen HVM guest support not present" in e.output: + self.cancel("KVM Xen support is not present " + "(need v5.12+ kernel with CONFIG_KVM_XEN)") + elif "Property 'kvm-accel.xen-version' not found" in e.output: + self.cancel("QEMU not built with CONFIG_XEN_EMU support") + else: + raise e + + self.log.info('VM launched, waiting for sshd') + console_pattern = 'Starting dropbear sshd: OK' + wait_for_console_pattern(self, console_pattern, 'Oops') + self.log.info('sshd ready') + self.ssh_connect('root', '', False) + + self.ssh_command('cat /proc/cmdline') + self.ssh_command('dmesg | grep -e "Grant table initialized"') + + def test_kvm_xen_guest(self): + """ + :avocado: tags=kvm_xen_guest + """ + + self.common_vm_setup() + + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks') + self.run_and_check() + self.ssh_command('grep xen-pirq.*msi /proc/interrupts') + + def test_kvm_xen_guest_nomsi(self): + """ + :avocado: tags=kvm_xen_guest_nomsi + """ + + self.common_vm_setup() + + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks pci=nomsi') + self.run_and_check() + self.ssh_command('grep xen-pirq.* /proc/interrupts') + + def test_kvm_xen_guest_noapic_nomsi(self): + """ + :avocado: tags=kvm_xen_guest_noapic_nomsi + """ + + self.common_vm_setup() + + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks noapic pci=nomsi') + self.run_and_check() + self.ssh_command('grep xen-pirq /proc/interrupts') + + def test_kvm_xen_guest_vapic(self): + """ + :avocado: tags=kvm_xen_guest_vapic + """ + + self.common_vm_setup() + self.vm.add_args('-cpu', 'host,+xen-vapic') + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks') + self.run_and_check() + self.ssh_command('grep xen-pirq /proc/interrupts') + self.ssh_command('grep PCI-MSI /proc/interrupts') + + def test_kvm_xen_guest_novector(self): + """ + :avocado: tags=kvm_xen_guest_novector + """ + + self.common_vm_setup() + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks' + + ' xen_no_vector_callback') + self.run_and_check() + self.ssh_command('grep xen-platform-pci /proc/interrupts') + + def test_kvm_xen_guest_novector_nomsi(self): + """ + :avocado: tags=kvm_xen_guest_novector_nomsi + """ + + self.common_vm_setup() + + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks pci=nomsi' + + ' xen_no_vector_callback') + self.run_and_check() + self.ssh_command('grep xen-platform-pci /proc/interrupts') + + def test_kvm_xen_guest_novector_noapic(self): + """ + :avocado: tags=kvm_xen_guest_novector_noapic + """ + + self.common_vm_setup() + self.kernel_params = (self.KERNEL_DEFAULT + + ' xen_emul_unplug=ide-disks' + + ' xen_no_vector_callback noapic') + self.run_and_check() + self.ssh_command('grep xen-platform-pci /proc/interrupts') diff --git a/tests/avocado/linux_initrd.py b/tests/avocado/linux_initrd.py new file mode 100644 index 0000000000..aad5b19bd9 --- /dev/null +++ b/tests/avocado/linux_initrd.py @@ -0,0 +1,92 @@ +# Linux initrd integration test. +# +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Wainer dos Santos Moschetta <wainersm@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 logging +import tempfile + +from avocado_qemu import QemuSystemTest +from avocado import skipUnless + + +class LinuxInitrd(QemuSystemTest): + """ + Checks QEMU evaluates correctly the initrd file passed as -initrd option. + + :avocado: tags=arch:x86_64 + :avocado: tags=machine:pc + """ + + timeout = 300 + + def test_with_2gib_file_should_exit_error_msg_with_linux_v3_6(self): + """ + Pretends to boot QEMU with an initrd file with size of 2GiB + and expect it exits with error message. + Fedora-18 shipped with linux-3.6 which have not supported xloadflags + cannot support more than 2GiB initrd. + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora/li' + 'nux/releases/18/Fedora/x86_64/os/images/pxeboot/vmlinuz') + kernel_hash = '41464f68efe42b9991250bed86c7081d2ccdbb21' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + max_size = 2 * (1024 ** 3) - 1 + + with tempfile.NamedTemporaryFile() as initrd: + initrd.seek(max_size) + initrd.write(b'\0') + initrd.flush() + self.vm.add_args('-kernel', kernel_path, '-initrd', initrd.name, + '-m', '4096') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1) + expected_msg = r'.*initrd is too large.*max: \d+, need %s.*' % ( + max_size + 1) + self.assertRegex(self.vm.get_log(), expected_msg) + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_with_2gib_file_should_work_with_linux_v4_16(self): + """ + :avocado: tags=flaky + + QEMU has supported up to 4 GiB initrd for recent kernel + Expect guest can reach 'Unpacking initramfs...' + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/28/Everything/x86_64/os/images/pxeboot/' + 'vmlinuz') + kernel_hash = '238e083e114c48200f80d889f7e32eeb2793e02a' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + max_size = 2 * (1024 ** 3) + 1 + + with tempfile.NamedTemporaryFile() as initrd: + initrd.seek(max_size) + initrd.write(b'\0') + initrd.flush() + + self.vm.set_console() + kernel_command_line = 'console=ttyS0' + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line, + '-initrd', initrd.name, + '-m', '5120') + self.vm.launch() + console = self.vm.console_socket.makefile() + console_logger = logging.getLogger('console') + while True: + msg = console.readline() + console_logger.debug(msg.strip()) + if 'Unpacking initramfs...' in msg: + break + if 'Kernel panic - not syncing' in msg: + self.fail("Kernel panic reached") diff --git a/tests/avocado/linux_ssh_mips_malta.py b/tests/avocado/linux_ssh_mips_malta.py new file mode 100644 index 0000000000..d9bb525ad9 --- /dev/null +++ b/tests/avocado/linux_ssh_mips_malta.py @@ -0,0 +1,205 @@ +# Functional test that boots a VM and run commands via a SSH session +# +# Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# 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 re +import base64 +import logging +import time + +from avocado import skipUnless +from avocado_qemu import LinuxSSHMixIn +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado.utils import process +from avocado.utils import archive +from avocado.utils import ssh + + +@skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') +@skipUnless(ssh.SSH_CLIENT_BINARY, 'No SSH client available') +class LinuxSSH(QemuSystemTest, LinuxSSHMixIn): + """ + :avocado: tags=accel:tcg + """ + + timeout = 150 # Not for 'configure --enable-debug --enable-debug-tcg' + + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + VM_IP = '127.0.0.1' + + BASE_URL = 'https://people.debian.org/~aurel32/qemu/' + IMAGE_INFO = { + 'be': {'base_url': 'mips', + 'image_name': 'debian_wheezy_mips_standard.qcow2', + 'image_hash': '8987a63270df67345b2135a6b7a4885a35e392d5', + 'kernel_hash': { + 32: '592e384a4edc16dade52a6cd5c785c637bcbc9ad', + 64: 'db6eea7de35d36c77d8c165b6bcb222e16eb91db'} + }, + 'le': {'base_url': 'mipsel', + 'image_name': 'debian_wheezy_mipsel_standard.qcow2', + 'image_hash': '7866764d9de3ef536ffca24c9fb9f04ffdb45802', + 'kernel_hash': { + 32: 'a66bea5a8adaa2cb3d36a1d4e0ccdb01be8f6c2a', + 64: '6a7f77245acf231415a0e8b725d91ed2f3487794'} + } + } + CPU_INFO = { + 32: {'cpu': 'MIPS 24Kc', 'kernel_release': '3.2.0-4-4kc-malta'}, + 64: {'cpu': 'MIPS 20Kc', 'kernel_release': '3.2.0-4-5kc-malta'} + } + + def get_url(self, endianess, path=''): + qkey = {'le': 'el', 'be': ''} + return '%s/mips%s/%s' % (self.BASE_URL, qkey[endianess], path) + + def get_image_info(self, endianess): + dinfo = self.IMAGE_INFO[endianess] + image_url = self.get_url(endianess, dinfo['image_name']) + image_hash = dinfo['image_hash'] + return (image_url, image_hash) + + def get_kernel_info(self, endianess, wordsize): + minfo = self.CPU_INFO[wordsize] + kernel_url = self.get_url(endianess, + 'vmlinux-%s' % minfo['kernel_release']) + kernel_hash = self.IMAGE_INFO[endianess]['kernel_hash'][wordsize] + return kernel_url, kernel_hash + + def ssh_disconnect_vm(self): + self.ssh_session.quit() + + def boot_debian_wheezy_image_and_ssh_login(self, endianess, kernel_path): + image_url, image_hash = self.get_image_info(endianess) + image_path = self.fetch_asset(image_url, asset_hash=image_hash) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 root=/dev/sda1') + self.vm.add_args('-no-reboot', + '-kernel', kernel_path, + '-append', kernel_command_line, + '-drive', 'file=%s,snapshot=on' % image_path, + '-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22', + '-device', 'pcnet,netdev=vnet') + self.vm.launch() + + self.log.info('VM launched, waiting for sshd') + console_pattern = 'Starting OpenBSD Secure Shell server: sshd' + wait_for_console_pattern(self, console_pattern, 'Oops') + self.log.info('sshd ready') + + self.ssh_connect('root', 'root', False) + + def shutdown_via_ssh(self): + self.ssh_command('poweroff') + self.ssh_disconnect_vm() + wait_for_console_pattern(self, 'Power down', 'Oops') + + def run_common_commands(self, wordsize): + self.ssh_command_output_contains( + 'cat /proc/cpuinfo', + self.CPU_INFO[wordsize]['cpu']) + self.ssh_command_output_contains( + 'uname -m', + 'mips') + self.ssh_command_output_contains( + 'uname -r', + self.CPU_INFO[wordsize]['kernel_release']) + self.ssh_command_output_contains( + 'cat /proc/interrupts', + 'XT-PIC timer') + self.ssh_command_output_contains( + 'cat /proc/interrupts', + 'XT-PIC i8042') + self.ssh_command_output_contains( + 'cat /proc/interrupts', + 'XT-PIC serial') + self.ssh_command_output_contains( + 'cat /proc/interrupts', + 'XT-PIC ata_piix') + self.ssh_command_output_contains( + 'cat /proc/interrupts', + 'XT-PIC eth0') + self.ssh_command_output_contains( + 'cat /proc/devices', + 'input') + self.ssh_command_output_contains( + 'cat /proc/devices', + 'usb') + self.ssh_command_output_contains( + 'cat /proc/devices', + 'fb') + self.ssh_command_output_contains( + 'cat /proc/ioports', + ' : serial') + self.ssh_command_output_contains( + 'cat /proc/ioports', + ' : ata_piix') + self.ssh_command_output_contains( + 'cat /proc/ioports', + ' : piix4_smbus') + self.ssh_command_output_contains( + 'lspci -d 11ab:4620', + 'GT-64120') + self.ssh_command_output_contains( + 'cat /sys/bus/i2c/devices/i2c-0/name', + 'SMBus PIIX4 adapter') + self.ssh_command_output_contains( + 'cat /proc/mtd', + 'YAMON') + # Empty 'Board Config' (64KB) + self.ssh_command_output_contains( + 'md5sum /dev/mtd2ro', + '0dfbe8aa4c20b52e1b8bf3cb6cbdf193') + + def check_mips_malta(self, uname_m, endianess): + wordsize = 64 if '64' in uname_m else 32 + kernel_url, kernel_hash = self.get_kernel_info(endianess, wordsize) + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + self.boot_debian_wheezy_image_and_ssh_login(endianess, kernel_path) + + stdout, _ = self.ssh_command('uname -a') + self.assertIn(True, [uname_m + " GNU/Linux" in line for line in stdout]) + + self.run_common_commands(wordsize) + self.shutdown_via_ssh() + # Wait for VM to shut down gracefully + self.vm.wait() + + def test_mips_malta32eb_kernel3_2_0(self): + """ + :avocado: tags=arch:mips + :avocado: tags=endian:big + :avocado: tags=device:pcnet32 + """ + self.check_mips_malta('mips', 'be') + + def test_mips_malta32el_kernel3_2_0(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=endian:little + :avocado: tags=device:pcnet32 + """ + self.check_mips_malta('mips', 'le') + + def test_mips_malta64eb_kernel3_2_0(self): + """ + :avocado: tags=arch:mips64 + :avocado: tags=endian:big + :avocado: tags=device:pcnet32 + """ + self.check_mips_malta('mips64', 'be') + + def test_mips_malta64el_kernel3_2_0(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=endian:little + :avocado: tags=device:pcnet32 + """ + self.check_mips_malta('mips64', 'le') diff --git a/tests/avocado/load_bflt.py b/tests/avocado/load_bflt.py new file mode 100644 index 0000000000..bb50cec1ee --- /dev/null +++ b/tests/avocado/load_bflt.py @@ -0,0 +1,54 @@ +# Test the bFLT loader format +# +# Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import bz2 +import subprocess + +from avocado import skipUnless +from avocado_qemu import QemuUserTest +from avocado_qemu import has_cmd + + +class LoadBFLT(QemuUserTest): + + def extract_cpio(self, cpio_path): + """ + Extracts a cpio archive into the test workdir + + :param cpio_path: path to the cpio archive + """ + cwd = os.getcwd() + os.chdir(self.workdir) + with bz2.open(cpio_path, 'rb') as archive_cpio: + subprocess.run(['cpio', '-i'], input=archive_cpio.read(), + stderr=subprocess.DEVNULL) + os.chdir(cwd) + + @skipUnless(*has_cmd('cpio')) + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_stm32(self): + """ + :avocado: tags=arch:arm + :avocado: tags=linux_user + :avocado: tags=quick + """ + # See https://elinux.org/STM32#User_Space + rootfs_url = ('https://elinux.org/images/5/51/' + 'Stm32_mini_rootfs.cpio.bz2') + rootfs_hash = '9f065e6ba40cce7411ba757f924f30fcc57951e6' + rootfs_path_bz2 = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash) + busybox_path = os.path.join(self.workdir, "/bin/busybox") + + self.extract_cpio(rootfs_path_bz2) + + res = self.run(busybox_path) + ver = 'BusyBox v1.24.0.git (2015-02-03 22:17:13 CET) multi-call binary.' + self.assertIn(ver, res.stdout_text) + + res = self.run(busybox_path, ['uname', '-a']) + unm = 'armv7l GNU/Linux' + self.assertIn(unm, res.stdout_text) diff --git a/tests/avocado/machine_aarch64_sbsaref.py b/tests/avocado/machine_aarch64_sbsaref.py new file mode 100644 index 0000000000..98c76c1ff7 --- /dev/null +++ b/tests/avocado/machine_aarch64_sbsaref.py @@ -0,0 +1,238 @@ +# Functional test that boots a Linux kernel and checks the console +# +# SPDX-FileCopyrightText: 2023-2024 Linaro Ltd. +# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org> +# SPDX-FileContributor: Marcin Juszkiewicz <marcin.juszkiewicz@linaro.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os + +from avocado import skipUnless +from avocado.utils import archive + +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import interrupt_interactive_console_until_pattern + + +class Aarch64SbsarefMachine(QemuSystemTest): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:sbsa-ref + :avocado: tags=accel:tcg + + As firmware runs at a higher privilege level than the hypervisor we + can only run these tests under TCG emulation. + """ + + timeout = 180 + + def fetch_firmware(self): + """ + Flash volumes generated using: + + Toolchain from Debian: + aarch64-linux-gnu-gcc (Debian 12.2.0-14) 12.2.0 + + Used components: + + - Trusted Firmware 2.10.2 + - Tianocore EDK2 stable202402 + - Tianocore EDK2-platforms commit 085c2fb + + """ + + # Secure BootRom (TF-A code) + fs0_xz_url = ( + "https://artifacts.codelinaro.org/artifactory/linaro-419-sbsa-ref/" + "20240313-116475/edk2/SBSA_FLASH0.fd.xz" + ) + fs0_xz_hash = "637593749cc307dea7dc13265c32e5d020267552f22b18a31850b8429fc5e159" + tar_xz_path = self.fetch_asset(fs0_xz_url, asset_hash=fs0_xz_hash, + algorithm='sha256') + archive.extract(tar_xz_path, self.workdir) + fs0_path = os.path.join(self.workdir, "SBSA_FLASH0.fd") + + # Non-secure rom (UEFI and EFI variables) + fs1_xz_url = ( + "https://artifacts.codelinaro.org/artifactory/linaro-419-sbsa-ref/" + "20240313-116475/edk2/SBSA_FLASH1.fd.xz" + ) + fs1_xz_hash = "cb0a5e8cf5e303c5d3dc106cfd5943ffe9714b86afddee7164c69ee1dd41991c" + tar_xz_path = self.fetch_asset(fs1_xz_url, asset_hash=fs1_xz_hash, + algorithm='sha256') + archive.extract(tar_xz_path, self.workdir) + fs1_path = os.path.join(self.workdir, "SBSA_FLASH1.fd") + + for path in [fs0_path, fs1_path]: + with open(path, "ab+") as fd: + fd.truncate(256 << 20) # Expand volumes to 256MiB + + self.vm.set_console() + self.vm.add_args( + "-drive", + f"if=pflash,file={fs0_path},format=raw", + "-drive", + f"if=pflash,file={fs1_path},format=raw", + "-smp", + "1", + "-machine", + "sbsa-ref", + ) + + def test_sbsaref_edk2_firmware(self): + """ + :avocado: tags=cpu:cortex-a57 + """ + + self.fetch_firmware() + self.vm.launch() + + # TF-A boot sequence: + # + # https://github.com/ARM-software/arm-trusted-firmware/blob/v2.8.0/\ + # docs/design/trusted-board-boot.rst#trusted-board-boot-sequence + # https://trustedfirmware-a.readthedocs.io/en/v2.8/\ + # design/firmware-design.html#cold-boot + + # AP Trusted ROM + wait_for_console_pattern(self, "Booting Trusted Firmware") + wait_for_console_pattern(self, "BL1: v2.10.2(release):") + wait_for_console_pattern(self, "BL1: Booting BL2") + + # Trusted Boot Firmware + wait_for_console_pattern(self, "BL2: v2.10.2(release)") + wait_for_console_pattern(self, "Booting BL31") + + # EL3 Runtime Software + wait_for_console_pattern(self, "BL31: v2.10.2(release)") + + # Non-trusted Firmware + wait_for_console_pattern(self, "UEFI firmware (version 1.0") + interrupt_interactive_console_until_pattern(self, "QEMU SBSA-REF Machine") + + # This tests the whole boot chain from EFI to Userspace + # We only boot a whole OS for the current top level CPU and GIC + # Other test profiles should use more minimal boots + def boot_alpine_linux(self, cpu): + self.fetch_firmware() + + iso_url = ( + "https://dl-cdn.alpinelinux.org/" + "alpine/v3.17/releases/aarch64/alpine-standard-3.17.2-aarch64.iso" + ) + + iso_hash = "5a36304ecf039292082d92b48152a9ec21009d3a62f459de623e19c4bd9dc027" + iso_path = self.fetch_asset(iso_url, algorithm="sha256", asset_hash=iso_hash) + + self.vm.set_console() + self.vm.add_args( + "-cpu", + cpu, + "-drive", + f"file={iso_path},format=raw", + ) + + self.vm.launch() + wait_for_console_pattern(self, "Welcome to Alpine Linux 3.17") + + def test_sbsaref_alpine_linux_cortex_a57(self): + """ + :avocado: tags=cpu:cortex-a57 + :avocado: tags=os:linux + """ + self.boot_alpine_linux("cortex-a57") + + def test_sbsaref_alpine_linux_neoverse_n1(self): + """ + :avocado: tags=cpu:neoverse-n1 + :avocado: tags=os:linux + """ + self.boot_alpine_linux("neoverse-n1") + + def test_sbsaref_alpine_linux_max_pauth_off(self): + """ + :avocado: tags=cpu:max + :avocado: tags=os:linux + """ + self.boot_alpine_linux("max,pauth=off") + + def test_sbsaref_alpine_linux_max_pauth_impdef(self): + """ + :avocado: tags=cpu:max + :avocado: tags=os:linux + """ + self.boot_alpine_linux("max,pauth-impdef=on") + + @skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') + def test_sbsaref_alpine_linux_max(self): + """ + :avocado: tags=cpu:max + :avocado: tags=os:linux + """ + self.boot_alpine_linux("max") + + + # This tests the whole boot chain from EFI to Userspace + # We only boot a whole OS for the current top level CPU and GIC + # Other test profiles should use more minimal boots + def boot_openbsd73(self, cpu): + self.fetch_firmware() + + img_url = ( + "https://cdn.openbsd.org/pub/OpenBSD/7.3/arm64/miniroot73.img" + ) + + img_hash = "7fc2c75401d6f01fbfa25f4953f72ad7d7c18650056d30755c44b9c129b707e5" + img_path = self.fetch_asset(img_url, algorithm="sha256", asset_hash=img_hash) + + self.vm.set_console() + self.vm.add_args( + "-cpu", + cpu, + "-drive", + f"file={img_path},format=raw", + ) + + self.vm.launch() + wait_for_console_pattern(self, + "Welcome to the OpenBSD/arm64" + " 7.3 installation program.") + + def test_sbsaref_openbsd73_cortex_a57(self): + """ + :avocado: tags=cpu:cortex-a57 + :avocado: tags=os:openbsd + """ + self.boot_openbsd73("cortex-a57") + + def test_sbsaref_openbsd73_neoverse_n1(self): + """ + :avocado: tags=cpu:neoverse-n1 + :avocado: tags=os:openbsd + """ + self.boot_openbsd73("neoverse-n1") + + def test_sbsaref_openbsd73_max_pauth_off(self): + """ + :avocado: tags=cpu:max + :avocado: tags=os:openbsd + """ + self.boot_openbsd73("max,pauth=off") + + @skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') + def test_sbsaref_openbsd73_max_pauth_impdef(self): + """ + :avocado: tags=cpu:max + :avocado: tags=os:openbsd + """ + self.boot_openbsd73("max,pauth-impdef=on") + + @skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') + def test_sbsaref_openbsd73_max(self): + """ + :avocado: tags=cpu:max + :avocado: tags=os:openbsd + """ + self.boot_openbsd73("max") diff --git a/tests/avocado/machine_aarch64_virt.py b/tests/avocado/machine_aarch64_virt.py new file mode 100644 index 0000000000..a90dc6ff4b --- /dev/null +++ b/tests/avocado/machine_aarch64_virt.py @@ -0,0 +1,146 @@ +# Functional test that boots a various Linux systems and checks the +# console output. +# +# Copyright (c) 2022 Linaro Ltd. +# +# Author: +# Alex Bennée <alex.bennee@linaro.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import time +import os +import logging + +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import exec_command +from avocado_qemu import BUILD_DIR +from avocado.utils import process +from avocado.utils.path import find_command + +class Aarch64VirtMachine(QemuSystemTest): + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + timeout = 360 + + 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) + + # This tests the whole boot chain from EFI to Userspace + # We only boot a whole OS for the current top level CPU and GIC + # Other test profiles should use more minimal boots + def test_alpine_virt_tcg_gic_max(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=accel:tcg + """ + iso_url = ('https://dl-cdn.alpinelinux.org/' + 'alpine/v3.17/releases/aarch64/' + 'alpine-standard-3.17.2-aarch64.iso') + + # Alpine use sha256 so I recalculated this myself + iso_sha1 = '76284fcd7b41fe899b0c2375ceb8470803eea839' + iso_path = self.fetch_asset(iso_url, asset_hash=iso_sha1) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + self.require_accelerator("tcg") + + self.vm.add_args("-accel", "tcg") + self.vm.add_args("-cpu", "max,pauth-impdef=on") + self.vm.add_args("-machine", + "virt,acpi=on," + "virtualization=on," + "mte=on," + "gic-version=max,iommu=smmuv3") + self.vm.add_args("-smp", "2", "-m", "1024") + self.vm.add_args('-bios', os.path.join(BUILD_DIR, 'pc-bios', + 'edk2-aarch64-code.fd')) + self.vm.add_args("-drive", f"file={iso_path},format=raw") + self.vm.add_args('-device', 'virtio-rng-pci,rng=rng0') + self.vm.add_args('-object', 'rng-random,id=rng0,filename=/dev/urandom') + + self.vm.launch() + self.wait_for_console_pattern('Welcome to Alpine Linux 3.17') + + + def common_aarch64_virt(self, machine): + """ + Common code to launch basic virt machine with kernel+initrd + and a scratch disk. + """ + logger = logging.getLogger('aarch64_virt') + + kernel_url = ('https://fileserver.linaro.org/s/' + 'z6B2ARM7DQT3HWN/download') + kernel_hash = 'ed11daab50c151dde0e1e9c9cb8b2d9bd3215347' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + self.require_accelerator("tcg") + self.vm.add_args('-cpu', 'max,pauth-impdef=on', + '-machine', machine, + '-accel', 'tcg', + '-kernel', kernel_path, + '-append', kernel_command_line) + + # A RNG offers an easy way to generate a few IRQs + self.vm.add_args('-device', 'virtio-rng-pci,rng=rng0') + self.vm.add_args('-object', + 'rng-random,id=rng0,filename=/dev/urandom') + + # Also add a scratch block device + logger.info('creating scratch qcow2 image') + image_path = os.path.join(self.workdir, 'scratch.qcow2') + qemu_img = os.path.join(BUILD_DIR, 'qemu-img') + if not os.path.exists(qemu_img): + qemu_img = find_command('qemu-img', False) + if qemu_img is False: + self.cancel('Could not find "qemu-img", which is required to ' + 'create the temporary qcow2 image') + cmd = '%s create -f qcow2 %s 8M' % (qemu_img, image_path) + process.run(cmd) + + # Add the device + self.vm.add_args('-blockdev', + f"driver=qcow2,file.driver=file,file.filename={image_path},node-name=scratch") + self.vm.add_args('-device', + 'virtio-blk-device,drive=scratch') + + self.vm.launch() + self.wait_for_console_pattern('Welcome to Buildroot') + time.sleep(0.1) + exec_command(self, 'root') + time.sleep(0.1) + exec_command(self, 'dd if=/dev/hwrng of=/dev/vda bs=512 count=4') + time.sleep(0.1) + exec_command(self, 'md5sum /dev/vda') + time.sleep(0.1) + exec_command(self, 'cat /proc/interrupts') + time.sleep(0.1) + exec_command(self, 'cat /proc/self/maps') + time.sleep(0.1) + + def test_aarch64_virt_gicv3(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=accel:tcg + :avocado: tags=cpu:max + """ + self.common_aarch64_virt("virt,gic_version=3") + + def test_aarch64_virt_gicv2(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=accel:tcg + :avocado: tags=cpu:max + """ + self.common_aarch64_virt("virt,gic-version=2") diff --git a/tests/avocado/machine_arm_canona1100.py b/tests/avocado/machine_arm_canona1100.py new file mode 100644 index 0000000000..a42d8b0f2b --- /dev/null +++ b/tests/avocado/machine_arm_canona1100.py @@ -0,0 +1,35 @@ +# Functional test that boots the canon-a1100 machine with firmware +# +# Copyright (c) 2020 Red Hat, Inc. +# +# Author: +# Thomas Huth <thuth@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. + +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado.utils import archive + +class CanonA1100Machine(QemuSystemTest): + """Boots the barebox firmware and checks that the console is operational""" + + timeout = 90 + + def test_arm_canona1100(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:canon-a1100 + :avocado: tags=device:pflash_cfi02 + """ + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day18.tar.xz') + tar_hash = '068b5fc4242b29381acee94713509f8a876e9db6' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-bios', + self.workdir + '/day18/barebox.canon-a1100.bin') + self.vm.launch() + wait_for_console_pattern(self, 'running /env/bin/init') diff --git a/tests/avocado/machine_arm_integratorcp.py b/tests/avocado/machine_arm_integratorcp.py new file mode 100644 index 0000000000..87f5cf3953 --- /dev/null +++ b/tests/avocado/machine_arm_integratorcp.py @@ -0,0 +1,99 @@ +# Functional test that boots a Linux kernel and checks the console +# +# Copyright (c) 2020 Red Hat, Inc. +# +# Author: +# Thomas Huth <thuth@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 logging + +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + + +NUMPY_AVAILABLE = True +try: + import numpy as np +except ImportError: + NUMPY_AVAILABLE = False + +CV2_AVAILABLE = True +try: + import cv2 +except ImportError: + CV2_AVAILABLE = False + + +class IntegratorMachine(QemuSystemTest): + + timeout = 90 + + def boot_integratorcp(self): + kernel_url = ('https://github.com/zayac/qemu-arm/raw/master/' + 'arm-test/kernel/zImage.integrator') + kernel_hash = '0d7adba893c503267c946a3cbdc63b4b54f25468' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + initrd_url = ('https://github.com/zayac/qemu-arm/raw/master/' + 'arm-test/kernel/arm_root.img') + initrd_hash = 'b51e4154285bf784e017a37586428332d8c7bd8b' + initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path, + '-initrd', initrd_path, + '-append', 'printk.time=0 console=ttyAMA0') + self.vm.launch() + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_integratorcp_console(self): + """ + Boots the Linux kernel and checks that the console is operational + :avocado: tags=arch:arm + :avocado: tags=machine:integratorcp + :avocado: tags=device:pl011 + """ + self.boot_integratorcp() + wait_for_console_pattern(self, 'Log in as root') + + @skipUnless(NUMPY_AVAILABLE, 'Python NumPy not installed') + @skipUnless(CV2_AVAILABLE, 'Python OpenCV not installed') + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_framebuffer_tux_logo(self): + """ + Boot Linux and verify the Tux logo is displayed on the framebuffer. + :avocado: tags=arch:arm + :avocado: tags=machine:integratorcp + :avocado: tags=device:pl110 + :avocado: tags=device:framebuffer + """ + screendump_path = os.path.join(self.workdir, "screendump.pbm") + tuxlogo_url = ('https://github.com/torvalds/linux/raw/v2.6.12/' + 'drivers/video/logo/logo_linux_vga16.ppm') + tuxlogo_hash = '3991c2ddbd1ddaecda7601f8aafbcf5b02dc86af' + tuxlogo_path = self.fetch_asset(tuxlogo_url, asset_hash=tuxlogo_hash) + + self.boot_integratorcp() + framebuffer_ready = 'Console: switching to colour frame buffer device' + wait_for_console_pattern(self, framebuffer_ready) + self.vm.cmd('human-monitor-command', command_line='stop') + self.vm.cmd('human-monitor-command', + command_line='screendump %s' % screendump_path) + logger = logging.getLogger('framebuffer') + + cpu_count = 1 + match_threshold = 0.92 + screendump_bgr = cv2.imread(screendump_path) + screendump_gray = cv2.cvtColor(screendump_bgr, cv2.COLOR_BGR2GRAY) + result = cv2.matchTemplate(screendump_gray, cv2.imread(tuxlogo_path, 0), + cv2.TM_CCOEFF_NORMED) + loc = np.where(result >= match_threshold) + tux_count = 0 + for tux_count, pt in enumerate(zip(*loc[::-1]), start=1): + logger.debug('found Tux at position [x, y] = %s', pt) + self.assertGreaterEqual(tux_count, cpu_count) diff --git a/tests/avocado/machine_arm_n8x0.py b/tests/avocado/machine_arm_n8x0.py new file mode 100644 index 0000000000..12e9a6803b --- /dev/null +++ b/tests/avocado/machine_arm_n8x0.py @@ -0,0 +1,49 @@ +# Functional test that boots a Linux kernel and checks the console +# +# Copyright (c) 2020 Red Hat, Inc. +# +# Author: +# Thomas Huth <thuth@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 + +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class N8x0Machine(QemuSystemTest): + """Boots the Linux kernel and checks that the console is operational""" + + timeout = 90 + + def __do_test_n8x0(self): + kernel_url = ('http://stskeeps.subnetmask.net/meego-n8x0/' + 'meego-arm-n8x0-1.0.80.20100712.1431-' + 'vmlinuz-2.6.35~rc4-129.1-n8x0') + kernel_hash = 'e9d5ab8d7548923a0061b6fbf601465e479ed269' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console(console_index=1) + self.vm.add_args('-kernel', kernel_path, + '-append', 'printk.time=0 console=ttyS1') + self.vm.launch() + wait_for_console_pattern(self, 'TSC2005 driver initializing') + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_n800(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:n800 + """ + self.__do_test_n8x0() + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_n810(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:n810 + """ + self.__do_test_n8x0() diff --git a/tests/avocado/machine_aspeed.py b/tests/avocado/machine_aspeed.py new file mode 100644 index 0000000000..cec0181424 --- /dev/null +++ b/tests/avocado/machine_aspeed.py @@ -0,0 +1,377 @@ +# Functional test that boots the ASPEED SoCs with firmware +# +# Copyright (C) 2022 ASPEED Technology Inc +# +# 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 time +import os +import tempfile +import subprocess + +from avocado_qemu import LinuxSSHMixIn +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import exec_command +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import interrupt_interactive_console_until_pattern +from avocado_qemu import has_cmd +from avocado.utils import archive +from avocado import skipUnless +from avocado import skipUnless + + +class AST1030Machine(QemuSystemTest): + """Boots the zephyr os and checks that the console is operational""" + + timeout = 10 + + def test_ast1030_zephyros_1_04(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast1030-evb + :avocado: tags=os:zephyr + """ + tar_url = ('https://github.com/AspeedTech-BMC' + '/zephyr/releases/download/v00.01.04/ast1030-evb-demo.zip') + tar_hash = '4c6a8ce3a8ba76ef1a65dae419ae3409343c4b20' + tar_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(tar_path, self.workdir) + kernel_file = self.workdir + "/ast1030-evb-demo/zephyr.elf" + self.vm.set_console() + self.vm.add_args('-kernel', kernel_file, + '-nographic') + self.vm.launch() + wait_for_console_pattern(self, "Booting Zephyr OS") + exec_command_and_wait_for_pattern(self, "help", + "Available commands") + + def test_ast1030_zephyros_1_07(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast1030-evb + :avocado: tags=os:zephyr + """ + tar_url = ('https://github.com/AspeedTech-BMC' + '/zephyr/releases/download/v00.01.07/ast1030-evb-demo.zip') + tar_hash = '40ac87eabdcd3b3454ce5aad11fedc72a33ecda2' + tar_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(tar_path, self.workdir) + kernel_file = self.workdir + "/ast1030-evb-demo/zephyr.bin" + self.vm.set_console() + self.vm.add_args('-kernel', kernel_file, + '-nographic') + self.vm.launch() + wait_for_console_pattern(self, "Booting Zephyr OS") + for shell_cmd in [ + 'kernel stacks', + 'otp info conf', + 'otp info scu', + 'hwinfo devid', + 'crypto aes256_cbc_vault', + 'random get', + 'jtag JTAG1 sw_xfer high TMS', + 'adc ADC0 resolution 12', + 'adc ADC0 read 42', + 'adc ADC1 read 69', + 'i2c scan I2C_0', + 'i3c attach I3C_0', + 'hash test', + 'kernel uptime', + 'kernel reboot warm', + 'kernel uptime', + 'kernel reboot cold', + 'kernel uptime', + ]: exec_command_and_wait_for_pattern(self, shell_cmd, "uart:~$") + +class AST2x00Machine(QemuSystemTest): + + timeout = 90 + + 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 do_test_arm_aspeed(self, image): + self.vm.set_console() + self.vm.add_args('-drive', 'file=' + image + ',if=mtd,format=raw', + '-net', 'nic') + self.vm.launch() + + self.wait_for_console_pattern("U-Boot 2016.07") + self.wait_for_console_pattern("## Loading kernel from FIT Image at 20080000") + self.wait_for_console_pattern("Starting kernel ...") + self.wait_for_console_pattern("Booting Linux on physical CPU 0x0") + wait_for_console_pattern(self, + "aspeed-smc 1e620000.spi: read control register: 203b0641") + self.wait_for_console_pattern("ftgmac100 1e660000.ethernet eth0: irq ") + self.wait_for_console_pattern("systemd[1]: Set hostname to") + + def test_arm_ast2400_palmetto_openbmc_v2_9_0(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:palmetto-bmc + """ + + image_url = ('https://github.com/openbmc/openbmc/releases/download/2.9.0/' + 'obmc-phosphor-image-palmetto.static.mtd') + image_hash = ('3e13bbbc28e424865dc42f35ad672b10f2e82cdb11846bb28fa625b48beafd0d') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + + self.do_test_arm_aspeed(image_path) + + def test_arm_ast2500_romulus_openbmc_v2_9_0(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:romulus-bmc + """ + + image_url = ('https://github.com/openbmc/openbmc/releases/download/2.9.0/' + 'obmc-phosphor-image-romulus.static.mtd') + image_hash = ('820341076803f1955bc31e647a512c79f9add4f5233d0697678bab4604c7bb25') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + + self.do_test_arm_aspeed(image_path) + + def do_test_arm_aspeed_buildroot_start(self, image, cpu_id, pattern='Aspeed EVB'): + self.require_netdev('user') + + self.vm.set_console() + self.vm.add_args('-drive', 'file=' + image + ',if=mtd,format=raw', + '-net', 'nic', '-net', 'user') + self.vm.launch() + + self.wait_for_console_pattern('U-Boot 2019.04') + self.wait_for_console_pattern('## Loading kernel from FIT Image') + self.wait_for_console_pattern('Starting kernel ...') + self.wait_for_console_pattern('Booting Linux on physical CPU ' + cpu_id) + self.wait_for_console_pattern('lease of 10.0.2.15') + # the line before login: + self.wait_for_console_pattern(pattern) + time.sleep(0.1) + exec_command(self, 'root') + time.sleep(0.1) + exec_command(self, "passw0rd") + + def do_test_arm_aspeed_buildroot_poweroff(self): + exec_command_and_wait_for_pattern(self, 'poweroff', + 'reboot: System halted'); + + def test_arm_ast2500_evb_buildroot(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast2500-evb + """ + + image_url = ('https://github.com/legoater/qemu-aspeed-boot/raw/master/' + 'images/ast2500-evb/buildroot-2023.11/flash.img') + image_hash = ('c23db6160cf77d0258397eb2051162c8473a56c441417c52a91ba217186e715f') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + + self.vm.add_args('-device', + 'tmp105,bus=aspeed.i2c.bus.3,address=0x4d,id=tmp-test'); + self.do_test_arm_aspeed_buildroot_start(image_path, '0x0', 'Aspeed AST2500 EVB') + + exec_command_and_wait_for_pattern(self, + 'echo lm75 0x4d > /sys/class/i2c-dev/i2c-3/device/new_device', + 'i2c i2c-3: new_device: Instantiated device lm75 at 0x4d'); + exec_command_and_wait_for_pattern(self, + 'cat /sys/class/hwmon/hwmon1/temp1_input', '0') + self.vm.cmd('qom-set', path='/machine/peripheral/tmp-test', + property='temperature', value=18000); + exec_command_and_wait_for_pattern(self, + 'cat /sys/class/hwmon/hwmon1/temp1_input', '18000') + + self.do_test_arm_aspeed_buildroot_poweroff() + + def test_arm_ast2600_evb_buildroot(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast2600-evb + """ + + image_url = ('https://github.com/legoater/qemu-aspeed-boot/raw/master/' + 'images/ast2600-evb/buildroot-2023.11/flash.img') + image_hash = ('b62808daef48b438d0728ee07662290490ecfa65987bb91294cafb1bb7ad1a68') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + + self.vm.add_args('-device', + 'tmp105,bus=aspeed.i2c.bus.3,address=0x4d,id=tmp-test'); + self.vm.add_args('-device', + 'ds1338,bus=aspeed.i2c.bus.3,address=0x32'); + self.vm.add_args('-device', + 'i2c-echo,bus=aspeed.i2c.bus.3,address=0x42'); + self.do_test_arm_aspeed_buildroot_start(image_path, '0xf00', 'Aspeed AST2600 EVB') + + exec_command_and_wait_for_pattern(self, + 'echo lm75 0x4d > /sys/class/i2c-dev/i2c-3/device/new_device', + 'i2c i2c-3: new_device: Instantiated device lm75 at 0x4d'); + exec_command_and_wait_for_pattern(self, + 'cat /sys/class/hwmon/hwmon1/temp1_input', '0') + self.vm.cmd('qom-set', path='/machine/peripheral/tmp-test', + property='temperature', value=18000); + exec_command_and_wait_for_pattern(self, + 'cat /sys/class/hwmon/hwmon1/temp1_input', '18000') + + exec_command_and_wait_for_pattern(self, + 'echo ds1307 0x32 > /sys/class/i2c-dev/i2c-3/device/new_device', + 'i2c i2c-3: new_device: Instantiated device ds1307 at 0x32'); + year = time.strftime("%Y") + exec_command_and_wait_for_pattern(self, 'hwclock -f /dev/rtc1', year); + + exec_command_and_wait_for_pattern(self, + 'echo slave-24c02 0x1064 > /sys/bus/i2c/devices/i2c-3/new_device', + 'i2c i2c-3: new_device: Instantiated device slave-24c02 at 0x64'); + exec_command(self, 'i2cset -y 3 0x42 0x64 0x00 0xaa i'); + time.sleep(0.1) + exec_command_and_wait_for_pattern(self, + 'hexdump /sys/bus/i2c/devices/3-1064/slave-eeprom', + '0000000 ffaa ffff ffff ffff ffff ffff ffff ffff'); + self.do_test_arm_aspeed_buildroot_poweroff() + + @skipUnless(*has_cmd('swtpm')) + def test_arm_ast2600_evb_buildroot_tpm(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast2600-evb + """ + + image_url = ('https://github.com/legoater/qemu-aspeed-boot/raw/master/' + 'images/ast2600-evb/buildroot-2023.02-tpm/flash.img') + image_hash = ('a46009ae8a5403a0826d607215e731a8c68d27c14c41e55331706b8f9c7bd997') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + + # force creation of VM object, which also defines self._sd + vm = self.vm + + socket = os.path.join(self._sd.name, 'swtpm-socket') + + subprocess.run(['swtpm', 'socket', '-d', '--tpm2', + '--tpmstate', f'dir={self.vm.temp_dir}', + '--ctrl', f'type=unixio,path={socket}']) + + self.vm.add_args('-chardev', f'socket,id=chrtpm,path={socket}') + self.vm.add_args('-tpmdev', 'emulator,id=tpm0,chardev=chrtpm') + self.vm.add_args('-device', + 'tpm-tis-i2c,tpmdev=tpm0,bus=aspeed.i2c.bus.12,address=0x2e') + self.do_test_arm_aspeed_buildroot_start(image_path, '0xf00', 'Aspeed AST2600 EVB') + + exec_command_and_wait_for_pattern(self, + 'echo tpm_tis_i2c 0x2e > /sys/bus/i2c/devices/i2c-12/new_device', + 'tpm_tis_i2c 12-002e: 2.0 TPM (device-id 0x1, rev-id 1)'); + exec_command_and_wait_for_pattern(self, + 'cat /sys/class/tpm/tpm0/pcr-sha256/0', + 'B804724EA13F52A9072BA87FE8FDCC497DFC9DF9AA15B9088694639C431688E0'); + + self.do_test_arm_aspeed_buildroot_poweroff() + +class AST2x00MachineSDK(QemuSystemTest, LinuxSSHMixIn): + + EXTRA_BOOTARGS = ( + 'quiet ' + 'systemd.mask=org.openbmc.HostIpmi.service ' + 'systemd.mask=xyz.openbmc_project.Chassis.Control.Power@0.service ' + 'systemd.mask=modprobe@fuse.service ' + 'systemd.mask=rngd.service ' + 'systemd.mask=obmc-console@ttyS2.service ' + ) + + # FIXME: Although these tests boot a whole distro they are still + # slower than comparable machine models. There may be some + # optimisations which bring down the runtime. In the meantime they + # have generous timeouts and are disable for CI which aims for all + # tests to run in less than 60 seconds. + timeout = 240 + + 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 do_test_arm_aspeed_sdk_start(self, image): + self.require_netdev('user') + self.vm.set_console() + self.vm.add_args('-drive', 'file=' + image + ',if=mtd,format=raw', + '-net', 'nic', '-net', 'user,hostfwd=:127.0.0.1:0-:22') + self.vm.launch() + + self.wait_for_console_pattern('U-Boot 2019.04') + interrupt_interactive_console_until_pattern( + self, 'Hit any key to stop autoboot:', 'ast#') + exec_command_and_wait_for_pattern( + self, 'setenv bootargs ${bootargs} ' + self.EXTRA_BOOTARGS, 'ast#') + exec_command_and_wait_for_pattern( + self, 'boot', '## Loading kernel from FIT Image') + self.wait_for_console_pattern('Starting kernel ...') + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_arm_ast2500_evb_sdk(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast2500-evb + :avocado: tags=flaky + """ + + image_url = ('https://github.com/AspeedTech-BMC/openbmc/releases/' + 'download/v08.06/ast2500-default-obmc.tar.gz') + image_hash = ('e1755f3cadff69190438c688d52dd0f0d399b70a1e14b1d3d5540fc4851d38ca') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + archive.extract(image_path, self.workdir) + + self.do_test_arm_aspeed_sdk_start( + self.workdir + '/ast2500-default/image-bmc') + self.wait_for_console_pattern('nodistro.0 ast2500-default ttyS4') + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_arm_ast2600_evb_sdk(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:ast2600-evb + :avocado: tags=flaky + """ + + image_url = ('https://github.com/AspeedTech-BMC/openbmc/releases/' + 'download/v08.06/ast2600-a2-obmc.tar.gz') + image_hash = ('9083506135f622d5e7351fcf7d4e1c7125cee5ba16141220c0ba88931f3681a4') + image_path = self.fetch_asset(image_url, asset_hash=image_hash, + algorithm='sha256') + archive.extract(image_path, self.workdir) + + self.vm.add_args('-device', + 'tmp105,bus=aspeed.i2c.bus.5,address=0x4d,id=tmp-test'); + self.vm.add_args('-device', + 'ds1338,bus=aspeed.i2c.bus.5,address=0x32'); + self.do_test_arm_aspeed_sdk_start( + self.workdir + '/ast2600-a2/image-bmc') + self.wait_for_console_pattern('nodistro.0 ast2600-a2 ttyS4') + + self.ssh_connect('root', '0penBmc', False) + self.ssh_command('dmesg -c > /dev/null') + + self.ssh_command_output_contains( + 'echo lm75 0x4d > /sys/class/i2c-dev/i2c-5/device/new_device ; ' + 'dmesg -c', + 'i2c i2c-5: new_device: Instantiated device lm75 at 0x4d'); + self.ssh_command_output_contains( + 'cat /sys/class/hwmon/hwmon19/temp1_input', '0') + self.vm.cmd('qom-set', path='/machine/peripheral/tmp-test', + property='temperature', value=18000); + self.ssh_command_output_contains( + 'cat /sys/class/hwmon/hwmon19/temp1_input', '18000') + + self.ssh_command_output_contains( + 'echo ds1307 0x32 > /sys/class/i2c-dev/i2c-5/device/new_device ; ' + 'dmesg -c', + 'i2c i2c-5: new_device: Instantiated device ds1307 at 0x32'); + year = time.strftime("%Y") + self.ssh_command_output_contains('/sbin/hwclock -f /dev/rtc1', year); diff --git a/tests/avocado/machine_avr6.py b/tests/avocado/machine_avr6.py new file mode 100644 index 0000000000..5485db79c6 --- /dev/null +++ b/tests/avocado/machine_avr6.py @@ -0,0 +1,50 @@ +# +# QEMU AVR integration tests +# +# Copyright (c) 2019-2020 Michael Rolnik <mrolnik@gmail.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import time + +from avocado_qemu import QemuSystemTest + +class AVR6Machine(QemuSystemTest): + timeout = 5 + + def test_freertos(self): + """ + :avocado: tags=arch:avr + :avocado: tags=machine:arduino-mega-2560-v3 + """ + """ + https://github.com/seharris/qemu-avr-tests/raw/master/free-rtos/Demo/AVR_ATMega2560_GCC/demo.elf + constantly prints out 'ABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOPQRSTUVWX' + """ + rom_url = ('https://github.com/seharris/qemu-avr-tests' + '/raw/36c3e67b8755dcf/free-rtos/Demo' + '/AVR_ATMega2560_GCC/demo.elf') + rom_hash = '7eb521f511ca8f2622e0a3c5e8dd686efbb911d4' + rom_path = self.fetch_asset(rom_url, asset_hash=rom_hash) + + self.vm.add_args('-bios', rom_path) + self.vm.add_args('-nographic') + self.vm.launch() + + time.sleep(2) + self.vm.shutdown() + + self.assertIn('ABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOPQRSTUVWX', + self.vm.get_log()) diff --git a/tests/avocado/machine_loongarch.py b/tests/avocado/machine_loongarch.py new file mode 100644 index 0000000000..7d8a3c1fa5 --- /dev/null +++ b/tests/avocado/machine_loongarch.py @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# LoongArch virt test. +# +# Copyright (c) 2023 Loongson Technology Corporation Limited +# + +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import wait_for_console_pattern + +class LoongArchMachine(QemuSystemTest): + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + + timeout = 120 + + 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 test_loongarch64_devices(self): + + """ + :avocado: tags=arch:loongarch64 + :avocado: tags=machine:virt + """ + + kernel_url = ('https://github.com/yangxiaojuan-loongson/qemu-binary/' + 'releases/download/binary-files/vmlinuz.efi') + kernel_hash = '951b485b16e3788b6db03a3e1793c067009e31a2' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + initrd_url = ('https://github.com/yangxiaojuan-loongson/qemu-binary/' + 'releases/download/binary-files/ramdisk') + initrd_hash = 'c67658d9b2a447ce7db2f73ba3d373c9b2b90ab2' + initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + + bios_url = ('https://github.com/yangxiaojuan-loongson/qemu-binary/' + 'releases/download/binary-files/QEMU_EFI.fd') + bios_hash = ('dfc1bfba4853cd763b9d392d0031827e8addbca8') + bios_path = self.fetch_asset(bios_url, asset_hash=bios_hash) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'root=/dev/ram rdinit=/sbin/init console=ttyS0,115200') + self.vm.add_args('-nographic', + '-smp', '4', + '-m', '1024', + '-cpu', 'la464', + '-kernel', kernel_path, + '-initrd', initrd_path, + '-bios', bios_path, + '-append', kernel_command_line) + self.vm.launch() + self.wait_for_console_pattern('Run /sbin/init as init process') + exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo', + 'processor : 3') diff --git a/tests/avocado/machine_m68k_nextcube.py b/tests/avocado/machine_m68k_nextcube.py new file mode 100644 index 0000000000..1f3c883910 --- /dev/null +++ b/tests/avocado/machine_m68k_nextcube.py @@ -0,0 +1,70 @@ +# Functional test that boots a VM and run OCR on the framebuffer +# +# Copyright (c) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# 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 time + +from avocado_qemu import QemuSystemTest +from avocado import skipUnless + +from tesseract_utils import tesseract_available, tesseract_ocr + +PIL_AVAILABLE = True +try: + from PIL import Image +except ImportError: + PIL_AVAILABLE = False + + +class NextCubeMachine(QemuSystemTest): + """ + :avocado: tags=arch:m68k + :avocado: tags=machine:next-cube + :avocado: tags=device:framebuffer + """ + + timeout = 15 + + def check_bootrom_framebuffer(self, screenshot_path): + rom_url = ('https://sourceforge.net/p/previous/code/1350/tree/' + 'trunk/src/Rev_2.5_v66.BIN?format=raw') + rom_hash = 'b3534796abae238a0111299fc406a9349f7fee24' + rom_path = self.fetch_asset(rom_url, asset_hash=rom_hash) + + self.vm.add_args('-bios', rom_path) + self.vm.launch() + + self.log.info('VM launched, waiting for display') + # TODO: Use avocado.utils.wait.wait_for to catch the + # 'displaysurface_create 1120x832' trace-event. + time.sleep(2) + + self.vm.cmd('human-monitor-command', + command_line='screendump %s' % screenshot_path) + + @skipUnless(PIL_AVAILABLE, 'Python PIL not installed') + def test_bootrom_framebuffer_size(self): + screenshot_path = os.path.join(self.workdir, "dump.ppm") + self.check_bootrom_framebuffer(screenshot_path) + + width, height = Image.open(screenshot_path).size + self.assertEqual(width, 1120) + self.assertEqual(height, 832) + + # Tesseract 4 adds a new OCR engine based on LSTM neural networks. The + # new version is faster and more accurate than version 3. The drawback is + # that it is still alpha-level software. + @skipUnless(tesseract_available(4), 'tesseract OCR tool not available') + def test_bootrom_framebuffer_ocr_with_tesseract(self): + screenshot_path = os.path.join(self.workdir, "dump.ppm") + self.check_bootrom_framebuffer(screenshot_path) + lines = tesseract_ocr(screenshot_path, tesseract_version=4) + text = '\n'.join(lines) + self.assertIn('Testing the FPU', text) + self.assertIn('System test failed. Error code', text) + self.assertIn('Boot command', text) + self.assertIn('Next>', text) diff --git a/tests/avocado/machine_microblaze.py b/tests/avocado/machine_microblaze.py new file mode 100644 index 0000000000..807709cd11 --- /dev/null +++ b/tests/avocado/machine_microblaze.py @@ -0,0 +1,61 @@ +# Functional test that boots a microblaze Linux kernel and checks the console +# +# Copyright (c) 2018, 2021 Red Hat, Inc. +# +# 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 time +from avocado_qemu import exec_command, exec_command_and_wait_for_pattern +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado.utils import archive + +class MicroblazeMachine(QemuSystemTest): + + timeout = 90 + + def test_microblaze_s3adsp1800(self): + """ + :avocado: tags=arch:microblaze + :avocado: tags=machine:petalogix-s3adsp1800 + """ + + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day17.tar.xz') + tar_hash = '08bf3e3bfb6b6c7ce1e54ab65d54e189f2caf13f' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-kernel', self.workdir + '/day17/ballerina.bin') + self.vm.launch() + wait_for_console_pattern(self, 'This architecture does not have ' + 'kernel memory protection') + # Note: + # The kernel sometimes gets stuck after the "This architecture ..." + # message, that's why we don't test for a later string here. This + # needs some investigation by a microblaze wizard one day... + + def test_microblazeel_s3adsp1800(self): + """ + :avocado: tags=arch:microblazeel + :avocado: tags=machine:petalogix-s3adsp1800 + """ + + self.require_netdev('user') + tar_url = ('http://www.qemu-advent-calendar.org/2023/download/' + 'day13.tar.gz') + tar_hash = '6623d5fff5f84cfa8f34e286f32eff6a26546f44' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-kernel', self.workdir + '/day13/xmaton.bin') + self.vm.add_args('-nic', 'user,tftp=' + self.workdir + '/day13/') + self.vm.launch() + wait_for_console_pattern(self, 'QEMU Advent Calendar 2023') + time.sleep(0.1) + exec_command(self, 'root') + time.sleep(0.1) + exec_command_and_wait_for_pattern(self, + 'tftp -g -r xmaton.png 10.0.2.2 ; md5sum xmaton.png', + '821cd3cab8efd16ad6ee5acc3642a8ea') diff --git a/tests/avocado/machine_mips_fuloong2e.py b/tests/avocado/machine_mips_fuloong2e.py new file mode 100644 index 0000000000..89291f47b2 --- /dev/null +++ b/tests/avocado/machine_mips_fuloong2e.py @@ -0,0 +1,42 @@ +# Functional tests for the Lemote Fuloong-2E machine. +# +# Copyright (c) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os + +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class MipsFuloong2e(QemuSystemTest): + + timeout = 60 + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + @skipUnless(os.getenv('RESCUE_YL_PATH'), 'RESCUE_YL_PATH not available') + def test_linux_kernel_isa_serial(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:fuloong2e + :avocado: tags=endian:little + :avocado: tags=device:bonito64 + :avocado: tags=device:via686b + """ + # Recovery system for the Yeeloong laptop + # (enough to test the fuloong2e southbridge, accessing its ISA bus) + # http://dev.lemote.com/files/resource/download/rescue/rescue-yl + kernel_hash = 'ec4d1bd89a8439c41033ca63db60160cc6d6f09a' + kernel_path = self.fetch_asset('file://' + os.getenv('RESCUE_YL_PATH'), + asset_hash=kernel_hash) + + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path) + self.vm.launch() + wait_for_console_pattern(self, 'Linux version 2.6.27.7lemote') + cpu_revision = 'CPU revision is: 00006302 (ICT Loongson-2)' + wait_for_console_pattern(self, cpu_revision) diff --git a/tests/avocado/machine_mips_loongson3v.py b/tests/avocado/machine_mips_loongson3v.py new file mode 100644 index 0000000000..5194cf18c9 --- /dev/null +++ b/tests/avocado/machine_mips_loongson3v.py @@ -0,0 +1,39 @@ +# Functional tests for the Generic Loongson-3 Platform. +# +# Copyright (c) 2021 Jiaxun Yang <jiaxun.yang@flygoat.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. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import time + +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class MipsLoongson3v(QemuSystemTest): + timeout = 60 + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_pmon_serial_console(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=endian:little + :avocado: tags=machine:loongson3-virt + :avocado: tags=cpu:Loongson-3A1000 + :avocado: tags=device:liointc + :avocado: tags=device:goldfish_rtc + """ + + pmon_hash = '7c8b45dd81ccfc55ff28f5aa267a41c3' + pmon_path = self.fetch_asset('https://github.com/loongson-community/pmon/' + 'releases/download/20210112/pmon-3avirt.bin', + asset_hash=pmon_hash, algorithm='md5') + + self.vm.set_console() + self.vm.add_args('-bios', pmon_path) + self.vm.launch() + wait_for_console_pattern(self, 'CPU GODSON3 BogoMIPS:') diff --git a/tests/avocado/machine_mips_malta.py b/tests/avocado/machine_mips_malta.py new file mode 100644 index 0000000000..8cf84bd805 --- /dev/null +++ b/tests/avocado/machine_mips_malta.py @@ -0,0 +1,164 @@ +# Functional tests for the MIPS Malta board +# +# Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import gzip +import logging + +from avocado import skipUnless +from avocado import skipUnless +from avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import interrupt_interactive_console_until_pattern +from avocado_qemu import wait_for_console_pattern + + +NUMPY_AVAILABLE = True +try: + import numpy as np +except ImportError: + NUMPY_AVAILABLE = False + +CV2_AVAILABLE = True +try: + import cv2 +except ImportError: + CV2_AVAILABLE = False + + +@skipUnless(NUMPY_AVAILABLE, 'Python NumPy not installed') +@skipUnless(CV2_AVAILABLE, 'Python OpenCV not installed') +class MaltaMachineFramebuffer(QemuSystemTest): + + timeout = 30 + + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + + def do_test_i6400_framebuffer_logo(self, cpu_cores_count): + """ + Boot Linux kernel and check Tux logo is displayed on the framebuffer. + """ + screendump_path = os.path.join(self.workdir, 'screendump.pbm') + + kernel_url = ('https://github.com/philmd/qemu-testing-blob/raw/' + 'a5966ca4b5/mips/malta/mips64el/' + 'vmlinux-4.7.0-rc1.I6400.gz') + kernel_hash = '096f50c377ec5072e6a366943324622c312045f6' + kernel_path_gz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + kernel_path = self.workdir + "vmlinux" + archive.gzip_uncompress(kernel_path_gz, kernel_path) + + tuxlogo_url = ('https://github.com/torvalds/linux/raw/v2.6.12/' + 'drivers/video/logo/logo_linux_vga16.ppm') + tuxlogo_hash = '3991c2ddbd1ddaecda7601f8aafbcf5b02dc86af' + tuxlogo_path = self.fetch_asset(tuxlogo_url, asset_hash=tuxlogo_hash) + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'clocksource=GIC console=tty0 console=ttyS0') + self.vm.add_args('-kernel', kernel_path, + '-smp', '%u' % cpu_cores_count, + '-vga', 'std', + '-append', kernel_command_line) + self.vm.launch() + framebuffer_ready = 'Console: switching to colour frame buffer device' + wait_for_console_pattern(self, framebuffer_ready, + failure_message='Kernel panic - not syncing') + self.vm.cmd('human-monitor-command', command_line='stop') + self.vm.cmd('human-monitor-command', + command_line='screendump %s' % screendump_path) + logger = logging.getLogger('framebuffer') + + match_threshold = 0.95 + screendump_bgr = cv2.imread(screendump_path, cv2.IMREAD_COLOR) + tuxlogo_bgr = cv2.imread(tuxlogo_path, cv2.IMREAD_COLOR) + result = cv2.matchTemplate(screendump_bgr, tuxlogo_bgr, + cv2.TM_CCOEFF_NORMED) + loc = np.where(result >= match_threshold) + tuxlogo_count = 0 + h, w = tuxlogo_bgr.shape[:2] + debug_png = os.getenv('AVOCADO_CV2_SCREENDUMP_PNG_PATH') + for tuxlogo_count, pt in enumerate(zip(*loc[::-1]), start=1): + logger.debug('found Tux at position (x, y) = %s', pt) + cv2.rectangle(screendump_bgr, pt, + (pt[0] + w, pt[1] + h), (0, 0, 255), 2) + if debug_png: + cv2.imwrite(debug_png, screendump_bgr) + self.assertGreaterEqual(tuxlogo_count, cpu_cores_count) + + def test_mips_malta_i6400_framebuffer_logo_1core(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=cpu:I6400 + """ + self.do_test_i6400_framebuffer_logo(1) + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_mips_malta_i6400_framebuffer_logo_7cores(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=cpu:I6400 + :avocado: tags=mips:smp + :avocado: tags=flaky + """ + self.do_test_i6400_framebuffer_logo(7) + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_mips_malta_i6400_framebuffer_logo_8cores(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=cpu:I6400 + :avocado: tags=mips:smp + :avocado: tags=flaky + """ + self.do_test_i6400_framebuffer_logo(8) + +class MaltaMachine(QemuSystemTest): + + def do_test_yamon(self): + rom_url = ('https://s3-eu-west-1.amazonaws.com/' + 'downloads-mips/mips-downloads/' + 'YAMON/yamon-bin-02.22.zip') + rom_hash = '8da7ecddbc5312704b8b324341ee238189bde480' + zip_path = self.fetch_asset(rom_url, asset_hash=rom_hash) + + archive.extract(zip_path, self.workdir) + yamon_path = os.path.join(self.workdir, 'yamon-02.22.bin') + + self.vm.set_console() + self.vm.add_args('-bios', yamon_path) + self.vm.launch() + + prompt = 'YAMON>' + pattern = 'YAMON ROM Monitor' + interrupt_interactive_console_until_pattern(self, pattern, prompt) + wait_for_console_pattern(self, prompt) + self.vm.shutdown() + + def test_mipsel_malta_yamon(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + """ + self.do_test_yamon() + + def test_mips64el_malta_yamon(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=endian:little + """ + self.do_test_yamon() diff --git a/tests/avocado/machine_rx_gdbsim.py b/tests/avocado/machine_rx_gdbsim.py new file mode 100644 index 0000000000..412a7a5089 --- /dev/null +++ b/tests/avocado/machine_rx_gdbsim.py @@ -0,0 +1,77 @@ +# Functional test that boots a Linux kernel and checks the console +# +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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 + +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import wait_for_console_pattern +from avocado.utils import archive + + +class RxGdbSimMachine(QemuSystemTest): + + timeout = 30 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_uboot(self): + """ + U-Boot and checks that the console is operational. + + :avocado: tags=arch:rx + :avocado: tags=machine:gdbsim-r5f562n8 + :avocado: tags=endian:little + :avocado: tags=flaky + """ + uboot_url = ('https://acc.dl.osdn.jp/users/23/23888/u-boot.bin.gz') + uboot_hash = '9b78dbd43b40b2526848c0b1ce9de02c24f4dcdb' + uboot_path = self.fetch_asset(uboot_url, asset_hash=uboot_hash) + uboot_path = archive.uncompress(uboot_path, self.workdir) + + self.vm.set_console() + self.vm.add_args('-bios', uboot_path, + '-no-reboot') + self.vm.launch() + uboot_version = 'U-Boot 2016.05-rc3-23705-ga1ef3c71cb-dirty' + wait_for_console_pattern(self, uboot_version) + gcc_version = 'rx-unknown-linux-gcc (GCC) 9.0.0 20181105 (experimental)' + # FIXME limit baudrate on chardev, else we type too fast + #exec_command_and_wait_for_pattern(self, 'version', gcc_version) + + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_linux_sash(self): + """ + Boots a Linux kernel and checks that the console is operational. + + :avocado: tags=arch:rx + :avocado: tags=machine:gdbsim-r5f562n7 + :avocado: tags=endian:little + :avocado: tags=flaky + """ + dtb_url = ('https://acc.dl.osdn.jp/users/23/23887/rx-virt.dtb') + dtb_hash = '7b4e4e2c71905da44e86ce47adee2210b026ac18' + dtb_path = self.fetch_asset(dtb_url, asset_hash=dtb_hash) + kernel_url = ('http://acc.dl.osdn.jp/users/23/23845/zImage') + kernel_hash = '39a81067f8d72faad90866ddfefa19165d68fc99' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon' + self.vm.add_args('-kernel', kernel_path, + '-dtb', dtb_path, + '-no-reboot') + self.vm.launch() + wait_for_console_pattern(self, 'Sash command shell (version 1.1.1)', + failure_message='Kernel panic - not syncing') + exec_command_and_wait_for_pattern(self, 'printenv', 'TERM=linux') diff --git a/tests/avocado/machine_s390_ccw_virtio.py b/tests/avocado/machine_s390_ccw_virtio.py new file mode 100644 index 0000000000..26e938c9e9 --- /dev/null +++ b/tests/avocado/machine_s390_ccw_virtio.py @@ -0,0 +1,277 @@ +# 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 avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import wait_for_console_pattern +from avocado.utils import archive + +class S390CCWVirtioMachine(QemuSystemTest): + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + + timeout = 120 + + 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): + + """ + :avocado: tags=arch:s390x + :avocado: tags=machine:s390-ccw-virtio + """ + + kernel_url = ('https://snapshot.debian.org/archive/debian/' + '20201126T092837Z/dists/buster/main/installer-s390x/' + '20190702+deb10u6/images/generic/kernel.debian') + kernel_hash = '5821fbee57d6220a067a8b967d24595621aa1eb6' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + initrd_url = ('https://snapshot.debian.org/archive/debian/' + '20201126T092837Z/dists/buster/main/installer-s390x/' + '20190702+deb10u6/images/generic/initrd.debian') + initrd_hash = '81ba09c97bef46e8f4660ac25b4ac0a5be3a94d6' + initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + + 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): + + """ + :avocado: tags=arch:s390x + :avocado: tags=machine:s390-ccw-virtio + :avocado: tags=device:virtio-gpu + :avocado: tags=device:virtio-crypto + :avocado: tags=device:virtio-net + :avocado: tags=flaky + """ + + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/31/Server/s390x/os' + '/images/kernel.img') + kernel_hash = 'b93d1efcafcf29c1673a4ce371a1f8b43941cfeb' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + initrd_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/31/Server/s390x/os' + '/images/initrd.img') + initrd_hash = '3de45d411df5624b8d8ef21cd0b44419ab59b12f' + initrd_path_xz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'initrd-raw.img') + archive.lzma_uncompress(initrd_path_xz, initrd_path) + + 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('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.') diff --git a/tests/avocado/machine_sparc64_sun4u.py b/tests/avocado/machine_sparc64_sun4u.py new file mode 100644 index 0000000000..d333c0ae91 --- /dev/null +++ b/tests/avocado/machine_sparc64_sun4u.py @@ -0,0 +1,36 @@ +# Functional test that boots a Linux kernel and checks the console +# +# Copyright (c) 2020 Red Hat, Inc. +# +# Author: +# Thomas Huth <thuth@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 + +from avocado_qemu import wait_for_console_pattern +from avocado.utils import archive +from boot_linux_console import LinuxKernelTest + +class Sun4uMachine(LinuxKernelTest): + """Boots the Linux kernel and checks that the console is operational""" + + timeout = 90 + + def test_sparc64_sun4u(self): + """ + :avocado: tags=arch:sparc64 + :avocado: tags=machine:sun4u + """ + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day23.tar.xz') + tar_hash = '142db83cd974ffadc4f75c8a5cad5bcc5722c240' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-kernel', self.workdir + '/day23/vmlinux', + '-append', self.KERNEL_COMMON_COMMAND_LINE) + self.vm.launch() + wait_for_console_pattern(self, 'Starting logging: OK') diff --git a/tests/avocado/machine_sparc_leon3.py b/tests/avocado/machine_sparc_leon3.py new file mode 100644 index 0000000000..e61b223185 --- /dev/null +++ b/tests/avocado/machine_sparc_leon3.py @@ -0,0 +1,37 @@ +# Functional test that boots a Leon3 machine and checks its serial console. +# +# Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# 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 avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado import skip + + +class Leon3Machine(QemuSystemTest): + + timeout = 60 + + @skip("Test currently broken") + # A Window Underflow exception occurs before booting the kernel, + # and QEMU exit calling cpu_abort(), which makes this test to fail. + def test_leon3_helenos_uimage(self): + """ + :avocado: tags=arch:sparc + :avocado: tags=machine:leon3_generic + :avocado: tags=binfmt:uimage + """ + kernel_url = ('http://www.helenos.org/releases/' + 'HelenOS-0.6.0-sparc32-leon3.bin') + kernel_hash = 'a88c9cfdb8430c66650e5290a08765f9bf049a30' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path) + + self.vm.launch() + + wait_for_console_pattern(self, 'Copyright (c) 2001-2014 HelenOS project') + wait_for_console_pattern(self, 'Booting the kernel ...') diff --git a/tests/avocado/mem-addr-space-check.py b/tests/avocado/mem-addr-space-check.py new file mode 100644 index 0000000000..af019969c0 --- /dev/null +++ b/tests/avocado/mem-addr-space-check.py @@ -0,0 +1,356 @@ +# Check for crash when using memory beyond the available guest processor +# address space. +# +# Copyright (c) 2023 Red Hat, Inc. +# +# Author: +# Ani Sinha <anisinha@redhat.com> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from avocado_qemu import QemuSystemTest +import signal +import time + +class MemAddrCheck(QemuSystemTest): + # after launch, in order to generate the logs from QEMU we need to + # wait for some time. Launching and then immediately shutting down + # the VM generates empty logs. A delay of 1 second is added for + # this reason. + DELAY_Q35_BOOT_SEQUENCE = 1 + + # first, lets test some 32-bit processors. + # for all 32-bit cases, pci64_hole_size is 0. + def test_phybits_low_pse36(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + With pse36 feature ON, a processor has 36 bits of addressing. So it can + access up to a maximum of 64GiB of memory. Memory hotplug region begins + at 4 GiB boundary when "above_4g_mem_size" is 0 (this would be true when + we have 0.5 GiB of VM memory, see pc_q35_init()). This means total + hotpluggable memory size is 60 GiB. Per slot, we reserve 1 GiB of memory + for dimm alignment for all newer machines (see enforce_aligned_dimm + property for pc machines and pc_get_device_memory_range()). That leaves + total hotpluggable actual memory size of 59 GiB. If the VM is started + with 0.5 GiB of memory, maxmem should be set to a maximum value of + 59.5 GiB to ensure that the processor can address all memory directly. + Note that 64-bit pci hole size is 0 in this case. If maxmem is set to + 59.6G, QEMU should fail to start with a message "phy-bits are too low". + If maxmem is set to 59.5G with all other QEMU parameters identical, QEMU + should start fine. + """ + self.vm.add_args('-S', '-machine', 'q35', '-m', + '512,slots=1,maxmem=59.6G', + '-cpu', 'pentium,pse36=on', '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_low_pae(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + With pae feature ON, a processor has 36 bits of addressing. So it can + access up to a maximum of 64GiB of memory. Rest is the same as the case + with pse36 above. + """ + self.vm.add_args('-S', '-machine', 'q35', '-m', + '512,slots=1,maxmem=59.6G', + '-cpu', 'pentium,pae=on', '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_pentium_pse36(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Setting maxmem to 59.5G and making sure that QEMU can start with the + same options as the failing case above with pse36 cpu feature. + """ + self.vm.add_args('-machine', 'q35', '-m', + '512,slots=1,maxmem=59.5G', + '-cpu', 'pentium,pse36=on', '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_pentium_pae(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Test is same as above but now with pae cpu feature turned on. + Setting maxmem to 59.5G and making sure that QEMU can start fine + with the same options as the case above. + """ + self.vm.add_args('-machine', 'q35', '-m', + '512,slots=1,maxmem=59.5G', + '-cpu', 'pentium,pae=on', '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_pentium2(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Pentium2 has 36 bits of addressing, so its same as pentium + with pse36 ON. + """ + self.vm.add_args('-machine', 'q35', '-m', + '512,slots=1,maxmem=59.5G', + '-cpu', 'pentium2', '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_low_nonpse36(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Pentium processor has 32 bits of addressing without pse36 or pae + so it can access physical address up to 4 GiB. Setting maxmem to + 4 GiB should make QEMU fail to start with "phys-bits too low" + message because the region for memory hotplug is always placed + above 4 GiB due to the PCI hole and simplicity. + """ + self.vm.add_args('-S', '-machine', 'q35', '-m', + '512,slots=1,maxmem=4G', + '-cpu', 'pentium', '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + # now lets test some 64-bit CPU cases. + def test_phybits_low_tcg_q35_70_amd(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + For q35 7.1 machines and above, there is a HT window that starts at + 1024 GiB and ends at 1 TiB - 1. If the max GPA falls in this range, + "above_4G" memory is adjusted to start at 1 TiB boundary for AMD cpus + in the default case. Lets test without that case for machines 7.0. + For q35-7.0 machines, "above 4G" memory starts are 4G. + pci64_hole size is 32 GiB. Since TCG_PHYS_ADDR_BITS is defined to + be 40, TCG emulated CPUs have maximum of 1 TiB (1024 GiB) of + directly addressable memory. + Hence, maxmem value at most can be + 1024 GiB - 4 GiB - 1 GiB per slot for alignment - 32 GiB + 0.5 GiB + which is equal to 987.5 GiB. Setting the value to 988 GiB should + make QEMU fail with the error message. + """ + self.vm.add_args('-S', '-machine', 'pc-q35-7.0', '-m', + '512,slots=1,maxmem=988G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_low_tcg_q35_71_amd(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + AMD_HT_START is defined to be at 1012 GiB. So for q35 machines + version > 7.0 and AMD cpus, instead of 1024 GiB limit for 40 bit + processor address space, it has to be 1012 GiB , that is 12 GiB + less than the case above in order to accommodate HT hole. + Make sure QEMU fails when maxmem size is 976 GiB (12 GiB less + than 988 GiB). + """ + self.vm.add_args('-S', '-machine', 'pc-q35-7.1', '-m', + '512,slots=1,maxmem=976G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_tcg_q35_70_amd(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Same as q35-7.0 AMD case except that here we check that QEMU can + successfully start when maxmem is < 988G. + """ + self.vm.add_args('-S', '-machine', 'pc-q35-7.0', '-m', + '512,slots=1,maxmem=987.5G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_tcg_q35_71_amd(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Same as q35-7.1 AMD case except that here we check that QEMU can + successfully start when maxmem is < 976G. + """ + self.vm.add_args('-S', '-machine', 'pc-q35-7.1', '-m', + '512,slots=1,maxmem=975.5G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_tcg_q35_71_intel(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Same parameters as test_phybits_low_tcg_q35_71_amd() but use + Intel cpu instead. QEMU should start fine in this case as + "above_4G" memory starts at 4G. + """ + self.vm.add_args('-S', '-cpu', 'Skylake-Server', + '-machine', 'pc-q35-7.1', '-m', + '512,slots=1,maxmem=976G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_low_tcg_q35_71_amd_41bits(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + AMD processor with 41 bits. Max cpu hw address = 2 TiB. + By setting maxram above 1012 GiB - 32 GiB - 4 GiB = 976 GiB, we can + force "above_4G" memory to start at 1 TiB for q35-7.1 machines + (max GPA will be above AMD_HT_START which is defined as 1012 GiB). + + With pci_64_hole size at 32 GiB, in this case, maxmem should be 991.5 + GiB with 1 GiB per slot for alignment and 0.5 GiB as non-hotplug + memory for the VM (1024 - 32 - 1 + 0.5). With 992 GiB, QEMU should + fail to start. + """ + self.vm.add_args('-S', '-cpu', 'EPYC-v4,phys-bits=41', + '-machine', 'pc-q35-7.1', '-m', + '512,slots=1,maxmem=992G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_tcg_q35_71_amd_41bits(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + AMD processor with 41 bits. Max cpu hw address = 2 TiB. + Same as above but by setting maxram between 976 GiB and 992 Gib, + QEMU should start fine. + """ + self.vm.add_args('-S', '-cpu', 'EPYC-v4,phys-bits=41', + '-machine', 'pc-q35-7.1', '-m', + '512,slots=1,maxmem=990G', + '-display', 'none', + '-object', 'memory-backend-ram,id=mem1,size=1G', + '-device', 'pc-dimm,id=vm0,memdev=mem1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_low_tcg_q35_intel_cxl(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + cxl memory window starts after memory device range. Here, we use 1 GiB + of cxl window memory. 4G_mem end aligns at 4G. pci64_hole is 32 GiB and + starts after the cxl memory window. + So maxmem here should be at most 986 GiB considering all memory boundary + alignment constraints with 40 bits (1 TiB) of processor physical bits. + """ + self.vm.add_args('-S', '-cpu', 'Skylake-Server,phys-bits=40', + '-machine', 'q35,cxl=on', '-m', + '512,slots=1,maxmem=987G', + '-display', 'none', + '-device', 'pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1', + '-M', 'cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=1G') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + self.vm.wait() + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") + self.assertRegex(self.vm.get_log(), r'phys-bits too low') + + def test_phybits_ok_tcg_q35_intel_cxl(self): + """ + :avocado: tags=machine:q35 + :avocado: tags=arch:x86_64 + + Same as above but here we do not reserve any cxl memory window. Hence, + with the exact same parameters as above, QEMU should start fine even + with cxl enabled. + """ + self.vm.add_args('-S', '-cpu', 'Skylake-Server,phys-bits=40', + '-machine', 'q35,cxl=on', '-m', + '512,slots=1,maxmem=987G', + '-display', 'none', + '-device', 'pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1') + self.vm.set_qmp_monitor(enabled=False) + self.vm.launch() + time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) + self.vm.shutdown() + self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') diff --git a/tests/avocado/migration.py b/tests/avocado/migration.py new file mode 100644 index 0000000000..be6234b3c2 --- /dev/null +++ b/tests/avocado/migration.py @@ -0,0 +1,135 @@ +# Migration test +# +# Copyright (c) 2019 Red Hat, Inc. +# +# Authors: +# Cleber Rosa <crosa@redhat.com> +# Caio Carrara <ccarrara@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 tempfile +import os + +from avocado_qemu import QemuSystemTest +from avocado import skipUnless + +from avocado.utils.network import ports +from avocado.utils import wait +from avocado.utils.path import find_command + + +class MigrationTest(QemuSystemTest): + """ + :avocado: tags=migration + """ + + timeout = 10 + + @staticmethod + def migration_finished(vm): + return vm.cmd('query-migrate')['status'] in ('completed', 'failed') + + def assert_migration(self, src_vm, dst_vm): + wait.wait_for(self.migration_finished, + timeout=self.timeout, + step=0.1, + args=(src_vm,)) + wait.wait_for(self.migration_finished, + timeout=self.timeout, + step=0.1, + args=(dst_vm,)) + self.assertEqual(src_vm.cmd('query-migrate')['status'], 'completed') + self.assertEqual(dst_vm.cmd('query-migrate')['status'], 'completed') + self.assertEqual(dst_vm.cmd('query-status')['status'], 'running') + self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate') + + def do_migrate(self, dest_uri, src_uri=None): + dest_vm = self.get_vm('-incoming', dest_uri) + dest_vm.add_args('-nodefaults') + dest_vm.launch() + if src_uri is None: + src_uri = dest_uri + source_vm = self.get_vm() + source_vm.add_args('-nodefaults') + source_vm.launch() + source_vm.qmp('migrate', uri=src_uri) + self.assert_migration(source_vm, dest_vm) + + def _get_free_port(self): + port = ports.find_free_port() + if port is None: + self.cancel('Failed to find a free port') + return port + + def migration_with_tcp_localhost(self): + dest_uri = 'tcp:localhost:%u' % self._get_free_port() + self.do_migrate(dest_uri) + + def migration_with_unix(self): + with tempfile.TemporaryDirectory(prefix='socket_') as socket_path: + dest_uri = 'unix:%s/qemu-test.sock' % socket_path + self.do_migrate(dest_uri) + + @skipUnless(find_command('nc', default=False), "'nc' command not found") + def migration_with_exec(self): + """The test works for both netcat-traditional and netcat-openbsd packages.""" + free_port = self._get_free_port() + dest_uri = 'exec:nc -l localhost %u' % free_port + src_uri = 'exec:nc localhost %u' % free_port + self.do_migrate(dest_uri, src_uri) + + +@skipUnless('aarch64' in os.uname()[4], "host != target") +class Aarch64(MigrationTest): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:max + """ + + def test_migration_with_tcp_localhost(self): + self.migration_with_tcp_localhost() + + def test_migration_with_unix(self): + self.migration_with_unix() + + def test_migration_with_exec(self): + self.migration_with_exec() + + +@skipUnless('x86_64' in os.uname()[4], "host != target") +class X86_64(MigrationTest): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:pc + :avocado: tags=cpu:qemu64 + """ + + def test_migration_with_tcp_localhost(self): + self.migration_with_tcp_localhost() + + def test_migration_with_unix(self): + self.migration_with_unix() + + def test_migration_with_exec(self): + self.migration_with_exec() + + +@skipUnless('ppc64le' in os.uname()[4], "host != target") +class PPC64(MigrationTest): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + def test_migration_with_tcp_localhost(self): + self.migration_with_tcp_localhost() + + def test_migration_with_unix(self): + self.migration_with_unix() + + def test_migration_with_exec(self): + self.migration_with_exec() diff --git a/tests/avocado/multiprocess.py b/tests/avocado/multiprocess.py new file mode 100644 index 0000000000..ee7490ae08 --- /dev/null +++ b/tests/avocado/multiprocess.py @@ -0,0 +1,102 @@ +# Test for multiprocess qemu +# +# 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 socket + +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import exec_command +from avocado_qemu import exec_command_and_wait_for_pattern + +class Multiprocess(QemuSystemTest): + """ + :avocado: tags=multiprocess + """ + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + + def do_test(self, kernel_url, kernel_hash, initrd_url, initrd_hash, + kernel_command_line, machine_type): + """Main test method""" + self.require_accelerator('kvm') + self.require_multiprocess() + + # Create socketpair to connect proxy and remote processes + proxy_sock, remote_sock = socket.socketpair(socket.AF_UNIX, + socket.SOCK_STREAM) + os.set_inheritable(proxy_sock.fileno(), True) + os.set_inheritable(remote_sock.fileno(), True) + + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + + # Create remote process + remote_vm = self.get_vm() + remote_vm.add_args('-machine', 'x-remote') + remote_vm.add_args('-nodefaults') + remote_vm.add_args('-device', 'lsi53c895a,id=lsi1') + remote_vm.add_args('-object', 'x-remote-object,id=robj1,' + 'devid=lsi1,fd='+str(remote_sock.fileno())) + remote_vm.launch() + + # Create proxy process + self.vm.set_console() + self.vm.add_args('-machine', machine_type) + self.vm.add_args('-accel', 'kvm') + self.vm.add_args('-cpu', 'host') + self.vm.add_args('-object', + 'memory-backend-memfd,id=sysmem-file,size=2G') + self.vm.add_args('--numa', 'node,memdev=sysmem-file') + self.vm.add_args('-m', '2048') + self.vm.add_args('-kernel', kernel_path, + '-initrd', initrd_path, + '-append', kernel_command_line) + self.vm.add_args('-device', + 'x-pci-proxy-dev,' + 'id=lsi1,fd='+str(proxy_sock.fileno())) + self.vm.launch() + wait_for_console_pattern(self, 'as init process', + 'Kernel panic - not syncing') + exec_command(self, 'mount -t sysfs sysfs /sys') + exec_command_and_wait_for_pattern(self, + 'cat /sys/bus/pci/devices/*/uevent', + 'PCI_ID=1000:0012') + + def test_multiprocess_x86_64(self): + """ + :avocado: tags=arch:x86_64 + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/31/Everything/x86_64/os/images' + '/pxeboot/vmlinuz') + kernel_hash = '5b6f6876e1b5bda314f93893271da0d5777b1f3c' + initrd_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/31/Everything/x86_64/os/images' + '/pxeboot/initrd.img') + initrd_hash = 'dd0340a1b39bd28f88532babd4581c67649ec5b1' + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 rdinit=/bin/bash') + machine_type = 'pc' + self.do_test(kernel_url, kernel_hash, initrd_url, initrd_hash, + kernel_command_line, machine_type) + + def test_multiprocess_aarch64(self): + """ + :avocado: tags=arch:aarch64 + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/31/Everything/aarch64/os/images' + '/pxeboot/vmlinuz') + kernel_hash = '3505f2751e2833c681de78cee8dda1e49cabd2e8' + initrd_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/31/Everything/aarch64/os/images' + '/pxeboot/initrd.img') + initrd_hash = '519a1962daf17d67fc3a9c89d45affcb399607db' + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'rdinit=/bin/bash console=ttyAMA0') + machine_type = 'virt,gic-version=3' + self.do_test(kernel_url, kernel_hash, initrd_url, initrd_hash, + kernel_command_line, machine_type) diff --git a/tests/avocado/netdev-ethtool.py b/tests/avocado/netdev-ethtool.py new file mode 100644 index 0000000000..5f33288f81 --- /dev/null +++ b/tests/avocado/netdev-ethtool.py @@ -0,0 +1,101 @@ +# ethtool tests for emulated network devices +# +# This test leverages ethtool's --test sequence to validate network +# device behaviour. +# +# SPDX-License-Identifier: GPL-2.0-or-late + +from avocado import skip +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class NetDevEthtool(QemuSystemTest): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:q35 + """ + + # Runs in about 17s under KVM, 19s under TCG, 25s under GCOV + timeout = 45 + + # Fetch assets from the netdev-ethtool subdir of my shared test + # images directory on fileserver.linaro.org. + def get_asset(self, name, sha1): + base_url = ('https://fileserver.linaro.org/s/' + 'kE4nCFLdQcoBF9t/download?' + 'path=%2Fnetdev-ethtool&files=' ) + url = base_url + name + # use explicit name rather than failing to neatly parse the + # URL into a unique one + return self.fetch_asset(name=name, locations=(url), asset_hash=sha1) + + def common_test_code(self, netdev, extra_args=None): + + # This custom kernel has drivers for all the supported network + # devices we can emulate in QEMU + kernel = self.get_asset("bzImage", + "33469d7802732d5815226166581442395cb289e2") + + rootfs = self.get_asset("rootfs.squashfs", + "9793cea7021414ae844bda51f558bd6565b50cdc") + + append = 'printk.time=0 console=ttyS0 ' + append += 'root=/dev/sr0 rootfstype=squashfs ' + + # any additional kernel tweaks for the test + if extra_args: + append += extra_args + + # finally invoke ethtool directly + append += ' init=/usr/sbin/ethtool -- -t eth1 offline' + + # add the rootfs via a readonly cdrom image + drive = f"file={rootfs},if=ide,index=0,media=cdrom" + + self.vm.add_args('-kernel', kernel, + '-append', append, + '-drive', drive, + '-device', netdev) + + self.vm.set_console(console_index=0) + self.vm.launch() + + wait_for_console_pattern(self, + "The test result is PASS", + "The test result is FAIL", + vm=None) + # no need to gracefully shutdown, just finish + self.vm.kill() + + def test_igb(self): + """ + :avocado: tags=device:igb + """ + self.common_test_code("igb") + + def test_igb_nomsi(self): + """ + :avocado: tags=device:igb + """ + self.common_test_code("igb", "pci=nomsi") + + # It seems the other popular cards we model in QEMU currently fail + # the pattern test with: + # + # pattern test failed (reg 0x00178): got 0x00000000 expected 0x00005A5A + # + # So for now we skip them. + + @skip("Incomplete reg 0x00178 support") + def test_e1000(self): + """ + :avocado: tags=device:e1000 + """ + self.common_test_code("e1000") + + @skip("Incomplete reg 0x00178 support") + def test_i82550(self): + """ + :avocado: tags=device:i82550 + """ + self.common_test_code("i82550") diff --git a/tests/avocado/pc_cpu_hotplug_props.py b/tests/avocado/pc_cpu_hotplug_props.py new file mode 100644 index 0000000000..4bd3e02665 --- /dev/null +++ b/tests/avocado/pc_cpu_hotplug_props.py @@ -0,0 +1,35 @@ +# +# Ensure CPU die-id can be omitted on -device +# +# Copyright (c) 2019 Red Hat Inc +# +# Author: +# Eduardo Habkost <ehabkost@redhat.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see <http://www.gnu.org/licenses/>. +# + +from avocado_qemu import QemuSystemTest + +class OmittedCPUProps(QemuSystemTest): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=cpu:qemu64 + """ + def test_no_die_id(self): + self.vm.add_args('-nodefaults', '-S') + self.vm.add_args('-smp', '1,sockets=2,cores=2,threads=2,maxcpus=8') + self.vm.add_args('-device', 'qemu64-x86_64-cpu,socket-id=1,core-id=0,thread-id=0') + self.vm.launch() + self.assertEqual(len(self.vm.cmd('query-cpus-fast')), 2) diff --git a/tests/avocado/ppc_405.py b/tests/avocado/ppc_405.py new file mode 100644 index 0000000000..4e7e01aa76 --- /dev/null +++ b/tests/avocado/ppc_405.py @@ -0,0 +1,36 @@ +# Test that the U-Boot firmware boots on ppc 405 machines and check the console +# +# Copyright (c) 2021 Red Hat, Inc. +# +# 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 avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import exec_command_and_wait_for_pattern + +class Ppc405Machine(QemuSystemTest): + + timeout = 90 + + def do_test_ppc405(self): + uboot_url = ('https://gitlab.com/huth/u-boot/-/raw/' + 'taihu-2021-10-09/u-boot-taihu.bin') + uboot_hash = ('3208940e908a5edc7c03eab072c60f0dcfadc2ab'); + file_path = self.fetch_asset(uboot_url, asset_hash=uboot_hash) + self.vm.set_console(console_index=1) + self.vm.add_args('-bios', file_path) + self.vm.launch() + wait_for_console_pattern(self, 'AMCC PPC405EP Evaluation Board') + exec_command_and_wait_for_pattern(self, 'reset', 'AMCC PowerPC 405EP') + + def test_ppc_ref405ep(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:ref405ep + :avocado: tags=cpu:405ep + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.do_test_ppc405() diff --git a/tests/avocado/ppc_74xx.py b/tests/avocado/ppc_74xx.py new file mode 100644 index 0000000000..f54757c243 --- /dev/null +++ b/tests/avocado/ppc_74xx.py @@ -0,0 +1,136 @@ +# Smoke tests for 74xx cpus (aka G4). +# +# Copyright (c) 2021, IBM Corp. +# +# 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 avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class ppc74xxCpu(QemuSystemTest): + """ + :avocado: tags=arch:ppc + :avocado: tags=accel:tcg + """ + timeout = 5 + + def test_ppc_7400(self): + """ + :avocado: tags=cpu:7400 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7410(self): + """ + :avocado: tags=cpu:7410 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,74xx') + + def test_ppc_7441(self): + """ + :avocado: tags=cpu:7441 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7445(self): + """ + :avocado: tags=cpu:7445 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7447(self): + """ + :avocado: tags=cpu:7447 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7447a(self): + """ + :avocado: tags=cpu:7447a + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7448(self): + """ + :avocado: tags=cpu:7448 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,MPC86xx') + + def test_ppc_7450(self): + """ + :avocado: tags=cpu:7450 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7451(self): + """ + :avocado: tags=cpu:7451 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7455(self): + """ + :avocado: tags=cpu:7455 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7457(self): + """ + :avocado: tags=cpu:7457 + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') + + def test_ppc_7457a(self): + """ + :avocado: tags=cpu:7457a + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> CPU type PowerPC,G4') diff --git a/tests/avocado/ppc_amiga.py b/tests/avocado/ppc_amiga.py new file mode 100644 index 0000000000..b6f866f91d --- /dev/null +++ b/tests/avocado/ppc_amiga.py @@ -0,0 +1,38 @@ +# Test AmigaNG boards +# +# Copyright (c) 2023 BALATON Zoltan +# +# 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 avocado.utils import archive +from avocado.utils import process +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class AmigaOneMachine(QemuSystemTest): + + timeout = 90 + + def test_ppc_amigaone(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:amigaone + :avocado: tags=device:articia + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + tar_name = 'A1Firmware_Floppy_05-Mar-2005.zip' + tar_url = ('https://www.hyperion-entertainment.com/index.php/' + 'downloads?view=download&format=raw&file=25') + tar_hash = 'c52e59bc73e31d8bcc3cc2106778f7ac84f6c755' + zip_file = self.fetch_asset(tar_name, locations=tar_url, + asset_hash=tar_hash) + archive.extract(zip_file, self.workdir) + cmd = f"tail -c 524288 {self.workdir}/floppy_edition/updater.image >{self.workdir}/u-boot-amigaone.bin" + process.run(cmd, shell=True) + + self.vm.set_console() + self.vm.add_args('-bios', self.workdir + '/u-boot-amigaone.bin') + self.vm.launch() + wait_for_console_pattern(self, 'FLASH:') diff --git a/tests/avocado/ppc_bamboo.py b/tests/avocado/ppc_bamboo.py new file mode 100644 index 0000000000..a81be3d608 --- /dev/null +++ b/tests/avocado/ppc_bamboo.py @@ -0,0 +1,42 @@ +# Test that Linux kernel boots on the ppc bamboo board and check the console +# +# Copyright (c) 2021 Red Hat +# +# 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 avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import exec_command_and_wait_for_pattern + +class BambooMachine(QemuSystemTest): + + timeout = 90 + + def test_ppc_bamboo(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:bamboo + :avocado: tags=cpu:440epb + :avocado: tags=device:rtl8139 + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.require_netdev('user') + tar_url = ('http://landley.net/aboriginal/downloads/binaries/' + 'system-image-powerpc-440fp.tar.gz') + tar_hash = '53e5f16414b195b82d2c70272f81c2eedb39bad9' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-kernel', self.workdir + + '/system-image-powerpc-440fp/linux', + '-initrd', self.workdir + + '/system-image-powerpc-440fp/rootfs.cpio.gz', + '-nic', 'user,model=rtl8139,restrict=on') + self.vm.launch() + wait_for_console_pattern(self, 'Type exit when done') + exec_command_and_wait_for_pattern(self, 'ping 10.0.2.2', + '10.0.2.2 is alive!') + exec_command_and_wait_for_pattern(self, 'halt', 'System Halted') diff --git a/tests/avocado/ppc_hv_tests.py b/tests/avocado/ppc_hv_tests.py new file mode 100644 index 0000000000..bf8822bb97 --- /dev/null +++ b/tests/avocado/ppc_hv_tests.py @@ -0,0 +1,206 @@ +# Tests that specifically try to exercise hypervisor features of the +# target machines. powernv supports the Power hypervisor ISA, and +# pseries supports the nested-HV hypervisor spec. +# +# Copyright (c) 2023 IBM Corporation +# +# 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 avocado import skipIf, skipUnless +from avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern, exec_command +import os +import time +import subprocess +from datetime import datetime + +deps = ["xorriso"] # dependent tools needed in the test setup/box. + +def which(tool): + """ looks up the full path for @tool, returns None if not found + or if @tool does not have executable permissions. + """ + paths=os.getenv('PATH') + for p in paths.split(os.path.pathsep): + p = os.path.join(p, tool) + if os.path.exists(p) and os.access(p, os.X_OK): + return p + return None + +def missing_deps(): + """ returns True if any of the test dependent tools are absent. + """ + for dep in deps: + if which(dep) is None: + return True + return False + +# Alpine is a light weight distro that supports QEMU. These tests boot +# that on the machine then run a QEMU guest inside it in KVM mode, +# that runs the same Alpine distro image. +# QEMU packages are downloaded and installed on each test. That's not a +# large download, but it may be more polite to create qcow2 image with +# QEMU already installed and use that. +# XXX: The order of these tests seems to matter, see git blame. +@skipIf(missing_deps(), 'dependencies (%s) not installed' % ','.join(deps)) +@skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test sometimes gets stuck due to console handling problem') +@skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited') +@skipUnless(os.getenv('SPEED') == 'slow', 'runtime limited') +class HypervisorTest(QemuSystemTest): + + timeout = 1000 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 console=hvc0 ' + panic_message = 'Kernel panic - not syncing' + good_message = 'VFS: Cannot open root device' + + def extract_from_iso(self, iso, path): + """ + Extracts a file from an iso file into the test workdir + + :param iso: path to the iso file + :param path: path within the iso file of the file to be extracted + :returns: path of the extracted file + """ + filename = os.path.basename(path) + + cwd = os.getcwd() + os.chdir(self.workdir) + + with open(filename, "w") as outfile: + cmd = "xorriso -osirrox on -indev %s -cpx %s %s" % (iso, path, filename) + subprocess.run(cmd.split(), + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + os.chdir(cwd) + + # Return complete path to extracted file. Because callers to + # extract_from_iso() specify 'path' with a leading slash, it is + # necessary to use os.path.relpath() as otherwise os.path.join() + # interprets it as an absolute path and drops the self.workdir part. + return os.path.normpath(os.path.join(self.workdir, filename)) + + def setUp(self): + super().setUp() + + iso_url = ('https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/ppc64le/alpine-standard-3.18.4-ppc64le.iso') + + # Alpine use sha256 so I recalculated this myself + iso_sha256 = 'c26b8d3e17c2f3f0fed02b4b1296589c2390e6d5548610099af75300edd7b3ff' + iso_path = self.fetch_asset(iso_url, asset_hash=iso_sha256, + algorithm = "sha256") + + self.iso_path = iso_path + self.vmlinuz = self.extract_from_iso(iso_path, '/boot/vmlinuz-lts') + self.initramfs = self.extract_from_iso(iso_path, '/boot/initramfs-lts') + + def do_start_alpine(self): + self.vm.set_console() + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + self.vm.add_args("-kernel", self.vmlinuz) + self.vm.add_args("-initrd", self.initramfs) + self.vm.add_args("-smp", "4", "-m", "2g") + self.vm.add_args("-drive", f"file={self.iso_path},format=raw,if=none,id=drive0") + + self.vm.launch() + wait_for_console_pattern(self, 'Welcome to Alpine Linux 3.18') + exec_command(self, 'root') + wait_for_console_pattern(self, 'localhost login:') + wait_for_console_pattern(self, 'You may change this message by editing /etc/motd.') + # If the time is wrong, SSL certificates can fail. + exec_command(self, 'date -s "' + datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S' + '"')) + exec_command(self, 'setup-alpine -qe') + wait_for_console_pattern(self, 'Updating repository indexes... done.') + + def do_stop_alpine(self): + exec_command(self, 'poweroff') + wait_for_console_pattern(self, 'alpine:~#') + self.vm.wait() + + def do_setup_kvm(self): + exec_command(self, 'echo http://dl-cdn.alpinelinux.org/alpine/v3.18/main > /etc/apk/repositories') + wait_for_console_pattern(self, 'alpine:~#') + exec_command(self, 'echo http://dl-cdn.alpinelinux.org/alpine/v3.18/community >> /etc/apk/repositories') + wait_for_console_pattern(self, 'alpine:~#') + exec_command(self, 'apk update') + wait_for_console_pattern(self, 'alpine:~#') + exec_command(self, 'apk add qemu-system-ppc64') + wait_for_console_pattern(self, 'alpine:~#') + exec_command(self, 'modprobe kvm-hv') + wait_for_console_pattern(self, 'alpine:~#') + + # This uses the host's block device as the source file for guest block + # device for install media. This is a bit hacky but allows reuse of the + # iso without having a passthrough filesystem configured. + def do_test_kvm(self, hpt=False): + if hpt: + append = 'disable_radix' + else: + append = '' + exec_command(self, 'qemu-system-ppc64 -nographic -smp 2 -m 1g ' + '-machine pseries,x-vof=on,accel=kvm ' + '-machine cap-cfpc=broken,cap-sbbc=broken,' + 'cap-ibs=broken,cap-ccf-assist=off ' + '-drive file=/dev/nvme0n1,format=raw,readonly=on ' + '-initrd /media/nvme0n1/boot/initramfs-lts ' + '-kernel /media/nvme0n1/boot/vmlinuz-lts ' + '-append \'usbcore.nousb ' + append + '\'') + # Alpine 3.18 kernel seems to crash in XHCI USB driver. + wait_for_console_pattern(self, 'Welcome to Alpine Linux 3.18') + exec_command(self, 'root') + wait_for_console_pattern(self, 'localhost login:') + wait_for_console_pattern(self, 'You may change this message by editing /etc/motd.') + exec_command(self, 'poweroff >& /dev/null') + wait_for_console_pattern(self, 'localhost:~#') + wait_for_console_pattern(self, 'reboot: Power down') + time.sleep(1) + exec_command(self, '') + wait_for_console_pattern(self, 'alpine:~#') + + def test_hv_pseries(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.add_args("-accel", "tcg,thread=multi") + self.vm.add_args('-device', 'nvme,serial=1234,drive=drive0') + self.vm.add_args("-machine", "x-vof=on,cap-nested-hv=on") + self.do_start_alpine() + self.do_setup_kvm() + self.do_test_kvm() + self.do_stop_alpine() + + def test_hv_pseries_kvm(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + :avocado: tags=accel:kvm + """ + self.require_accelerator("kvm") + self.vm.add_args("-accel", "kvm") + self.vm.add_args('-device', 'nvme,serial=1234,drive=drive0') + self.vm.add_args("-machine", "x-vof=on,cap-nested-hv=on,cap-ccf-assist=off") + self.do_start_alpine() + self.do_setup_kvm() + self.do_test_kvm() + self.do_stop_alpine() + + def test_hv_powernv(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.add_args("-accel", "tcg,thread=multi") + self.vm.add_args('-device', 'nvme,bus=pcie.2,addr=0x0,serial=1234,drive=drive0', + '-device', 'e1000e,netdev=net0,mac=C0:FF:EE:00:00:02,bus=pcie.0,addr=0x0', + '-netdev', 'user,id=net0,hostfwd=::20022-:22,hostname=alpine') + self.do_start_alpine() + self.do_setup_kvm() + self.do_test_kvm() + self.do_test_kvm(True) + self.do_stop_alpine() diff --git a/tests/avocado/ppc_mpc8544ds.py b/tests/avocado/ppc_mpc8544ds.py new file mode 100644 index 0000000000..b599fb1cc9 --- /dev/null +++ b/tests/avocado/ppc_mpc8544ds.py @@ -0,0 +1,34 @@ +# Test that Linux kernel boots on ppc machines and check the console +# +# Copyright (c) 2018, 2020 Red Hat, Inc. +# +# 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 avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class Mpc8544dsMachine(QemuSystemTest): + + timeout = 90 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + panic_message = 'Kernel panic - not syncing' + + def test_ppc_mpc8544ds(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:mpc8544ds + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day04.tar.xz') + tar_hash = 'f46724d281a9f30fa892d458be7beb7d34dc25f9' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-kernel', self.workdir + '/creek/creek.bin') + self.vm.launch() + wait_for_console_pattern(self, 'QEMU advent calendar 2020', + self.panic_message) diff --git a/tests/avocado/ppc_powernv.py b/tests/avocado/ppc_powernv.py new file mode 100644 index 0000000000..4342941d5d --- /dev/null +++ b/tests/avocado/ppc_powernv.py @@ -0,0 +1,102 @@ +# Test that Linux kernel boots on ppc powernv machines and check the console +# +# Copyright (c) 2018, 2020 Red Hat, Inc. +# +# 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 avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class powernvMachine(QemuSystemTest): + + timeout = 90 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 console=hvc0 ' + panic_message = 'Kernel panic - not syncing' + good_message = 'VFS: Cannot open root device' + + def do_test_linux_boot(self, command_line = KERNEL_COMMON_COMMAND_LINE): + self.require_accelerator("tcg") + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/29/Everything/ppc64le/os' + '/ppc/ppc64/vmlinuz') + kernel_hash = '3fe04abfc852b66653b8c3c897a59a689270bc77' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path, + '-append', command_line) + self.vm.launch() + + def test_linux_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + + self.do_test_linux_boot() + console_pattern = 'VFS: Cannot open root device' + wait_for_console_pattern(self, console_pattern, self.panic_message) + + def test_linux_smp_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + + self.vm.add_args('-smp', '4') + self.do_test_linux_boot() + console_pattern = 'smp: Brought up 1 node, 4 CPUs' + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) + + def test_linux_smp_hpt_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + + self.vm.add_args('-smp', '4') + self.do_test_linux_boot(self.KERNEL_COMMON_COMMAND_LINE + + 'disable_radix') + console_pattern = 'smp: Brought up 1 node, 4 CPUs' + wait_for_console_pattern(self, 'hash-mmu: Initializing hash mmu', + self.panic_message) + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) + + def test_linux_smt_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + + self.vm.add_args('-smp', '4,threads=4') + self.do_test_linux_boot() + console_pattern = 'CPU maps initialized for 4 threads per core' + wait_for_console_pattern(self, console_pattern, self.panic_message) + console_pattern = 'smp: Brought up 1 node, 4 CPUs' + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) + + def test_linux_big_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + + self.vm.add_args('-smp', '16,threads=4,cores=2,sockets=2') + + # powernv does not support NUMA + self.do_test_linux_boot() + console_pattern = 'CPU maps initialized for 4 threads per core' + wait_for_console_pattern(self, console_pattern, self.panic_message) + console_pattern = 'smp: Brought up 2 nodes, 16 CPUs' + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) diff --git a/tests/avocado/ppc_prep_40p.py b/tests/avocado/ppc_prep_40p.py new file mode 100644 index 0000000000..d4f1eb7e1d --- /dev/null +++ b/tests/avocado/ppc_prep_40p.py @@ -0,0 +1,85 @@ +# Functional test that boots a PReP/40p machine and checks its serial console. +# +# Copyright (c) Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# 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 + +from avocado import skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + + +class IbmPrep40pMachine(QemuSystemTest): + + timeout = 60 + + # 12H0455 PPS Firmware Licensed Materials + # Property of IBM (C) Copyright IBM Corp. 1994. + # All rights reserved. + # U.S. Government Users Restricted Rights - Use, duplication or disclosure + # restricted by GSA ADP Schedule Contract with IBM Corp. + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_factory_firmware_and_netbsd(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:40p + :avocado: tags=os:netbsd + :avocado: tags=slowness:high + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + bios_url = ('http://ftpmirror.your.org/pub/misc/' + 'ftp.software.ibm.com/rs6000/firmware/' + '7020-40p/P12H0456.IMG') + bios_hash = '1775face4e6dc27f3a6ed955ef6eb331bf817f03' + bios_path = self.fetch_asset(bios_url, asset_hash=bios_hash) + drive_url = ('https://archive.netbsd.org/pub/NetBSD-archive/' + 'NetBSD-4.0/prep/installation/floppy/generic_com0.fs') + drive_hash = 'dbcfc09912e71bd5f0d82c7c1ee43082fb596ceb' + drive_path = self.fetch_asset(drive_url, asset_hash=drive_hash) + + self.vm.set_console() + self.vm.add_args('-bios', bios_path, + '-fda', drive_path) + self.vm.launch() + os_banner = 'NetBSD 4.0 (GENERIC) #0: Sun Dec 16 00:49:40 PST 2007' + wait_for_console_pattern(self, os_banner) + wait_for_console_pattern(self, 'Model: IBM PPS Model 6015') + + def test_openbios_192m(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:40p + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + self.vm.set_console() + self.vm.add_args('-m', '192') # test fw_cfg + + self.vm.launch() + wait_for_console_pattern(self, '>> OpenBIOS') + wait_for_console_pattern(self, '>> Memory: 192M') + wait_for_console_pattern(self, '>> CPU type PowerPC,604') + + def test_openbios_and_netbsd(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:40p + :avocado: tags=os:netbsd + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + drive_url = ('https://archive.netbsd.org/pub/NetBSD-archive/' + 'NetBSD-7.1.2/iso/NetBSD-7.1.2-prep.iso') + drive_hash = 'ac6fa2707d888b36d6fa64de6e7fe48e' + drive_path = self.fetch_asset(drive_url, asset_hash=drive_hash, + algorithm='md5') + self.vm.set_console() + self.vm.add_args('-cdrom', drive_path, + '-boot', 'd') + + self.vm.launch() + wait_for_console_pattern(self, 'NetBSD/prep BOOT, Revision 1.9') diff --git a/tests/avocado/ppc_pseries.py b/tests/avocado/ppc_pseries.py new file mode 100644 index 0000000000..74aaa4ac4a --- /dev/null +++ b/tests/avocado/ppc_pseries.py @@ -0,0 +1,110 @@ +# Test that Linux kernel boots on ppc machines and check the console +# +# Copyright (c) 2018, 2020 Red Hat, Inc. +# +# 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 avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class pseriesMachine(QemuSystemTest): + + timeout = 90 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 console=hvc0 ' + panic_message = 'Kernel panic - not syncing' + good_message = 'VFS: Cannot open root device' + + def do_test_ppc64_linux_boot(self, kernel_command_line = KERNEL_COMMON_COMMAND_LINE): + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/29/Everything/ppc64le/os' + '/ppc/ppc64/vmlinuz') + kernel_hash = '3fe04abfc852b66653b8c3c897a59a689270bc77' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.vm.set_console() + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + + def test_ppc64_vof_linux_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + self.vm.add_args('-machine', 'x-vof=on') + self.do_test_ppc64_linux_boot() + console_pattern = 'VFS: Cannot open root device' + wait_for_console_pattern(self, console_pattern, self.panic_message) + + def test_ppc64_linux_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + self.do_test_ppc64_linux_boot() + console_pattern = 'VFS: Cannot open root device' + wait_for_console_pattern(self, console_pattern, self.panic_message) + + def test_ppc64_linux_smp_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + self.vm.add_args('-smp', '4') + self.do_test_ppc64_linux_boot() + console_pattern = 'smp: Brought up 1 node, 4 CPUs' + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) + + def test_ppc64_linux_hpt_smp_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + self.vm.add_args('-smp', '4') + self.do_test_ppc64_linux_boot(self.KERNEL_COMMON_COMMAND_LINE + + 'disable_radix') + console_pattern = 'smp: Brought up 1 node, 4 CPUs' + wait_for_console_pattern(self, 'hash-mmu: Initializing hash mmu', + self.panic_message) + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) + + def test_ppc64_linux_smt_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + self.vm.add_args('-smp', '4,threads=4') + self.do_test_ppc64_linux_boot() + console_pattern = 'CPU maps initialized for 4 threads per core' + wait_for_console_pattern(self, console_pattern, self.panic_message) + console_pattern = 'smp: Brought up 1 node, 4 CPUs' + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) + + def test_ppc64_linux_big_boot(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + """ + + self.vm.add_args('-smp', '16,threads=4,cores=2,sockets=2') + self.vm.add_args('-m', '512M', + '-object', 'memory-backend-ram,size=256M,id=m0', + '-object', 'memory-backend-ram,size=256M,id=m1') + self.vm.add_args('-numa', 'node,nodeid=0,memdev=m0') + self.vm.add_args('-numa', 'node,nodeid=1,memdev=m1') + self.do_test_ppc64_linux_boot() + console_pattern = 'CPU maps initialized for 4 threads per core' + wait_for_console_pattern(self, console_pattern, self.panic_message) + console_pattern = 'smp: Brought up 2 nodes, 16 CPUs' + wait_for_console_pattern(self, console_pattern, self.panic_message) + wait_for_console_pattern(self, self.good_message, self.panic_message) diff --git a/tests/avocado/ppc_virtex_ml507.py b/tests/avocado/ppc_virtex_ml507.py new file mode 100644 index 0000000000..a73f8ae396 --- /dev/null +++ b/tests/avocado/ppc_virtex_ml507.py @@ -0,0 +1,36 @@ +# Test that Linux kernel boots on ppc machines and check the console +# +# Copyright (c) 2018, 2020 Red Hat, Inc. +# +# 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 avocado.utils import archive +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class VirtexMl507Machine(QemuSystemTest): + + timeout = 90 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' + panic_message = 'Kernel panic - not syncing' + + def test_ppc_virtex_ml507(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:virtex-ml507 + :avocado: tags=accel:tcg + """ + self.require_accelerator("tcg") + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day08.tar.xz') + tar_hash = '74c68f5af7a7b8f21c03097b298f3bb77ff52c1f' + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + archive.extract(file_path, self.workdir) + self.vm.set_console() + self.vm.add_args('-kernel', self.workdir + '/hippo/hippo.linux', + '-dtb', self.workdir + '/hippo/virtex440-ml507.dtb', + '-m', '512') + self.vm.launch() + wait_for_console_pattern(self, 'QEMU advent calendar 2020', + self.panic_message) diff --git a/tests/avocado/replay_kernel.py b/tests/avocado/replay_kernel.py new file mode 100644 index 0000000000..2c81412dba --- /dev/null +++ b/tests/avocado/replay_kernel.py @@ -0,0 +1,550 @@ +# Record/replay test that boots a Linux kernel +# +# Copyright (c) 2020 ISP RAS +# +# Author: +# Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> +# +# 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 lzma +import shutil +import logging +import time + +from avocado import skip +from avocado import skipUnless +from avocado import skipUnless +from avocado_qemu import wait_for_console_pattern +from avocado.utils import archive +from avocado.utils import process +from boot_linux_console import LinuxKernelTest + +class ReplayKernelBase(LinuxKernelTest): + """ + Boots a Linux kernel in record mode and checks that the console + is operational and the kernel command line is properly passed + from QEMU to the kernel. + Then replays the same scenario and verifies, that QEMU correctly + terminates. + """ + + timeout = 120 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=1 panic=-1 ' + + def run_vm(self, kernel_path, kernel_command_line, console_pattern, + record, shift, args, replay_path): + # icount requires TCG to be available + self.require_accelerator('tcg') + + logger = logging.getLogger('replay') + start_time = time.time() + vm = self.get_vm() + vm.set_console() + if record: + logger.info('recording the execution...') + mode = 'record' + else: + logger.info('replaying the execution...') + mode = 'replay' + vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s' % + (shift, mode, replay_path), + '-kernel', kernel_path, + '-append', kernel_command_line, + '-net', 'none', + '-no-reboot') + if args: + vm.add_args(*args) + vm.launch() + self.wait_for_console_pattern(console_pattern, vm) + if record: + vm.shutdown() + logger.info('finished the recording with log size %s bytes' + % os.path.getsize(replay_path)) + else: + vm.wait() + logger.info('successfully finished the replay') + elapsed = time.time() - start_time + logger.info('elapsed time %.2f sec' % elapsed) + return elapsed + + def run_rr(self, kernel_path, kernel_command_line, console_pattern, + shift=7, args=None): + replay_path = os.path.join(self.workdir, 'replay.bin') + t1 = self.run_vm(kernel_path, kernel_command_line, console_pattern, + True, shift, args, replay_path) + t2 = self.run_vm(kernel_path, kernel_command_line, console_pattern, + False, shift, args, replay_path) + logger = logging.getLogger('replay') + logger.info('replay overhead {:.2%}'.format(t2 / t1 - 1)) + +class ReplayKernelNormal(ReplayKernelBase): + + def test_i386_pc(self): + """ + :avocado: tags=arch:i386 + :avocado: tags=machine:pc + """ + kernel_url = ('https://storage.tuxboot.com/20230331/i386/bzImage') + kernel_hash = 'a3e5b32a354729e65910f5a1ffcda7c14a6c12a55e8213fb86e277f1b76ed956' + kernel_path = self.fetch_asset(kernel_url, + asset_hash=kernel_hash, + algorithm = "sha256") + + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'VFS: Cannot open root device' + + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + # See https://gitlab.com/qemu-project/qemu/-/issues/2094 + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test sometimes gets stuck') + def test_x86_64_pc(self): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:pc + :avocado: tags=flaky + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/29/Everything/x86_64/os/images/pxeboot' + '/vmlinuz') + kernel_hash = '23bebd2680757891cf7adedb033532163a792495' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'VFS: Cannot open root device' + + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + def test_mips_malta(self): + """ + :avocado: tags=arch:mips + :avocado: tags=machine:malta + :avocado: tags=endian:big + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20130217T032700Z/pool/main/l/linux-2.6/' + 'linux-image-2.6.32-5-4kc-malta_2.6.32-48_mips.deb') + deb_hash = 'a8cfc28ad8f45f54811fc6cf74fc43ffcfe0ba04' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-2.6.32-5-4kc-malta') + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + def test_mips64el_malta(self): + """ + This test requires the ar tool to extract "data.tar.gz" from + the Debian package. + + The kernel can be rebuilt using this Debian kernel source [1] and + following the instructions on [2]. + + [1] http://snapshot.debian.org/package/linux-2.6/2.6.32-48/ + #linux-source-2.6.32_2.6.32-48 + [2] https://kernel-team.pages.debian.net/kernel-handbook/ + ch-common-tasks.html#s-common-official + + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20130217T032700Z/pool/main/l/linux-2.6/' + 'linux-image-2.6.32-5-5kc-malta_2.6.32-48_mipsel.deb') + deb_hash = '1aaec92083bf22fda31e0d27fa8d9a388e5fc3d5' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-2.6.32-5-5kc-malta') + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + def test_aarch64_virt(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:cortex-a53 + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/29/Everything/aarch64/os/images/pxeboot' + '/vmlinuz') + kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'VFS: Cannot open root device' + + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + def test_arm_virt(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:virt + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/29/Everything/armhfp/os/images/pxeboot' + '/vmlinuz') + kernel_hash = 'e9826d741b4fb04cadba8d4824d1ed3b7fb8b4d4' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'VFS: Cannot open root device' + + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=1) + + def test_arm_cubieboard_initrd(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:cubieboard + """ + deb_url = ('https://apt.armbian.com/pool/main/l/' + 'linux-5.10.16-sunxi/linux-image-current-sunxi_21.02.2_armhf.deb') + deb_hash = '9fa84beda245cabf0b4fa84cf6eaa7738ead1da0' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinuz-5.10.16-sunxi') + dtb_path = '/usr/lib/linux-image-current-sunxi/sun4i-a10-cubieboard.dtb' + dtb_path = self.extract_from_deb(deb_path, dtb_path) + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/' + 'arm/rootfs-armv5.cpio.gz') + initrd_hash = '2b50f1873e113523967806f4da2afe385462ff9b' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'rootfs.cpio') + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0,115200 ' + 'usbcore.nousb ' + 'panic=-1 noreboot') + console_pattern = 'Boot successful.' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=1, + args=('-dtb', dtb_path, + '-initrd', initrd_path, + '-no-reboot')) + + def test_s390x_s390_ccw_virtio(self): + """ + :avocado: tags=arch:s390x + :avocado: tags=machine:s390-ccw-virtio + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/29/Everything/s390x/os/images' + '/kernel.img') + kernel_hash = 'e8e8439103ef8053418ef062644ffd46a7919313' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + 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) + + def test_alpha_clipper(self): + """ + :avocado: tags=arch:alpha + :avocado: tags=machine:clipper + """ + kernel_url = ('http://archive.debian.org/debian/dists/lenny/main/' + 'installer-alpha/20090123lenny10/images/cdrom/vmlinuz') + kernel_hash = '3a943149335529e2ed3e74d0d787b85fb5671ba3' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + uncompressed_kernel = archive.uncompress(kernel_path, self.workdir) + + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=ttyS0' + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(uncompressed_kernel, kernel_command_line, console_pattern, shift=9, + args=('-nodefaults', )) + + def test_ppc64_pseries(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + :avocado: tags=accel:tcg + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/29/Everything/ppc64le/os' + '/ppc/ppc64/vmlinuz') + kernel_hash = '3fe04abfc852b66653b8c3c897a59a689270bc77' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'console=hvc0' + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + def test_ppc64_powernv(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=accel:tcg + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/29/Everything/ppc64le/os' + '/ppc/ppc64/vmlinuz') + kernel_hash = '3fe04abfc852b66653b8c3c897a59a689270bc77' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + \ + 'console=tty0 console=hvc0' + console_pattern = 'VFS: Cannot open root device' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + def test_m68k_q800(self): + """ + :avocado: tags=arch:m68k + :avocado: tags=machine:q800 + """ + deb_url = ('https://snapshot.debian.org/archive/debian-ports' + '/20191021T083923Z/pool-m68k/main' + '/l/linux/kernel-image-5.3.0-1-m68k-di_5.3.7-1_m68k.udeb') + deb_hash = '044954bb9be4160a3ce81f8bc1b5e856b75cccd1' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-5.3.0-1-m68k') + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 vga=off') + console_pattern = 'No filesystem could mount root' + self.run_rr(kernel_path, kernel_command_line, console_pattern) + + def do_test_advcal_2018(self, file_path, kernel_name, args=None): + archive.extract(file_path, self.workdir) + + for entry in os.scandir(self.workdir): + if entry.name.startswith('day') and entry.is_dir(): + kernel_path = os.path.join(entry.path, kernel_name) + break + + kernel_command_line = '' + console_pattern = 'QEMU advent calendar' + self.run_rr(kernel_path, kernel_command_line, console_pattern, + args=args) + + def test_arm_vexpressa9(self): + """ + :avocado: tags=arch:arm + :avocado: tags=machine:vexpress-a9 + """ + tar_hash = '32b7677ce8b6f1471fb0059865f451169934245b' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day16.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + dtb_path = self.workdir + '/day16/vexpress-v2p-ca9.dtb' + self.do_test_advcal_2018(file_path, 'winter.zImage', + args=('-dtb', dtb_path)) + + def test_m68k_mcf5208evb(self): + """ + :avocado: tags=arch:m68k + :avocado: tags=machine:mcf5208evb + """ + tar_hash = 'ac688fd00561a2b6ce1359f9ff6aa2b98c9a570c' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day07.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'sanity-clause.elf') + + def test_microblaze_s3adsp1800(self): + """ + :avocado: tags=arch:microblaze + :avocado: tags=machine:petalogix-s3adsp1800 + """ + tar_hash = '08bf3e3bfb6b6c7ce1e54ab65d54e189f2caf13f' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day17.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'ballerina.bin') + + def test_ppc64_e500(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:ppce500 + :avocado: tags=cpu:e5500 + """ + tar_hash = '6951d86d644b302898da2fd701739c9406527fe1' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day19.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'uImage') + + def test_or1k_sim(self): + """ + :avocado: tags=arch:or1k + :avocado: tags=machine:or1k-sim + """ + tar_hash = '20334cdaf386108c530ff0badaecc955693027dd' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day20.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'vmlinux') + + def test_ppc_g3beige(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:g3beige + """ + tar_hash = 'e0b872a5eb8fdc5bed19bd43ffe863900ebcedfc' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day15.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'invaders.elf', + args=('-M', 'graphics=off')) + + def test_ppc_mac99(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:mac99 + """ + tar_hash = 'e0b872a5eb8fdc5bed19bd43ffe863900ebcedfc' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day15.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'invaders.elf', + args=('-M', 'graphics=off')) + + def test_sparc_ss20(self): + """ + :avocado: tags=arch:sparc + :avocado: tags=machine:SS-20 + """ + tar_hash = 'b18550d5d61c7615d989a06edace051017726a9f' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day11.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'zImage.elf') + + def test_xtensa_lx60(self): + """ + :avocado: tags=arch:xtensa + :avocado: tags=machine:lx60 + :avocado: tags=cpu:dc233c + """ + tar_hash = '49e88d9933742f0164b60839886c9739cb7a0d34' + tar_url = ('https://qemu-advcal.gitlab.io' + '/qac-best-of-multiarch/download/day02.tar.xz') + file_path = self.fetch_asset(tar_url, asset_hash=tar_hash) + self.do_test_advcal_2018(file_path, 'santas-sleigh-ride.elf') + +@skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') +class ReplayKernelSlow(ReplayKernelBase): + # Override the timeout, because this kernel includes an inner + # loop which is executed with TB recompilings during replay, + # making it very slow. + timeout = 180 + + def test_mips_malta_cpio(self): + """ + :avocado: tags=arch:mips + :avocado: tags=machine:malta + :avocado: tags=endian:big + :avocado: tags=slowness:high + """ + deb_url = ('http://snapshot.debian.org/archive/debian/' + '20160601T041800Z/pool/main/l/linux/' + 'linux-image-4.5.0-2-4kc-malta_4.5.5-1_mips.deb') + deb_hash = 'a3c84f3e88b54e06107d65a410d1d1e8e0f340f8' + deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash) + kernel_path = self.extract_from_deb(deb_path, + '/boot/vmlinux-4.5.0-2-4kc-malta') + initrd_url = ('https://github.com/groeck/linux-build-test/raw/' + '8584a59ed9e5eb5ee7ca91f6d74bbb06619205b8/rootfs/' + 'mips/rootfs.cpio.gz') + initrd_hash = 'bf806e17009360a866bf537f6de66590de349a99' + initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash) + initrd_path = self.workdir + "rootfs.cpio" + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 console=tty ' + 'rdinit=/sbin/init noreboot') + console_pattern = 'Boot successful.' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5, + args=('-initrd', initrd_path)) + + @skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code') + def test_mips64el_malta_5KEc_cpio(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=slowness:high + :avocado: tags=cpu:5KEc + """ + kernel_url = ('https://github.com/philmd/qemu-testing-blob/' + 'raw/9ad2df38/mips/malta/mips64el/' + 'vmlinux-3.19.3.mtoman.20150408') + kernel_hash = '00d1d268fb9f7d8beda1de6bebcc46e884d71754' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + initrd_url = ('https://github.com/groeck/linux-build-test/' + 'raw/8584a59e/rootfs/' + 'mipsel64/rootfs.mipsel64r1.cpio.gz') + initrd_hash = '1dbb8a396e916847325284dbe2151167' + initrd_path_gz = self.fetch_asset(initrd_url, algorithm='md5', + asset_hash=initrd_hash) + initrd_path = self.workdir + "rootfs.cpio" + archive.gzip_uncompress(initrd_path_gz, initrd_path) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyS0 console=tty ' + 'rdinit=/sbin/init noreboot') + console_pattern = 'Boot successful.' + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5, + args=('-initrd', initrd_path)) + + def do_test_mips_malta32el_nanomips(self, kernel_path_xz): + kernel_path = self.workdir + "kernel" + with lzma.open(kernel_path_xz, 'rb') as f_in: + with open(kernel_path, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'mem=256m@@0x0 ' + 'console=ttyS0') + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.run_rr(kernel_path, kernel_command_line, console_pattern, shift=5) + + def test_mips_malta32el_nanomips_4k(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:I7200 + """ + kernel_url = ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page4k.xz') + kernel_hash = '477456aafd2a0f1ddc9482727f20fe9575565dd6' + kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + self.do_test_mips_malta32el_nanomips(kernel_path_xz) + + def test_mips_malta32el_nanomips_16k_up(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:I7200 + """ + kernel_url = ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page16k_up.xz') + kernel_hash = 'e882868f944c71c816e832e2303b7874d044a7bc' + kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + self.do_test_mips_malta32el_nanomips(kernel_path_xz) + + def test_mips_malta32el_nanomips_64k_dbg(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=endian:little + :avocado: tags=cpu:I7200 + """ + kernel_url = ('http://mipsdistros.mips.com/LinuxDistro/nanomips/' + 'kernels/v4.15.18-432-gb2eb9a8b07a1-20180627102142/' + 'generic_nano32r6el_page64k_dbg.xz') + kernel_hash = '18d1c68f2e23429e266ca39ba5349ccd0aeb7180' + kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + self.do_test_mips_malta32el_nanomips(kernel_path_xz) diff --git a/tests/avocado/replay_linux.py b/tests/avocado/replay_linux.py new file mode 100644 index 0000000000..f3a43dc98c --- /dev/null +++ b/tests/avocado/replay_linux.py @@ -0,0 +1,196 @@ +# Record/replay test that boots a complete Linux system via a cloud image +# +# Copyright (c) 2020 ISP RAS +# +# Author: +# Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru> +# +# 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 logging +import time + +from avocado import skipUnless +from avocado_qemu import BUILD_DIR +from avocado.utils import cloudinit +from avocado.utils import network +from avocado.utils import vmimage +from avocado.utils import datadrainer +from avocado.utils.path import find_command +from avocado_qemu import LinuxTest + +class ReplayLinux(LinuxTest): + """ + Boots a Linux system, checking for a successful initialization + """ + + timeout = 1800 + chksum = None + hdd = 'ide-hd' + cd = 'ide-cd' + bus = 'ide' + + def setUp(self): + # LinuxTest does many replay-incompatible things, but includes + # useful methods. Do not setup LinuxTest here and just + # call some functions. + super(LinuxTest, self).setUp() + self._set_distro() + self.boot_path = self.download_boot() + self.phone_server = cloudinit.PhoneHomeServer(('0.0.0.0', 0), + self.name) + ssh_pubkey, self.ssh_key = self.set_up_existing_ssh_keys() + self.cloudinit_path = self.prepare_cloudinit(ssh_pubkey) + + def vm_add_disk(self, vm, path, id, device): + bus_string = '' + if self.bus: + bus_string = ',bus=%s.%d' % (self.bus, id,) + vm.add_args('-drive', 'file=%s,snapshot=on,id=disk%s,if=none' % (path, id)) + vm.add_args('-drive', + 'driver=blkreplay,id=disk%s-rr,if=none,image=disk%s' % (id, id)) + vm.add_args('-device', + '%s,drive=disk%s-rr%s' % (device, id, bus_string)) + + def vm_add_cdrom(self, vm, path, id, device): + vm.add_args('-drive', 'file=%s,id=disk%s,if=none,media=cdrom' % (path, id)) + + def launch_and_wait(self, record, args, shift): + self.require_netdev('user') + vm = self.get_vm() + vm.add_args('-smp', '1') + vm.add_args('-m', '1024') + vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22', + '-device', 'virtio-net,netdev=vnet') + vm.add_args('-object', 'filter-replay,id=replay,netdev=vnet') + if args: + vm.add_args(*args) + self.vm_add_disk(vm, self.boot_path, 0, self.hdd) + self.vm_add_cdrom(vm, self.cloudinit_path, 1, self.cd) + logger = logging.getLogger('replay') + if record: + logger.info('recording the execution...') + mode = 'record' + else: + logger.info('replaying the execution...') + mode = 'replay' + replay_path = os.path.join(self.workdir, 'replay.bin') + vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s' % + (shift, mode, replay_path)) + + start_time = time.time() + + vm.set_console() + vm.launch() + console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(), + logger=self.log.getChild('console'), + stop_check=(lambda : not vm.is_running())) + console_drainer.start() + if record: + while not self.phone_server.instance_phoned_back: + self.phone_server.handle_request() + vm.shutdown() + logger.info('finished the recording with log size %s bytes' + % os.path.getsize(replay_path)) + else: + vm.event_wait('SHUTDOWN', self.timeout) + vm.wait() + logger.info('successfully finished the replay') + elapsed = time.time() - start_time + logger.info('elapsed time %.2f sec' % elapsed) + return elapsed + + def run_rr(self, args=None, shift=7): + t1 = self.launch_and_wait(True, args, shift) + t2 = self.launch_and_wait(False, args, shift) + logger = logging.getLogger('replay') + logger.info('replay overhead {:.2%}'.format(t2 / t1 - 1)) + +@skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') +class ReplayLinuxX8664(ReplayLinux): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=accel:tcg + """ + + chksum = 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0' + + def test_pc_i440fx(self): + """ + :avocado: tags=machine:pc + """ + self.run_rr(shift=1) + + def test_pc_q35(self): + """ + :avocado: tags=machine:q35 + """ + self.run_rr(shift=3) + +@skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') +class ReplayLinuxX8664Virtio(ReplayLinux): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=virtio + :avocado: tags=accel:tcg + """ + + hdd = 'virtio-blk-pci' + cd = 'virtio-blk-pci' + bus = None + + chksum = 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0' + + def test_pc_i440fx(self): + """ + :avocado: tags=machine:pc + """ + self.run_rr(shift=1) + + def test_pc_q35(self): + """ + :avocado: tags=machine:q35 + """ + self.run_rr(shift=3) + +@skipUnless(os.getenv('AVOCADO_TIMEOUT_EXPECTED'), 'Test might timeout') +class ReplayLinuxAarch64(ReplayLinux): + """ + :avocado: tags=accel:tcg + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:max + """ + + chksum = '1e18d9c0cf734940c4b5d5ec592facaed2af0ad0329383d5639c997fdf16fe49' + + hdd = 'virtio-blk-device' + cd = 'virtio-blk-device' + bus = None + + def get_common_args(self): + return ('-bios', + os.path.join(BUILD_DIR, 'pc-bios', 'edk2-aarch64-code.fd'), + "-cpu", "max,lpa2=off", + '-device', 'virtio-rng-pci,rng=rng0', + '-object', 'rng-builtin,id=rng0') + + def test_virt_gicv2(self): + """ + :avocado: tags=machine:gic-version=2 + """ + + self.run_rr(shift=3, + args=(*self.get_common_args(), + "-machine", "virt,gic-version=2")) + + def test_virt_gicv3(self): + """ + :avocado: tags=machine:gic-version=3 + """ + + self.run_rr(shift=3, + args=(*self.get_common_args(), + "-machine", "virt,gic-version=3")) diff --git a/tests/avocado/reverse_debugging.py b/tests/avocado/reverse_debugging.py new file mode 100644 index 0000000000..92855a02a5 --- /dev/null +++ b/tests/avocado/reverse_debugging.py @@ -0,0 +1,276 @@ +# Reverse debugging test +# +# Copyright (c) 2020 ISP RAS +# +# Author: +# Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru> +# +# 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 logging + +from avocado import skipUnless +from avocado_qemu import BUILD_DIR +from avocado.utils import datadrainer +from avocado.utils import gdb +from avocado.utils import process +from avocado.utils.network.ports import find_free_port +from avocado.utils.path import find_command +from boot_linux_console import LinuxKernelTest + +class ReverseDebugging(LinuxKernelTest): + """ + Test GDB reverse debugging commands: reverse step and reverse continue. + Recording saves the execution of some instructions and makes an initial + VM snapshot to allow reverse execution. + Replay saves the order of the first instructions and then checks that they + are executed backwards in the correct order. + After that the execution is replayed to the end, and reverse continue + command is checked by setting several breakpoints, and asserting + that the execution is stopped at the last of them. + """ + + timeout = 10 + STEPS = 10 + endian_is_le = True + + def run_vm(self, record, shift, args, replay_path, image_path, port): + logger = logging.getLogger('replay') + vm = self.get_vm() + vm.set_console() + if record: + logger.info('recording the execution...') + mode = 'record' + else: + logger.info('replaying the execution...') + mode = 'replay' + vm.add_args('-gdb', 'tcp::%d' % port, '-S') + vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' % + (shift, mode, replay_path), + '-net', 'none') + vm.add_args('-drive', 'file=%s,if=none' % image_path) + if args: + vm.add_args(*args) + vm.launch() + console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(), + logger=self.log.getChild('console'), + stop_check=(lambda : not vm.is_running())) + console_drainer.start() + return vm + + @staticmethod + def get_reg_le(g, reg): + res = g.cmd(b'p%x' % reg) + num = 0 + for i in range(len(res))[-2::-2]: + num = 0x100 * num + int(res[i:i + 2], 16) + return num + + @staticmethod + def get_reg_be(g, reg): + res = g.cmd(b'p%x' % reg) + return int(res, 16) + + def get_reg(self, g, reg): + # value may be encoded in BE or LE order + if self.endian_is_le: + return self.get_reg_le(g, reg) + else: + return self.get_reg_be(g, reg) + + def get_pc(self, g): + return self.get_reg(g, self.REG_PC) + + def check_pc(self, g, addr): + pc = self.get_pc(g) + if pc != addr: + self.fail('Invalid PC (read %x instead of %x)' % (pc, addr)) + + @staticmethod + def gdb_step(g): + g.cmd(b's', b'T05thread:01;') + + @staticmethod + def gdb_bstep(g): + g.cmd(b'bs', b'T05thread:01;') + + @staticmethod + def vm_get_icount(vm): + return vm.qmp('query-replay')['return']['icount'] + + def reverse_debugging(self, shift=7, args=None): + logger = logging.getLogger('replay') + + # create qcow2 for snapshots + logger.info('creating qcow2 image for VM snapshots') + image_path = os.path.join(self.workdir, 'disk.qcow2') + qemu_img = os.path.join(BUILD_DIR, 'qemu-img') + if not os.path.exists(qemu_img): + qemu_img = find_command('qemu-img', False) + if qemu_img is False: + self.cancel('Could not find "qemu-img", which is required to ' + 'create the temporary qcow2 image') + cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path) + process.run(cmd) + + replay_path = os.path.join(self.workdir, 'replay.bin') + port = find_free_port() + + # record the log + vm = self.run_vm(True, shift, args, replay_path, image_path, port) + while self.vm_get_icount(vm) <= self.STEPS: + pass + last_icount = self.vm_get_icount(vm) + vm.shutdown() + + logger.info("recorded log with %s+ steps" % last_icount) + + # replay and run debug commands + vm = self.run_vm(False, shift, args, replay_path, image_path, port) + logger.info('connecting to gdbstub') + g = gdb.GDBRemote('127.0.0.1', port, False, False) + g.connect() + r = g.cmd(b'qSupported') + if b'qXfer:features:read+' in r: + g.cmd(b'qXfer:features:read:target.xml:0,ffb') + if b'ReverseStep+' not in r: + self.fail('Reverse step is not supported by QEMU') + if b'ReverseContinue+' not in r: + self.fail('Reverse continue is not supported by QEMU') + + logger.info('stepping forward') + steps = [] + # record first instruction addresses + for _ in range(self.STEPS): + pc = self.get_pc(g) + logger.info('saving position %x' % pc) + steps.append(pc) + self.gdb_step(g) + + # visit the recorded instruction in reverse order + logger.info('stepping backward') + for addr in steps[::-1]: + self.gdb_bstep(g) + self.check_pc(g, addr) + logger.info('found position %x' % addr) + + # visit the recorded instruction in forward order + logger.info('stepping forward') + for addr in steps: + self.check_pc(g, addr) + self.gdb_step(g) + logger.info('found position %x' % addr) + + # set breakpoints for the instructions just stepped over + logger.info('setting breakpoints') + for addr in steps: + # hardware breakpoint at addr with len=1 + g.cmd(b'Z1,%x,1' % addr, b'OK') + + # this may hit a breakpoint if first instructions are executed + # again + logger.info('continuing execution') + vm.qmp('replay-break', icount=last_icount - 1) + # continue - will return after pausing + # This could stop at the end and get a T02 return, or by + # re-executing one of the breakpoints and get a T05 return. + g.cmd(b'c') + if self.vm_get_icount(vm) == last_icount - 1: + logger.info('reached the end (icount %s)' % (last_icount - 1)) + else: + logger.info('hit a breakpoint again at %x (icount %s)' % + (self.get_pc(g), self.vm_get_icount(vm))) + + logger.info('running reverse continue to reach %x' % steps[-1]) + # reverse continue - will return after stopping at the breakpoint + g.cmd(b'bc', b'T05thread:01;') + + # assume that none of the first instructions is executed again + # breaking the order of the breakpoints + self.check_pc(g, steps[-1]) + logger.info('successfully reached %x' % steps[-1]) + + logger.info('exiting gdb and qemu') + vm.shutdown() + +class ReverseDebugging_X86_64(ReverseDebugging): + """ + :avocado: tags=accel:tcg + """ + + REG_PC = 0x10 + REG_CS = 0x12 + def get_pc(self, g): + return self.get_reg_le(g, self.REG_PC) \ + + self.get_reg_le(g, self.REG_CS) * 0x10 + + # unidentified gitlab timeout problem + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_x86_64_pc(self): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:pc + """ + # start with BIOS only + self.reverse_debugging() + +class ReverseDebugging_AArch64(ReverseDebugging): + """ + :avocado: tags=accel:tcg + """ + + REG_PC = 32 + + # unidentified gitlab timeout problem + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_aarch64_virt(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:cortex-a53 + """ + kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora' + '/linux/releases/29/Everything/aarch64/os/images/pxeboot' + '/vmlinuz') + kernel_hash = '8c73e469fc6ea06a58dc83a628fc695b693b8493' + kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) + + self.reverse_debugging( + args=('-kernel', kernel_path)) + +class ReverseDebugging_ppc64(ReverseDebugging): + """ + :avocado: tags=accel:tcg + """ + + REG_PC = 0x40 + + # unidentified gitlab timeout problem + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_ppc64_pseries(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + :avocado: tags=flaky + """ + # SLOF branches back to its entry point, which causes this test + # to take the 'hit a breakpoint again' path. That's not a problem, + # just slightly different than the other machines. + self.endian_is_le = False + self.reverse_debugging() + + # See https://gitlab.com/qemu-project/qemu/-/issues/1992 + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + + def test_ppc64_powernv(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:powernv + :avocado: tags=flaky + """ + self.endian_is_le = False + self.reverse_debugging() diff --git a/tests/avocado/riscv_opensbi.py b/tests/avocado/riscv_opensbi.py new file mode 100644 index 0000000000..bfff9cc3c3 --- /dev/null +++ b/tests/avocado/riscv_opensbi.py @@ -0,0 +1,63 @@ +# OpenSBI boot test for RISC-V machines +# +# Copyright (c) 2022, Ventana Micro +# +# 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 avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern + +class RiscvOpenSBI(QemuSystemTest): + """ + :avocado: tags=accel:tcg + """ + timeout = 5 + + def boot_opensbi(self): + self.vm.set_console() + self.vm.launch() + wait_for_console_pattern(self, 'Platform Name') + wait_for_console_pattern(self, 'Boot HART MEDELEG') + + def test_riscv32_spike(self): + """ + :avocado: tags=arch:riscv32 + :avocado: tags=machine:spike + """ + self.boot_opensbi() + + def test_riscv64_spike(self): + """ + :avocado: tags=arch:riscv64 + :avocado: tags=machine:spike + """ + self.boot_opensbi() + + def test_riscv32_sifive_u(self): + """ + :avocado: tags=arch:riscv32 + :avocado: tags=machine:sifive_u + """ + self.boot_opensbi() + + def test_riscv64_sifive_u(self): + """ + :avocado: tags=arch:riscv64 + :avocado: tags=machine:sifive_u + """ + self.boot_opensbi() + + def test_riscv32_virt(self): + """ + :avocado: tags=arch:riscv32 + :avocado: tags=machine:virt + """ + self.boot_opensbi() + + def test_riscv64_virt(self): + """ + :avocado: tags=arch:riscv64 + :avocado: tags=machine:virt + """ + self.boot_opensbi() diff --git a/tests/avocado/s390_topology.py b/tests/avocado/s390_topology.py new file mode 100644 index 0000000000..9154ac8776 --- /dev/null +++ b/tests/avocado/s390_topology.py @@ -0,0 +1,439 @@ +# 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. + +import os +import shutil +import time + +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import interrupt_interactive_console_until_pattern +from avocado_qemu import wait_for_console_pattern +from avocado.utils import process +from avocado.utils import archive + + +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') + + 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_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/35/Server/s390x/os' + '/images/kernel.img') + kernel_hash = '0d1aaaf303f07cf0160c8c48e56fe638' + kernel_path = self.fetch_asset(kernel_url, algorithm='md5', + asset_hash=kernel_hash) + + initrd_url = ('https://archives.fedoraproject.org/pub/archive' + '/fedora-secondary/releases/35/Server/s390x/os' + '/images/initrd.img') + initrd_hash = 'a122057d95725ac030e2ec51df46e172' + initrd_path_xz = self.fetch_asset(initrd_url, algorithm='md5', + asset_hash=initrd_hash) + initrd_path = os.path.join(self.workdir, 'initrd-raw.img') + archive.lzma_uncompress(initrd_path_xz, initrd_path) + + 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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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. + + :avocado: tags=arch:s390x + :avocado: tags=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 + + :avocado: tags=arch:s390x + :avocado: tags=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 + + :avocado: tags=arch:s390x + :avocado: tags=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) diff --git a/tests/avocado/smmu.py b/tests/avocado/smmu.py new file mode 100644 index 0000000000..21ff030ca7 --- /dev/null +++ b/tests/avocado/smmu.py @@ -0,0 +1,139 @@ +# SMMUv3 Functional tests +# +# Copyright (c) 2021 Red Hat, Inc. +# +# Author: +# Eric Auger <eric.auger@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 + +from avocado import skipUnless +from avocado_qemu import LinuxTest, BUILD_DIR + +@skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + +class SMMU(LinuxTest): + """ + :avocado: tags=accel:kvm + :avocado: tags=cpu:host + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=distro:fedora + :avocado: tags=smmu + :avocado: tags=flaky + """ + + IOMMU_ADDON = ',iommu_platform=on,disable-modern=off,disable-legacy=on' + kernel_path = None + initrd_path = None + kernel_params = None + + def set_up_boot(self): + path = self.download_boot() + self.vm.add_args('-device', 'virtio-blk-pci,bus=pcie.0,scsi=off,' + + 'drive=drv0,id=virtio-disk0,bootindex=1,' + 'werror=stop,rerror=stop' + self.IOMMU_ADDON) + self.vm.add_args('-drive', + 'file=%s,if=none,cache=writethrough,id=drv0' % path) + + def setUp(self): + super(SMMU, self).setUp(None, 'virtio-net-pci' + self.IOMMU_ADDON) + + def common_vm_setup(self, custom_kernel=False): + self.require_accelerator("kvm") + self.vm.add_args("-accel", "kvm") + self.vm.add_args("-cpu", "host") + self.vm.add_args("-machine", "iommu=smmuv3") + self.vm.add_args("-d", "guest_errors") + self.vm.add_args('-bios', os.path.join(BUILD_DIR, 'pc-bios', + 'edk2-aarch64-code.fd')) + self.vm.add_args('-device', 'virtio-rng-pci,rng=rng0') + self.vm.add_args('-object', + 'rng-random,id=rng0,filename=/dev/urandom') + + if custom_kernel is False: + return + + kernel_url = self.distro.pxeboot_url + 'vmlinuz' + initrd_url = self.distro.pxeboot_url + 'initrd.img' + self.kernel_path = self.fetch_asset(kernel_url) + self.initrd_path = self.fetch_asset(initrd_url) + + def run_and_check(self): + if self.kernel_path: + self.vm.add_args('-kernel', self.kernel_path, + '-append', self.kernel_params, + '-initrd', self.initrd_path) + self.launch_and_wait() + self.ssh_command('cat /proc/cmdline') + self.ssh_command('dnf -y install numactl-devel') + + + # 5.3 kernel without RIL # + + def test_smmu_noril(self): + """ + :avocado: tags=smmu_noril + :avocado: tags=smmu_noril_tests + :avocado: tags=distro_version:31 + """ + self.common_vm_setup() + self.run_and_check() + + def test_smmu_noril_passthrough(self): + """ + :avocado: tags=smmu_noril_passthrough + :avocado: tags=smmu_noril_tests + :avocado: tags=distro_version:31 + """ + self.common_vm_setup(True) + self.kernel_params = (self.distro.default_kernel_params + + ' iommu.passthrough=on') + self.run_and_check() + + def test_smmu_noril_nostrict(self): + """ + :avocado: tags=smmu_noril_nostrict + :avocado: tags=smmu_noril_tests + :avocado: tags=distro_version:31 + """ + self.common_vm_setup(True) + self.kernel_params = (self.distro.default_kernel_params + + ' iommu.strict=0') + self.run_and_check() + + # 5.8 kernel featuring range invalidation + # >= v5.7 kernel + + def test_smmu_ril(self): + """ + :avocado: tags=smmu_ril + :avocado: tags=smmu_ril_tests + :avocado: tags=distro_version:33 + """ + self.common_vm_setup() + self.run_and_check() + + def test_smmu_ril_passthrough(self): + """ + :avocado: tags=smmu_ril_passthrough + :avocado: tags=smmu_ril_tests + :avocado: tags=distro_version:33 + """ + self.common_vm_setup(True) + self.kernel_params = (self.distro.default_kernel_params + + ' iommu.passthrough=on') + self.run_and_check() + + def test_smmu_ril_nostrict(self): + """ + :avocado: tags=smmu_ril_nostrict + :avocado: tags=smmu_ril_tests + :avocado: tags=distro_version:33 + """ + self.common_vm_setup(True) + self.kernel_params = (self.distro.default_kernel_params + + ' iommu.strict=0') + self.run_and_check() diff --git a/tests/avocado/tcg_plugins.py b/tests/avocado/tcg_plugins.py new file mode 100644 index 0000000000..15fd87b2c1 --- /dev/null +++ b/tests/avocado/tcg_plugins.py @@ -0,0 +1,155 @@ +# TCG Plugins tests +# +# These are a little more involved than the basic tests run by check-tcg. +# +# Copyright (c) 2021 Linaro +# +# Author: +# Alex Bennée <alex.bennee@linaro.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import tempfile +import mmap +import re + +from boot_linux_console import LinuxKernelTest + + +class PluginKernelBase(LinuxKernelTest): + """ + Boots a Linux kernel with a TCG plugin enabled. + """ + + timeout = 120 + KERNEL_COMMON_COMMAND_LINE = 'printk.time=1 panic=-1 ' + + def run_vm(self, kernel_path, kernel_command_line, + plugin, plugin_log, console_pattern, args=None): + + vm = self.get_vm() + vm.set_console() + vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line, + '-plugin', plugin, + '-d', 'plugin', + '-D', plugin_log, + '-net', 'none', + '-no-reboot') + if args: + vm.add_args(*args) + + try: + vm.launch() + except: + # TODO: probably fails because plugins not enabled but we + # can't currently probe for the feature. + self.cancel("TCG Plugins not enabled?") + + self.wait_for_console_pattern(console_pattern, vm) + # ensure logs are flushed + vm.shutdown() + + +class PluginKernelNormal(PluginKernelBase): + + def _grab_aarch64_kernel(self): + kernel_url = ('https://storage.tuxboot.com/20230331/arm64/Image') + kernel_sha256 = 'ce95a7101a5fecebe0fe630deee6bd97b32ba41bc8754090e9ad8961ea8674c7' + kernel_path = self.fetch_asset(kernel_url, + asset_hash=kernel_sha256, + algorithm = "sha256") + return kernel_path + + def test_aarch64_virt_insn(self): + """ + :avocado: tags=accel:tcg + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:cortex-a53 + """ + kernel_path = self._grab_aarch64_kernel() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'Kernel panic - not syncing: VFS:' + + plugin_log = tempfile.NamedTemporaryFile(mode="r+t", prefix="plugin", + suffix=".log") + + self.run_vm(kernel_path, kernel_command_line, + "tests/plugin/libinsn.so", plugin_log.name, + console_pattern) + + with plugin_log as lf, \ + mmap.mmap(lf.fileno(), 0, access=mmap.ACCESS_READ) as s: + + m = re.search(br"insns: (?P<count>\d+)", s) + if "count" not in m.groupdict(): + self.fail("Failed to find instruction count") + else: + count = int(m.group("count")) + self.log.info(f"Counted: {count} instructions") + + + def test_aarch64_virt_insn_icount(self): + """ + :avocado: tags=accel:tcg + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:cortex-a53 + """ + kernel_path = self._grab_aarch64_kernel() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'Kernel panic - not syncing: VFS:' + + plugin_log = tempfile.NamedTemporaryFile(mode="r+t", prefix="plugin", + suffix=".log") + + self.run_vm(kernel_path, kernel_command_line, + "tests/plugin/libinsn.so", plugin_log.name, + console_pattern, + args=('-icount', 'shift=1')) + + with plugin_log as lf, \ + mmap.mmap(lf.fileno(), 0, access=mmap.ACCESS_READ) as s: + + m = re.search(br"insns: (?P<count>\d+)", s) + if "count" not in m.groupdict(): + self.fail("Failed to find instruction count") + else: + count = int(m.group("count")) + self.log.info(f"Counted: {count} instructions") + + def test_aarch64_virt_mem_icount(self): + """ + :avocado: tags=accel:tcg + :avocado: tags=arch:aarch64 + :avocado: tags=machine:virt + :avocado: tags=cpu:cortex-a53 + """ + kernel_path = self._grab_aarch64_kernel() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + console_pattern = 'Kernel panic - not syncing: VFS:' + + plugin_log = tempfile.NamedTemporaryFile(mode="r+t", prefix="plugin", + suffix=".log") + + self.run_vm(kernel_path, kernel_command_line, + "tests/plugin/libmem.so,inline=true,callback=true", plugin_log.name, + console_pattern, + args=('-icount', 'shift=1')) + + with plugin_log as lf, \ + mmap.mmap(lf.fileno(), 0, access=mmap.ACCESS_READ) as s: + m = re.findall(br"mem accesses: (?P<count>\d+)", s) + if m is None or len(m) != 2: + self.fail("no memory access counts found") + else: + inline = int(m[0]) + callback = int(m[1]) + if inline != callback: + self.fail("mismatched access counts") + else: + self.log.info(f"Counted {inline} memory accesses") diff --git a/tests/avocado/tesseract_utils.py b/tests/avocado/tesseract_utils.py new file mode 100644 index 0000000000..476f528147 --- /dev/null +++ b/tests/avocado/tesseract_utils.py @@ -0,0 +1,46 @@ +# ... +# +# Copyright (c) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org> +# +# 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 re +import logging + +from avocado.utils import process +from avocado.utils.path import find_command, CmdNotFoundError + +def tesseract_available(expected_version): + try: + find_command('tesseract') + except CmdNotFoundError: + return False + res = process.run('tesseract --version') + try: + version = res.stdout_text.split()[1] + except IndexError: + version = res.stderr_text.split()[1] + return int(version.split('.')[0]) >= expected_version + + match = re.match(r'tesseract\s(\d)', res) + if match is None: + return False + # now this is guaranteed to be a digit + return int(match.groups()[0]) >= expected_version + + +def tesseract_ocr(image_path, tesseract_args='', tesseract_version=3): + console_logger = logging.getLogger('tesseract') + console_logger.debug(image_path) + if tesseract_version == 4: + tesseract_args += ' --oem 1' + proc = process.run("tesseract {} {} stdout".format(tesseract_args, + image_path)) + lines = [] + for line in proc.stdout_text.split('\n'): + sline = line.strip() + if len(sline): + console_logger.debug(sline) + lines += [sline] + return lines diff --git a/tests/avocado/tuxrun_baselines.py b/tests/avocado/tuxrun_baselines.py new file mode 100644 index 0000000000..a936a3b780 --- /dev/null +++ b/tests/avocado/tuxrun_baselines.py @@ -0,0 +1,620 @@ +# 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 + +import os +import time +import tempfile + +from avocado import skip, skipUnless +from avocado_qemu import QemuSystemTest +from avocado_qemu import exec_command, exec_command_and_wait_for_pattern +from avocado_qemu import wait_for_console_pattern +from avocado.utils import process +from avocado.utils.path import find_command + +class TuxRunBaselineTest(QemuSystemTest): + """ + :avocado: tags=accel:tcg + """ + + KERNEL_COMMON_COMMAND_LINE = 'printk.time=0' + # Tests are ~10-40s, allow for --debug/--enable-gcov overhead + timeout = 100 + + def get_tag(self, tagname, default=None): + """ + Get the metadata tag or return the default. + """ + utag = self._get_unique_tag_val(tagname) + print(f"{tagname}/{default} -> {utag}") + if utag: + return utag + + return default + + def setUp(self): + super().setUp() + + # We need zstd for all the tuxrun tests + # See https://github.com/avocado-framework/avocado/issues/5609 + zstd = find_command('zstd', False) + if zstd is False: + self.cancel('Could not find "zstd", which is required to ' + 'decompress rootfs') + self.zstd = zstd + + # Process the TuxRun specific tags, most machines work with + # reasonable defaults but we sometimes need to tweak the + # config. To avoid open coding everything we store all these + # details in the metadata for each test. + + # The tuxboot tag matches the root directory + self.tuxboot = self.get_tag('tuxboot') + + # Most Linux's use ttyS0 for their serial port + self.console = self.get_tag('console', "ttyS0") + + # Does the machine shutdown QEMU nicely on "halt" + self.shutdown = self.get_tag('shutdown') + + # The name of the kernel Image file + self.image = self.get_tag('image', "Image") + + self.root = self.get_tag('root', "vda") + + # Occasionally we need extra devices to hook things up + self.extradev = self.get_tag('extradev') + + self.qemu_img = super().get_qemu_img() + + 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 fetch_tuxrun_assets(self, csums=None, dt=None): + """ + Fetch the TuxBoot assets. They are stored in a standard way so we + use the per-test tags to fetch details. + """ + base_url = f"https://storage.tuxboot.com/20230331/{self.tuxboot}/" + + # empty hash if we weren't passed one + csums = {} if csums is None else csums + ksum = csums.get(self.image, None) + isum = csums.get("rootfs.ext4.zst", None) + + kernel_image = self.fetch_asset(base_url + self.image, + asset_hash = ksum, + algorithm = "sha256") + disk_image_zst = self.fetch_asset(base_url + "rootfs.ext4.zst", + asset_hash = isum, + algorithm = "sha256") + + cmd = f"{self.zstd} -d {disk_image_zst} -o {self.workdir}/rootfs.ext4" + process.run(cmd) + + if dt: + dsum = csums.get(dt, None) + dtb = self.fetch_asset(base_url + dt, + asset_hash = dsum, + algorithm = "sha256") + else: + dtb = None + + return (kernel_image, self.workdir + "/rootfs.ext4", dtb) + + def prepare_run(self, kernel, disk, drive, dtb=None, console_index=0): + """ + Setup to run and add the common parameters to the system + """ + self.vm.set_console(console_index=console_index) + + # all block devices are raw ext4's + blockdev = "driver=raw,file.driver=file," \ + + f"file.filename={disk},node-name=hd0" + + kcmd_line = self.KERNEL_COMMON_COMMAND_LINE + kcmd_line += f" root=/dev/{self.root}" + kcmd_line += f" console={self.console}" + + self.vm.add_args('-kernel', kernel, + '-append', kcmd_line, + '-blockdev', blockdev) + + # Sometimes we need extra devices attached + if self.extradev: + self.vm.add_args('-device', self.extradev) + + self.vm.add_args('-device', + f"{drive},drive=hd0") + + # Some machines need an explicit DTB + if dtb: + self.vm.add_args('-dtb', dtb) + + def run_tuxtest_tests(self, haltmsg): + """ + Wait for the system to boot up, wait for the login prompt and + then do a few things on the console. Trigger a shutdown and + wait to exit cleanly. + """ + self.wait_for_console_pattern("Welcome to TuxTest") + time.sleep(0.2) + exec_command(self, 'root') + time.sleep(0.2) + exec_command(self, 'cat /proc/interrupts') + time.sleep(0.1) + exec_command(self, 'cat /proc/self/maps') + time.sleep(0.1) + exec_command(self, 'uname -a') + time.sleep(0.1) + exec_command_and_wait_for_pattern(self, 'halt', haltmsg) + + # Wait for VM to shut down gracefully if it can + if self.shutdown == "nowait": + self.vm.shutdown() + else: + self.vm.wait() + + def common_tuxrun(self, + csums=None, + dt=None, + drive="virtio-blk-device", + haltmsg="reboot: System halted", + console_index=0): + """ + Common path for LKFT tests. Unless we need to do something + special with the command line we can process most things using + the tag metadata. + """ + (kernel, disk, dtb) = self.fetch_tuxrun_assets(csums, dt) + + self.prepare_run(kernel, disk, drive, dtb, console_index) + self.vm.launch() + self.run_tuxtest_tests(haltmsg) + + def ppc64_common_tuxrun(self, sums, prefix): + # add device args to command line. + self.require_netdev('user') + self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22', + '-device', 'virtio-net,netdev=vnet') + self.vm.add_args('-netdev', '{"type":"user","id":"hostnet0"}', + '-device', '{"driver":"virtio-net-pci","netdev":' + '"hostnet0","id":"net0","mac":"52:54:00:4c:e3:86",' + '"bus":"pci.0","addr":"0x9"}') + self.vm.add_args('-device', '{"driver":"qemu-xhci","p2":15,"p3":15,' + '"id":"usb","bus":"pci.0","addr":"0x2"}') + self.vm.add_args('-device', '{"driver":"virtio-scsi-pci","id":"scsi0"' + ',"bus":"pci.0","addr":"0x3"}') + self.vm.add_args('-device', '{"driver":"virtio-serial-pci","id":' + '"virtio-serial0","bus":"pci.0","addr":"0x4"}') + self.vm.add_args('-device', '{"driver":"scsi-cd","bus":"scsi0.0"' + ',"channel":0,"scsi-id":0,"lun":0,"device_id":' + '"drive-scsi0-0-0-0","id":"scsi0-0-0-0"}') + self.vm.add_args('-device', '{"driver":"virtio-balloon-pci",' + '"id":"balloon0","bus":"pci.0","addr":"0x6"}') + self.vm.add_args('-audiodev', '{"id":"audio1","driver":"none"}') + self.vm.add_args('-device', '{"driver":"usb-tablet","id":"input0"' + ',"bus":"usb.0","port":"1"}') + self.vm.add_args('-device', '{"driver":"usb-kbd","id":"input1"' + ',"bus":"usb.0","port":"2"}') + self.vm.add_args('-device', '{"driver":"VGA","id":"video0",' + '"vgamem_mb":16,"bus":"pci.0","addr":"0x7"}') + self.vm.add_args('-object', '{"qom-type":"rng-random","id":"objrng0"' + ',"filename":"/dev/urandom"}', + '-device', '{"driver":"virtio-rng-pci","rng":"objrng0"' + ',"id":"rng0","bus":"pci.0","addr":"0x8"}') + self.vm.add_args('-object', '{"qom-type":"cryptodev-backend-builtin",' + '"id":"objcrypto0","queues":1}', + '-device', '{"driver":"virtio-crypto-pci",' + '"cryptodev":"objcrypto0","id":"crypto0","bus"' + ':"pci.0","addr":"0xa"}') + self.vm.add_args('-device', '{"driver":"spapr-pci-host-bridge"' + ',"index":1,"id":"pci.1"}') + self.vm.add_args('-device', '{"driver":"spapr-vscsi","id":"scsi1"' + ',"reg":12288}') + self.vm.add_args('-m', '2G,slots=32,maxmem=4G', + '-object', 'memory-backend-ram,id=ram1,size=1G', + '-device', 'pc-dimm,id=dimm1,memdev=ram1') + + # Create a temporary qcow2 and launch the test-case + with tempfile.NamedTemporaryFile(prefix=prefix, + suffix='.qcow2') as qcow2: + process.run(self.qemu_img + ' create -f qcow2 ' + + qcow2.name + ' 1G') + + self.vm.add_args('-drive', 'file=' + qcow2.name + + ',format=qcow2,if=none,id=' + 'drive-virtio-disk1', + '-device', 'virtio-blk-pci,scsi=off,bus=pci.0,' + 'addr=0xb,drive=drive-virtio-disk1,id=virtio-disk1' + ',bootindex=2') + self.common_tuxrun(csums=sums, drive="scsi-hd") + + # + # The tests themselves. The configuration is derived from how + # tuxrun invokes qemu (with minor tweaks like using -blockdev + # consistently). The tuxrun equivalent is something like: + # + # tuxrun --device qemu-{ARCH} \ + # --kernel https://storage.tuxboot.com/{TUXBOOT}/{IMAGE} + # + + def test_arm64(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + :avocado: tags=tuxboot:arm64 + :avocado: tags=console:ttyAMA0 + :avocado: tags=shutdown:nowait + """ + sums = {"Image" : + "ce95a7101a5fecebe0fe630deee6bd97b32ba41bc8754090e9ad8961ea8674c7", + "rootfs.ext4.zst" : + "bbd5ed4b9c7d3f4ca19ba71a323a843c6b585e880115df3b7765769dbd9dd061"} + self.common_tuxrun(csums=sums) + + def test_arm64be(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=cpu:cortex-a57 + :avocado: tags=endian:big + :avocado: tags=machine:virt + :avocado: tags=tuxboot:arm64be + :avocado: tags=console:ttyAMA0 + :avocado: tags=shutdown:nowait + """ + sums = { "Image" : + "e0df4425eb2cd9ea9a283e808037f805641c65d8fcecc8f6407d8f4f339561b4", + "rootfs.ext4.zst" : + "e6ffd8813c8a335bc15728f2835f90539c84be7f8f5f691a8b01451b47fb4bd7"} + self.common_tuxrun(csums=sums) + + def test_armv5(self): + """ + :avocado: tags=arch:arm + :avocado: tags=cpu:arm926 + :avocado: tags=machine:versatilepb + :avocado: tags=tuxboot:armv5 + :avocado: tags=image:zImage + :avocado: tags=console:ttyAMA0 + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "17177afa74e7294da0642861f08c88ca3c836764299a54bf6d1ce276cb9712a5", + "versatile-pb.dtb" : + "0bc0c0b0858cefd3c32b385c0d66d97142ded29472a496f4f490e42fc7615b25", + "zImage" : + "c95af2f27647c12265d75e9df44c22ff5228c59855f54aaa70f41ec2842e3a4d" } + + self.common_tuxrun(csums=sums, + drive="virtio-blk-pci", + dt="versatile-pb.dtb") + + def test_armv7(self): + """ + :avocado: tags=arch:arm + :avocado: tags=cpu:cortex-a15 + :avocado: tags=machine:virt + :avocado: tags=tuxboot:armv7 + :avocado: tags=image:zImage + :avocado: tags=console:ttyAMA0 + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "ab1fbbeaddda1ffdd45c9405a28cd5370c20f23a7cbc809cc90dc9f243a8eb5a", + "zImage" : + "4c7a22e9f15875bec06bd2a29d822496571eb297d4f22694099ffcdb19077572" } + + self.common_tuxrun(csums=sums) + + def test_armv7be(self): + """ + :avocado: tags=arch:arm + :avocado: tags=cpu:cortex-a15 + :avocado: tags=endian:big + :avocado: tags=machine:virt + :avocado: tags=tuxboot:armv7be + :avocado: tags=image:zImage + :avocado: tags=console:ttyAMA0 + :avocado: tags=shutdown:nowait + """ + sums = {"rootfs.ext4.zst" : + "42ed46dd2d59986206c5b1f6cf35eab58fe3fd20c96b41aaa16b32f3f90a9835", + "zImage" : + "7facc62082b57af12015b08f7fdbaf2f123ba07a478367853ae12b219afc9f2f" } + + self.common_tuxrun(csums=sums) + + def test_i386(self): + """ + :avocado: tags=arch:i386 + :avocado: tags=cpu:coreduo + :avocado: tags=machine:q35 + :avocado: tags=tuxboot:i386 + :avocado: tags=image:bzImage + :avocado: tags=shutdown:nowait + """ + sums = {"bzImage" : + "a3e5b32a354729e65910f5a1ffcda7c14a6c12a55e8213fb86e277f1b76ed956", + "rootfs.ext4.zst" : + "f15e66b2bf673a210ec2a4b2e744a80530b36289e04f5388aab812b97f69754a" } + + self.common_tuxrun(csums=sums, drive="virtio-blk-pci") + + def test_mips32(self): + """ + :avocado: tags=arch:mips + :avocado: tags=machine:malta + :avocado: tags=cpu:mips32r6-generic + :avocado: tags=endian:big + :avocado: tags=tuxboot:mips32 + :avocado: tags=image:vmlinux + :avocado: tags=root:sda + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "fc3da0b4c2f38d74c6d705123bb0f633c76ed953128f9d0859378c328a6d11a0", + "vmlinux" : + "bfd2172f8b17fb32970ca0c8c58f59c5a4ca38aa5855d920be3a69b5d16e52f0" } + + self.common_tuxrun(csums=sums, drive="driver=ide-hd,bus=ide.0,unit=0") + + def test_mips32el(self): + """ + :avocado: tags=arch:mipsel + :avocado: tags=machine:malta + :avocado: tags=cpu:mips32r6-generic + :avocado: tags=tuxboot:mips32el + :avocado: tags=image:vmlinux + :avocado: tags=root:sda + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "e799768e289fd69209c21f4dacffa11baea7543d5db101e8ce27e3bc2c41d90e", + "vmlinux" : + "8573867c68a8443db8de6d08bb33fb291c189ca2ca671471d3973a3e712096a3" } + + self.common_tuxrun(csums=sums, drive="driver=ide-hd,bus=ide.0,unit=0") + + def test_mips64(self): + """ + :avocado: tags=arch:mips64 + :avocado: tags=machine:malta + :avocado: tags=tuxboot:mips64 + :avocado: tags=endian:big + :avocado: tags=image:vmlinux + :avocado: tags=root:sda + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "69d91eeb04df3d8d172922c6993bb37d4deeb6496def75d8580f6f9de3e431da", + "vmlinux" : + "09010e51e4b8bcbbd2494786ffb48eca78f228e96e5c5438344b0eac4029dc61" } + + self.common_tuxrun(csums=sums, drive="driver=ide-hd,bus=ide.0,unit=0") + + def test_mips64el(self): + """ + :avocado: tags=arch:mips64el + :avocado: tags=machine:malta + :avocado: tags=tuxboot:mips64el + :avocado: tags=image:vmlinux + :avocado: tags=root:sda + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "fba585368f5915b1498ed081863474b2d7ec4e97cdd46d21bdcb2f9698f83de4", + "vmlinux" : + "d4e08965e2155c4cccce7c5f34d18fe34c636cda2f2c9844387d614950155266" } + + self.common_tuxrun(csums=sums, drive="driver=ide-hd,bus=ide.0,unit=0") + + def test_ppc32(self): + """ + :avocado: tags=arch:ppc + :avocado: tags=machine:ppce500 + :avocado: tags=cpu:e500mc + :avocado: tags=tuxboot:ppc32 + :avocado: tags=image:uImage + :avocado: tags=shutdown:nowait + """ + sums = { "rootfs.ext4.zst" : + "8885b9d999cc24d679542a02e9b6aaf48f718f2050ece6b8347074b6ee41dd09", + "uImage" : + "1a68f74b860fda022fb12e03c5efece8c2b8b590d96cca37a8481a3ae0b3f81f" } + + self.common_tuxrun(csums=sums, drive="virtio-blk-pci") + + def test_ppc64(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + :avocado: tags=cpu:POWER10 + :avocado: tags=endian:big + :avocado: tags=console:hvc0 + :avocado: tags=tuxboot:ppc64 + :avocado: tags=image:vmlinux + :avocado: tags=extradev:driver=spapr-vscsi + :avocado: tags=root:sda + """ + sums = { "rootfs.ext4.zst" : + "1d953e81a4379e537fc8e41e05a0a59d9b453eef97aa03d47866c6c45b00bdff", + "vmlinux" : + "f22a9b9e924174a4c199f4c7e5d91a2339fcfe51c6eafd0907dc3e09b64ab728" } + self.ppc64_common_tuxrun(sums, prefix='tuxrun_ppc64_') + + def test_ppc64le(self): + """ + :avocado: tags=arch:ppc64 + :avocado: tags=machine:pseries + :avocado: tags=cpu:POWER10 + :avocado: tags=console:hvc0 + :avocado: tags=tuxboot:ppc64le + :avocado: tags=image:vmlinux + :avocado: tags=extradev:driver=spapr-vscsi + :avocado: tags=root:sda + """ + sums = { "rootfs.ext4.zst" : + "b442678c93fb8abe1f7d3bfa20556488de6b475c22c8fed363f42cf81a0a3906", + "vmlinux" : + "979eb61b445a010fb13e2b927126991f8ceef9c590fa2be0996c00e293e80cf2" } + self.ppc64_common_tuxrun(sums, prefix='tuxrun_ppc64le_') + + def test_riscv32(self): + """ + :avocado: tags=arch:riscv32 + :avocado: tags=machine:virt + :avocado: tags=tuxboot:riscv32 + """ + sums = { "Image" : + "89599407d7334de629a40e7ad6503c73670359eb5f5ae9d686353a3d6deccbd5", + "fw_jump.elf" : + "f2ef28a0b77826f79d085d3e4aa686f1159b315eff9099a37046b18936676985", + "rootfs.ext4.zst" : + "7168d296d0283238ea73cd5a775b3dd608e55e04c7b92b76ecce31bb13108cba" } + + self.common_tuxrun(csums=sums) + + def test_riscv64(self): + """ + :avocado: tags=arch:riscv64 + :avocado: tags=machine:virt + :avocado: tags=tuxboot:riscv64 + """ + sums = { "Image" : + "cd634badc65e52fb63465ec99e309c0de0369f0841b7d9486f9729e119bac25e", + "fw_jump.elf" : + "6e3373abcab4305fe151b564a4c71110d833c21f2c0a1753b7935459e36aedcf", + "rootfs.ext4.zst" : + "b18e3a3bdf27be03da0b285e84cb71bf09eca071c3a087b42884b6982ed679eb" } + + self.common_tuxrun(csums=sums) + + def test_riscv32_maxcpu(self): + """ + :avocado: tags=arch:riscv32 + :avocado: tags=machine:virt + :avocado: tags=cpu:max + :avocado: tags=tuxboot:riscv32 + """ + sums = { "Image" : + "89599407d7334de629a40e7ad6503c73670359eb5f5ae9d686353a3d6deccbd5", + "fw_jump.elf" : + "f2ef28a0b77826f79d085d3e4aa686f1159b315eff9099a37046b18936676985", + "rootfs.ext4.zst" : + "7168d296d0283238ea73cd5a775b3dd608e55e04c7b92b76ecce31bb13108cba" } + + self.common_tuxrun(csums=sums) + + def test_riscv64_maxcpu(self): + """ + :avocado: tags=arch:riscv64 + :avocado: tags=machine:virt + :avocado: tags=cpu:max + :avocado: tags=tuxboot:riscv64 + """ + sums = { "Image" : + "cd634badc65e52fb63465ec99e309c0de0369f0841b7d9486f9729e119bac25e", + "fw_jump.elf" : + "6e3373abcab4305fe151b564a4c71110d833c21f2c0a1753b7935459e36aedcf", + "rootfs.ext4.zst" : + "b18e3a3bdf27be03da0b285e84cb71bf09eca071c3a087b42884b6982ed679eb" } + + self.common_tuxrun(csums=sums) + + def test_s390(self): + """ + :avocado: tags=arch:s390x + :avocado: tags=endian:big + :avocado: tags=tuxboot:s390 + :avocado: tags=image:bzImage + :avocado: tags=shutdown:nowait + """ + sums = { "bzImage" : + "0414e98dd1c3dafff8496c9cd9c28a5f8d04553bb5ba37e906a812b48d442ef0", + "rootfs.ext4.zst" : + "88c37c32276677f873a25ab9ec6247895b8e3e6f8259134de2a616080b8ab3fc" } + + self.common_tuxrun(csums=sums, + drive="virtio-blk-ccw", + haltmsg="Requesting system halt") + + # Note: some segfaults caused by unaligned userspace access + @skipUnless(os.getenv('QEMU_TEST_FLAKY_TESTS'), 'Test is unstable on GitLab') + def test_sh4(self): + """ + :avocado: tags=arch:sh4 + :avocado: tags=machine:r2d + :avocado: tags=cpu:sh7785 + :avocado: tags=tuxboot:sh4 + :avocado: tags=image:zImage + :avocado: tags=root:sda + :avocado: tags=console:ttySC1 + :avocado: tags=flaky + """ + sums = { "rootfs.ext4.zst" : + "3592a7a3d5a641e8b9821449e77bc43c9904a56c30d45da0694349cfd86743fd", + "zImage" : + "29d9b2aba604a0f53a5dc3b5d0f2b8e35d497de1129f8ee5139eb6fdf0db692f" } + + # The test is currently too unstable to do much in userspace + # so we skip common_tuxrun and do a minimal boot and shutdown. + (kernel, disk, dtb) = self.fetch_tuxrun_assets(csums=sums) + + # the console comes on the second serial port + self.prepare_run(kernel, disk, + "driver=ide-hd,bus=ide.0,unit=0", + console_index=1) + self.vm.launch() + + self.wait_for_console_pattern("Welcome to TuxTest") + time.sleep(0.1) + exec_command(self, 'root') + time.sleep(0.1) + exec_command_and_wait_for_pattern(self, 'halt', + "reboot: System halted") + + def test_sparc64(self): + """ + :avocado: tags=arch:sparc64 + :avocado: tags=tuxboot:sparc64 + :avocado: tags=image:vmlinux + :avocado: tags=root:sda + :avocado: tags=shutdown:nowait + """ + + sums = { "rootfs.ext4.zst" : + "ad2f1dc436ab51583543d25d2c210cab478645d47078d30d129a66ab0e281d76", + "vmlinux" : + "e34313e4325ff21deaa3d38a502aa09a373ef62b9bd4d7f8f29388b688225c55" } + + self.common_tuxrun(csums=sums, drive="driver=ide-hd,bus=ide.0,unit=0") + + def test_x86_64(self): + """ + :avocado: tags=arch:x86_64 + :avocado: tags=machine:q35 + :avocado: tags=cpu:Nehalem + :avocado: tags=tuxboot:x86_64 + :avocado: tags=image:bzImage + :avocado: tags=root:sda + :avocado: tags=shutdown:nowait + """ + sums = { "bzImage" : + "2bc7480a669ee9b6b82500a236aba0c54233debe98cb968268fa230f52f03461", + "rootfs.ext4.zst" : + "b72ac729769b8f51c6dffb221113c9a063c774dbe1d66af30eb593c4e9999b4b" } + + self.common_tuxrun(csums=sums, + drive="driver=ide-hd,bus=ide.0,unit=0") diff --git a/tests/avocado/version.py b/tests/avocado/version.py new file mode 100644 index 0000000000..c6139568a1 --- /dev/null +++ b/tests/avocado/version.py @@ -0,0 +1,25 @@ +# Version check example test +# +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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. + + +from avocado_qemu import QemuSystemTest + + +class Version(QemuSystemTest): + """ + :avocado: tags=quick + :avocado: tags=machine:none + """ + def test_qmp_human_info_version(self): + self.vm.add_args('-nodefaults') + self.vm.launch() + res = self.vm.cmd('human-monitor-command', + command_line='info version') + self.assertRegex(res, r'^(\d+\.\d+\.\d)') diff --git a/tests/avocado/virtio-gpu.py b/tests/avocado/virtio-gpu.py new file mode 100644 index 0000000000..6091f614a4 --- /dev/null +++ b/tests/avocado/virtio-gpu.py @@ -0,0 +1,157 @@ +# virtio-gpu tests +# +# 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 avocado_qemu import BUILD_DIR +from avocado_qemu import QemuSystemTest +from avocado_qemu import wait_for_console_pattern +from avocado_qemu import exec_command_and_wait_for_pattern +from avocado_qemu import is_readable_executable_file + +from qemu.utils import kvm_available + +import os +import socket +import subprocess + + +def pick_default_vug_bin(): + relative_path = "./contrib/vhost-user-gpu/vhost-user-gpu" + if is_readable_executable_file(relative_path): + return relative_path + + bld_dir_path = os.path.join(BUILD_DIR, relative_path) + if is_readable_executable_file(bld_dir_path): + return bld_dir_path + + +class VirtioGPUx86(QemuSystemTest): + """ + :avocado: tags=virtio-gpu + :avocado: tags=arch:x86_64 + :avocado: tags=cpu:host + """ + + KERNEL_COMMAND_LINE = "printk.time=0 console=ttyS0 rdinit=/bin/bash" + KERNEL_URL = ( + "https://archives.fedoraproject.org/pub/archive/fedora" + "/linux/releases/33/Everything/x86_64/os/images" + "/pxeboot/vmlinuz" + ) + KERNEL_HASH = '1433cfe3f2ffaa44de4ecfb57ec25dc2399cdecf' + INITRD_URL = ( + "https://archives.fedoraproject.org/pub/archive/fedora" + "/linux/releases/33/Everything/x86_64/os/images" + "/pxeboot/initrd.img" + ) + INITRD_HASH = 'c828d68a027b53e5220536585efe03412332c2d9' + + 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 test_virtio_vga_virgl(self): + """ + :avocado: tags=device:virtio-vga-gl + """ + # FIXME: should check presence of virtio, virgl etc + self.require_accelerator('kvm') + + kernel_path = self.fetch_asset(self.KERNEL_URL, self.KERNEL_HASH) + initrd_path = self.fetch_asset(self.INITRD_URL, self.INITRD_HASH) + + self.vm.set_console() + self.vm.add_args("-m", "2G") + self.vm.add_args("-machine", "pc,accel=kvm") + self.vm.add_args("-device", "virtio-vga-gl") + self.vm.add_args("-display", "egl-headless") + self.vm.add_args( + "-kernel", + kernel_path, + "-initrd", + initrd_path, + "-append", + self.KERNEL_COMMAND_LINE, + ) + try: + self.vm.launch() + except: + # TODO: probably fails because we are missing the VirGL features + self.cancel("VirGL not enabled?") + + self.wait_for_console_pattern("as init process") + exec_command_and_wait_for_pattern( + self, "/usr/sbin/modprobe virtio_gpu", "" + ) + self.wait_for_console_pattern("features: +virgl +edid") + + def test_vhost_user_vga_virgl(self): + """ + :avocado: tags=device:vhost-user-vga + """ + # FIXME: should check presence of vhost-user-gpu, virgl, memfd etc + self.require_accelerator('kvm') + + vug = pick_default_vug_bin() + if not vug: + self.cancel("Could not find vhost-user-gpu") + + kernel_path = self.fetch_asset(self.KERNEL_URL, self.KERNEL_HASH) + initrd_path = self.fetch_asset(self.INITRD_URL, self.INITRD_HASH) + + # Create socketpair to connect proxy and remote processes + qemu_sock, vug_sock = socket.socketpair( + socket.AF_UNIX, socket.SOCK_STREAM + ) + os.set_inheritable(qemu_sock.fileno(), True) + os.set_inheritable(vug_sock.fileno(), True) + + self._vug_log_path = os.path.join( + self.logdir, "vhost-user-gpu.log" + ) + self._vug_log_file = open(self._vug_log_path, "wb") + self.log.info('Complete vhost-user-gpu.log file can be ' + 'found at %s', self._vug_log_path) + + vugp = subprocess.Popen( + [vug, "--virgl", "--fd=%d" % vug_sock.fileno()], + stdin=subprocess.DEVNULL, + stdout=self._vug_log_file, + stderr=subprocess.STDOUT, + shell=False, + close_fds=False, + ) + + self.vm.set_console() + self.vm.add_args("-m", "2G") + self.vm.add_args("-object", "memory-backend-memfd,id=mem,size=2G") + self.vm.add_args("-machine", "pc,memory-backend=mem,accel=kvm") + self.vm.add_args("-chardev", "socket,id=vug,fd=%d" % qemu_sock.fileno()) + self.vm.add_args("-device", "vhost-user-vga,chardev=vug") + self.vm.add_args("-display", "egl-headless") + self.vm.add_args( + "-kernel", + kernel_path, + "-initrd", + initrd_path, + "-append", + self.KERNEL_COMMAND_LINE, + ) + try: + self.vm.launch() + except: + # TODO: probably fails because we are missing the VirGL features + self.cancel("VirGL not enabled?") + self.wait_for_console_pattern("as init process") + exec_command_and_wait_for_pattern(self, "/usr/sbin/modprobe virtio_gpu", + "features: +virgl +edid") + self.vm.shutdown() + qemu_sock.close() + vugp.terminate() + vugp.wait() diff --git a/tests/avocado/virtio_check_params.py b/tests/avocado/virtio_check_params.py new file mode 100644 index 0000000000..5fe370a179 --- /dev/null +++ b/tests/avocado/virtio_check_params.py @@ -0,0 +1,143 @@ +# +# Test virtio-scsi and virtio-blk queue settings for all machine types +# +# Copyright (c) 2019 Virtuozzo International GmbH +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import sys +import os +import re +import logging + +from qemu.machine import QEMUMachine +from avocado_qemu import QemuSystemTest +from avocado import skip + +#list of machine types and virtqueue properties to test +VIRTIO_SCSI_PROPS = {'seg_max_adjust': 'seg_max_adjust'} +VIRTIO_BLK_PROPS = {'seg_max_adjust': 'seg-max-adjust'} + +DEV_TYPES = {'virtio-scsi-pci': VIRTIO_SCSI_PROPS, + 'virtio-blk-pci': VIRTIO_BLK_PROPS} + +VM_DEV_PARAMS = {'virtio-scsi-pci': ['-device', 'virtio-scsi-pci,id=scsi0'], + 'virtio-blk-pci': ['-device', + 'virtio-blk-pci,id=scsi0,drive=drive0', + '-drive', + 'driver=null-co,id=drive0,if=none']} + + +class VirtioMaxSegSettingsCheck(QemuSystemTest): + @staticmethod + def make_pattern(props): + pattern_items = [r'{0} = \w+'.format(prop) for prop in props] + return '|'.join(pattern_items) + + def query_virtqueue(self, vm, dev_type_name): + query_ok = False + error = None + props = None + + output = vm.cmd('human-monitor-command', + command_line = 'info qtree') + props_list = DEV_TYPES[dev_type_name].values(); + pattern = self.make_pattern(props_list) + res = re.findall(pattern, output) + + if len(res) != len(props_list): + props_list = set(props_list) + res = set(res) + not_found = props_list.difference(res) + not_found = ', '.join(not_found) + error = '({0}): The following properties not found: {1}'\ + .format(dev_type_name, not_found) + else: + query_ok = True + props = dict() + for prop in res: + p = prop.split(' = ') + props[p[0]] = p[1] + return query_ok, props, error + + def check_mt(self, mt, dev_type_name): + mt['device'] = dev_type_name # Only for the debug() call. + logger = logging.getLogger('machine') + logger.debug(mt) + with QEMUMachine(self.qemu_bin) as vm: + vm.set_machine(mt["name"]) + vm.add_args('-nodefaults') + for s in VM_DEV_PARAMS[dev_type_name]: + vm.add_args(s) + try: + vm.launch() + query_ok, props, error = self.query_virtqueue(vm, dev_type_name) + except: + query_ok = False + error = sys.exc_info()[0] + + if not query_ok: + self.fail('machine type {0}: {1}'.format(mt['name'], error)) + + for prop_name, prop_val in props.items(): + expected_val = mt[prop_name] + self.assertEqual(expected_val, prop_val) + + @staticmethod + def seg_max_adjust_enabled(mt): + # machine types >= 5.0 should have seg_max_adjust = true + # others seg_max_adjust = false + mt = mt.split("-") + + # machine types with one line name and name like pc-x.x + if len(mt) <= 2: + return False + + # machine types like pc-<chip_name>-x.x[.x] + ver = mt[2] + ver = ver.split("."); + + # versions >= 5.0 goes with seg_max_adjust enabled + major = int(ver[0]) + + if major >= 5: + return True + return False + + @skip("break multi-arch CI") + def test_machine_types(self): + # collect all machine types except 'none', 'isapc', 'microvm' + with QEMUMachine(self.qemu_bin) as vm: + vm.launch() + machines = [m['name'] for m in vm.cmd('query-machines')] + vm.shutdown() + machines.remove('none') + machines.remove('isapc') + machines.remove('microvm') + + for dev_type in DEV_TYPES: + # create the list of machine types and their parameters. + mtypes = list() + for m in machines: + if self.seg_max_adjust_enabled(m): + enabled = 'true' + else: + enabled = 'false' + mtypes.append({'name': m, + DEV_TYPES[dev_type]['seg_max_adjust']: enabled}) + + # test each machine type for a device type + for mt in mtypes: + self.check_mt(mt, dev_type) diff --git a/tests/avocado/virtio_version.py b/tests/avocado/virtio_version.py new file mode 100644 index 0000000000..afe5e828b5 --- /dev/null +++ b/tests/avocado/virtio_version.py @@ -0,0 +1,175 @@ +""" +Check compatibility of virtio device types +""" +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Eduardo Habkost <ehabkost@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 sys +import os + +from qemu.machine import QEMUMachine +from avocado_qemu import QemuSystemTest + +# Virtio Device IDs: +VIRTIO_NET = 1 +VIRTIO_BLOCK = 2 +VIRTIO_CONSOLE = 3 +VIRTIO_RNG = 4 +VIRTIO_BALLOON = 5 +VIRTIO_RPMSG = 7 +VIRTIO_SCSI = 8 +VIRTIO_9P = 9 +VIRTIO_RPROC_SERIAL = 11 +VIRTIO_CAIF = 12 +VIRTIO_GPU = 16 +VIRTIO_INPUT = 18 +VIRTIO_VSOCK = 19 +VIRTIO_CRYPTO = 20 + +PCI_VENDOR_ID_REDHAT_QUMRANET = 0x1af4 + +# Device IDs for legacy/transitional devices: +PCI_LEGACY_DEVICE_IDS = { + VIRTIO_NET: 0x1000, + VIRTIO_BLOCK: 0x1001, + VIRTIO_BALLOON: 0x1002, + VIRTIO_CONSOLE: 0x1003, + VIRTIO_SCSI: 0x1004, + VIRTIO_RNG: 0x1005, + VIRTIO_9P: 0x1009, + VIRTIO_VSOCK: 0x1012, +} + +def pci_modern_device_id(virtio_devid): + return virtio_devid + 0x1040 + +def devtype_implements(vm, devtype, implements): + return devtype in [d['name'] for d in + vm.cmd('qom-list-types', implements=implements)] + +def get_pci_interfaces(vm, devtype): + interfaces = ('pci-express-device', 'conventional-pci-device') + return [i for i in interfaces if devtype_implements(vm, devtype, i)] + +class VirtioVersionCheck(QemuSystemTest): + """ + Check if virtio-version-specific device types result in the + same device tree created by `disable-modern` and + `disable-legacy`. + + :avocado: tags=arch:x86_64 + """ + + # just in case there are failures, show larger diff: + maxDiff = 4096 + + def run_device(self, devtype, opts=None, machine='pc'): + """ + Run QEMU with `-device DEVTYPE`, return device info from `query-pci` + """ + with QEMUMachine(self.qemu_bin) as vm: + vm.set_machine(machine) + if opts: + devtype += ',' + opts + vm.add_args('-device', '%s,id=devfortest' % (devtype)) + vm.add_args('-S') + vm.launch() + + pcibuses = vm.cmd('query-pci') + alldevs = [dev for bus in pcibuses for dev in bus['devices']] + devfortest = [dev for dev in alldevs + if dev['qdev_id'] == 'devfortest'] + return devfortest[0], get_pci_interfaces(vm, devtype) + + + def assert_devids(self, dev, devid, non_transitional=False): + self.assertEqual(dev['id']['vendor'], PCI_VENDOR_ID_REDHAT_QUMRANET) + self.assertEqual(dev['id']['device'], devid) + if non_transitional: + self.assertTrue(0x1040 <= dev['id']['device'] <= 0x107f) + self.assertGreaterEqual(dev['id']['subsystem'], 0x40) + + def check_all_variants(self, qemu_devtype, virtio_devid): + """Check if a virtio device type and its variants behave as expected""" + # Force modern mode: + dev_modern, _ = self.run_device(qemu_devtype, + 'disable-modern=off,disable-legacy=on') + self.assert_devids(dev_modern, pci_modern_device_id(virtio_devid), + non_transitional=True) + + # <prefix>-non-transitional device types should be 100% equivalent to + # <prefix>,disable-modern=off,disable-legacy=on + dev_1_0, nt_ifaces = self.run_device('%s-non-transitional' % (qemu_devtype)) + self.assertEqual(dev_modern, dev_1_0) + + # Force transitional mode: + dev_trans, _ = self.run_device(qemu_devtype, + 'disable-modern=off,disable-legacy=off') + self.assert_devids(dev_trans, PCI_LEGACY_DEVICE_IDS[virtio_devid]) + + # Force legacy mode: + dev_legacy, _ = self.run_device(qemu_devtype, + 'disable-modern=on,disable-legacy=off') + self.assert_devids(dev_legacy, PCI_LEGACY_DEVICE_IDS[virtio_devid]) + + # No options: default to transitional on PC machine-type: + no_opts_pc, generic_ifaces = self.run_device(qemu_devtype) + self.assertEqual(dev_trans, no_opts_pc) + + #TODO: check if plugging on a PCI Express bus will make the + # device non-transitional + #no_opts_q35 = self.run_device(qemu_devtype, machine='q35') + #self.assertEqual(dev_modern, no_opts_q35) + + # <prefix>-transitional device types should be 100% equivalent to + # <prefix>,disable-modern=off,disable-legacy=off + dev_trans, trans_ifaces = self.run_device('%s-transitional' % (qemu_devtype)) + self.assertEqual(dev_trans, dev_trans) + + # ensure the interface information is correct: + self.assertIn('conventional-pci-device', generic_ifaces) + self.assertIn('pci-express-device', generic_ifaces) + + self.assertIn('conventional-pci-device', nt_ifaces) + self.assertIn('pci-express-device', nt_ifaces) + + self.assertIn('conventional-pci-device', trans_ifaces) + self.assertNotIn('pci-express-device', trans_ifaces) + + + def test_conventional_devs(self): + self.check_all_variants('virtio-net-pci', VIRTIO_NET) + # virtio-blk requires 'driver' parameter + #self.check_all_variants('virtio-blk-pci', VIRTIO_BLOCK) + self.check_all_variants('virtio-serial-pci', VIRTIO_CONSOLE) + self.check_all_variants('virtio-rng-pci', VIRTIO_RNG) + self.check_all_variants('virtio-balloon-pci', VIRTIO_BALLOON) + self.check_all_variants('virtio-scsi-pci', VIRTIO_SCSI) + # virtio-9p requires 'fsdev' parameter + #self.check_all_variants('virtio-9p-pci', VIRTIO_9P) + + def check_modern_only(self, qemu_devtype, virtio_devid): + """Check if a modern-only virtio device type behaves as expected""" + # Force modern mode: + dev_modern, _ = self.run_device(qemu_devtype, + 'disable-modern=off,disable-legacy=on') + self.assert_devids(dev_modern, pci_modern_device_id(virtio_devid), + non_transitional=True) + + # No options: should be modern anyway + dev_no_opts, ifaces = self.run_device(qemu_devtype) + self.assertEqual(dev_modern, dev_no_opts) + + self.assertIn('conventional-pci-device', ifaces) + self.assertIn('pci-express-device', ifaces) + + def test_modern_only_devs(self): + self.check_modern_only('virtio-vga', VIRTIO_GPU) + self.check_modern_only('virtio-gpu-pci', VIRTIO_GPU) + self.check_modern_only('virtio-mouse-pci', VIRTIO_INPUT) + self.check_modern_only('virtio-tablet-pci', VIRTIO_INPUT) + self.check_modern_only('virtio-keyboard-pci', VIRTIO_INPUT) diff --git a/tests/avocado/virtiofs_submounts.py.data/cleanup.sh b/tests/avocado/virtiofs_submounts.py.data/cleanup.sh new file mode 100644 index 0000000000..2a6579a0fe --- /dev/null +++ b/tests/avocado/virtiofs_submounts.py.data/cleanup.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +function print_usage() +{ + if [ -n "$2" ]; then + echo "Error: $2" + echo + fi + echo "Usage: $1 <scratch dir>" +} + +scratch_dir=$1 +if [ -z "$scratch_dir" ]; then + print_usage "$0" 'Scratch dir not given' >&2 + exit 1 +fi + +cd "$scratch_dir/share" || exit 1 +mps=(mnt*) +mp_i=0 +for mp in "${mps[@]}"; do + mp_i=$((mp_i + 1)) + printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}" + + sudo umount -R "$mp" + rm -rf "$mp" +done +echo + +rm some-file +cd .. +rmdir share + +imgs=(fs*.img) +img_i=0 +for img in "${imgs[@]}"; do + img_i=$((img_i + 1)) + printf "Detaching and deleting %i/%i...\r" "$img_i" "${#imgs[@]}" + + dev=$(losetup -j "$img" | sed -e 's/:.*//') + sudo losetup -d "$dev" + rm -f "$img" +done +echo + +echo 'Done.' diff --git a/tests/avocado/virtiofs_submounts.py.data/guest-cleanup.sh b/tests/avocado/virtiofs_submounts.py.data/guest-cleanup.sh new file mode 100644 index 0000000000..729cb2d1a5 --- /dev/null +++ b/tests/avocado/virtiofs_submounts.py.data/guest-cleanup.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +function print_usage() +{ + if [ -n "$2" ]; then + echo "Error: $2" + echo + fi + echo "Usage: $1 <scratch dir>" +} + +scratch_dir=$1 +if [ -z "$scratch_dir" ]; then + print_usage "$0" 'Scratch dir not given' >&2 + exit 1 +fi + +cd "$scratch_dir/share" || exit 1 + +mps=(mnt*) +mp_i=0 +for mp in "${mps[@]}"; do + mp_i=$((mp_i + 1)) + printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}" + + sudo umount -R "$mp" +done +echo + +echo 'Done.' diff --git a/tests/avocado/virtiofs_submounts.py.data/guest.sh b/tests/avocado/virtiofs_submounts.py.data/guest.sh new file mode 100644 index 0000000000..59ba40fde1 --- /dev/null +++ b/tests/avocado/virtiofs_submounts.py.data/guest.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +function print_usage() +{ + if [ -n "$2" ]; then + echo "Error: $2" + echo + fi + echo "Usage: $1 <shared dir>" + echo '(The shared directory is the "share" directory in the scratch' \ + 'directory)' +} + +shared_dir=$1 +if [ -z "$shared_dir" ]; then + print_usage "$0" 'Shared dir not given' >&2 + exit 1 +fi + +cd "$shared_dir" + +# FIXME: This should not be necessary, but it is. In order for all +# submounts to be proper mount points, we need to visit them. +# (Before we visit them, they will not be auto-mounted, and so just +# appear as normal directories, with the catch that their st_ino will +# be the st_ino of the filesystem they host, while the st_dev will +# still be the st_dev of the parent.) +# `find` does not work, because it will refuse to touch the mount +# points as long as they are not mounted; their st_dev being shared +# with the parent and st_ino just being the root node's inode ID +# will practically ensure that this node exists elsewhere on the +# filesystem, and `find` is required to recognize loops and not to +# follow them. +# Thus, we have to manually visit all nodes first. + +mnt_i=0 + +function recursively_visit() +{ + pushd "$1" >/dev/null + for entry in *; do + if [[ "$entry" == mnt* ]]; then + mnt_i=$((mnt_i + 1)) + printf "Triggering auto-mount $mnt_i...\r" + fi + + if [ -d "$entry" ]; then + recursively_visit "$entry" + fi + done + popd >/dev/null +} + +recursively_visit . +echo + + +if [ -n "$(find -name not-mounted)" ]; then + echo "Error: not-mounted files visible on mount points:" >&2 + find -name not-mounted >&2 + exit 1 +fi + +if [ ! -f some-file -o "$(cat some-file)" != 'root' ]; then + echo "Error: Bad file in the share root" >&2 + exit 1 +fi + +shopt -s nullglob + +function check_submounts() +{ + local base_path=$1 + + for mp in mnt*; do + printf "Checking submount %i...\r" "$((${#devs[@]} + 1))" + + mp_i=$(echo "$mp" | sed -e 's/mnt//') + dev=$(stat -c '%D' "$mp") + + if [ -n "${devs[mp_i]}" ]; then + echo "Error: $mp encountered twice" >&2 + exit 1 + fi + devs[mp_i]=$dev + + pushd "$mp" >/dev/null + path="$base_path$mp" + while true; do + expected_content="$(printf '%s\n%s\n' "$mp_i" "$path")" + if [ ! -f some-file ]; then + echo "Error: $PWD/some-file does not exist" >&2 + exit 1 + fi + + if [ "$(cat some-file)" != "$expected_content" ]; then + echo "Error: Bad content in $PWD/some-file:" >&2 + echo '--- found ---' + cat some-file + echo '--- expected ---' + echo "$expected_content" + exit 1 + fi + if [ "$(stat -c '%D' some-file)" != "$dev" ]; then + echo "Error: $PWD/some-file has the wrong device ID" >&2 + exit 1 + fi + + if [ -d sub ]; then + if [ "$(stat -c '%D' sub)" != "$dev" ]; then + echo "Error: $PWD/some-file has the wrong device ID" >&2 + exit 1 + fi + cd sub + path="$path/sub" + else + if [ -n "$(echo mnt*)" ]; then + check_submounts "$path/" + fi + break + fi + done + popd >/dev/null + done +} + +root_dev=$(stat -c '%D' some-file) +devs=() +check_submounts '' +echo + +reused_devs=$(echo "$root_dev ${devs[@]}" | tr ' ' '\n' | sort | uniq -d) +if [ -n "$reused_devs" ]; then + echo "Error: Reused device IDs: $reused_devs" >&2 + exit 1 +fi + +echo "Test passed for ${#devs[@]} submounts." diff --git a/tests/avocado/virtiofs_submounts.py.data/host.sh b/tests/avocado/virtiofs_submounts.py.data/host.sh new file mode 100644 index 0000000000..d8a9afebdb --- /dev/null +++ b/tests/avocado/virtiofs_submounts.py.data/host.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +mount_count=128 + +function print_usage() +{ + if [ -n "$2" ]; then + echo "Error: $2" + echo + fi + echo "Usage: $1 <scratch dir> [seed]" + echo "(If no seed is given, it will be randomly generated.)" +} + +scratch_dir=$1 +if [ -z "$scratch_dir" ]; then + print_usage "$0" 'No scratch dir given' >&2 + exit 1 +fi + +if [ ! -d "$scratch_dir" ]; then + print_usage "$0" "$scratch_dir is not a directory" >&2 + exit 1 +fi + +seed=$2 +if [ -z "$seed" ]; then + seed=$RANDOM +fi +RANDOM=$seed + +echo "Seed: $seed" + +set -e +shopt -s nullglob + +cd "$scratch_dir" +if [ -d share ]; then + echo 'Error: This directory seems to be in use already' >&2 + exit 1 +fi + +for ((i = 0; i < $mount_count; i++)); do + printf "Setting up fs %i/%i...\r" "$((i + 1))" "$mount_count" + + rm -f fs$i.img + truncate -s 512M fs$i.img + mkfs.xfs -q fs$i.img + devs[i]=$(sudo losetup -f --show fs$i.img) +done +echo + +top_level_mounts=$((RANDOM % mount_count + 1)) + +mkdir -p share +echo 'root' > share/some-file + +for ((i = 0; i < $top_level_mounts; i++)); do + printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count" + + mkdir -p share/mnt$i + touch share/mnt$i/not-mounted + sudo mount "${devs[i]}" share/mnt$i + sudo chown "$(id -u):$(id -g)" share/mnt$i + + pushd share/mnt$i >/dev/null + path=mnt$i + nesting=$((RANDOM % 4)) + for ((j = 0; j < $nesting; j++)); do + cat > some-file <<EOF +$i +$path +EOF + mkdir sub + cd sub + path="$path/sub" + done +cat > some-file <<EOF +$i +$path +EOF + popd >/dev/null +done + +for ((; i < $mount_count; i++)); do + printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count" + + mp_i=$((i % top_level_mounts)) + + pushd share/mnt$mp_i >/dev/null + path=mnt$mp_i + while true; do + sub_mp="$(echo mnt*)" + if cd sub 2>/dev/null; then + path="$path/sub" + elif [ -n "$sub_mp" ] && cd "$sub_mp" 2>/dev/null; then + path="$path/$sub_mp" + else + break + fi + done + mkdir mnt$i + touch mnt$i/not-mounted + sudo mount "${devs[i]}" mnt$i + sudo chown "$(id -u):$(id -g)" mnt$i + + cd mnt$i + path="$path/mnt$i" + nesting=$((RANDOM % 4)) + for ((j = 0; j < $nesting; j++)); do + cat > some-file <<EOF +$i +$path +EOF + mkdir sub + cd sub + path="$path/sub" + done + cat > some-file <<EOF +$i +$path +EOF + popd >/dev/null +done +echo + +echo 'Done.' diff --git a/tests/avocado/vnc.py b/tests/avocado/vnc.py new file mode 100644 index 0000000000..862c8996a8 --- /dev/null +++ b/tests/avocado/vnc.py @@ -0,0 +1,115 @@ +# Simple functional tests for VNC functionality +# +# Copyright (c) 2018 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@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 socket +from typing import List + +from avocado_qemu import QemuSystemTest + + +VNC_ADDR = '127.0.0.1' +VNC_PORT_START = 32768 +VNC_PORT_END = VNC_PORT_START + 1024 + + +def check_bind(port: int) -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + sock.bind((VNC_ADDR, port)) + except OSError: + return False + + return True + + +def check_connect(port: int) -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + sock.connect((VNC_ADDR, port)) + except ConnectionRefusedError: + return False + + return True + + +def find_free_ports(count: int) -> List[int]: + result = [] + for port in range(VNC_PORT_START, VNC_PORT_END): + if check_bind(port): + result.append(port) + if len(result) >= count: + break + assert len(result) == count + return result + + +class Vnc(QemuSystemTest): + """ + :avocado: tags=vnc,quick + :avocado: tags=machine:none + """ + def test_no_vnc(self): + self.vm.add_args('-nodefaults', '-S') + self.vm.launch() + self.assertFalse(self.vm.qmp('query-vnc')['return']['enabled']) + + def test_no_vnc_change_password(self): + self.vm.add_args('-nodefaults', '-S') + self.vm.launch() + self.assertFalse(self.vm.qmp('query-vnc')['return']['enabled']) + set_password_response = self.vm.qmp('change-vnc-password', + password='new_password') + self.assertIn('error', set_password_response) + self.assertEqual(set_password_response['error']['class'], + 'GenericError') + self.assertEqual(set_password_response['error']['desc'], + 'Could not set password') + + def test_change_password_requires_a_password(self): + self.vm.add_args('-nodefaults', '-S', '-vnc', ':0') + self.vm.launch() + self.assertTrue(self.vm.qmp('query-vnc')['return']['enabled']) + set_password_response = self.vm.qmp('change-vnc-password', + password='new_password') + self.assertIn('error', set_password_response) + self.assertEqual(set_password_response['error']['class'], + 'GenericError') + self.assertEqual(set_password_response['error']['desc'], + 'Could not set password') + + def test_change_password(self): + self.vm.add_args('-nodefaults', '-S', '-vnc', ':0,password=on') + self.vm.launch() + self.assertTrue(self.vm.qmp('query-vnc')['return']['enabled']) + self.vm.cmd('change-vnc-password', + password='new_password') + + def test_change_listen(self): + a, b, c = find_free_ports(3) + self.assertFalse(check_connect(a)) + self.assertFalse(check_connect(b)) + self.assertFalse(check_connect(c)) + + self.vm.add_args('-nodefaults', '-S', '-vnc', f'{VNC_ADDR}:{a - 5900}') + self.vm.launch() + self.assertEqual(self.vm.qmp('query-vnc')['return']['service'], str(a)) + self.assertTrue(check_connect(a)) + self.assertFalse(check_connect(b)) + self.assertFalse(check_connect(c)) + + self.vm.cmd('display-update', type='vnc', + addresses=[{'type': 'inet', 'host': VNC_ADDR, + 'port': str(b)}, + {'type': 'inet', 'host': VNC_ADDR, + 'port': str(c)}]) + self.assertEqual(self.vm.qmp('query-vnc')['return']['service'], str(b)) + self.assertFalse(check_connect(a)) + self.assertTrue(check_connect(b)) + self.assertTrue(check_connect(c)) diff --git a/tests/avocado/x86_cpu_model_versions.py b/tests/avocado/x86_cpu_model_versions.py new file mode 100644 index 0000000000..11101e02b9 --- /dev/null +++ b/tests/avocado/x86_cpu_model_versions.py @@ -0,0 +1,362 @@ +# +# Basic validation of x86 versioned CPU models and CPU model aliases +# +# Copyright (c) 2019 Red Hat Inc +# +# Author: +# Eduardo Habkost <ehabkost@redhat.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see <http://www.gnu.org/licenses/>. +# + + +import avocado_qemu +import re + +class X86CPUModelAliases(avocado_qemu.QemuSystemTest): + """ + Validation of PC CPU model versions and CPU model aliases + + :avocado: tags=arch:x86_64 + """ + def validate_aliases(self, cpus): + for c in cpus.values(): + if 'alias-of' in c: + # all aliases must point to a valid CPU model name: + self.assertIn(c['alias-of'], cpus, + '%s.alias-of (%s) is not a valid CPU model name' % (c['name'], c['alias-of'])) + # aliases must not point to aliases + self.assertNotIn('alias-of', cpus[c['alias-of']], + '%s.alias-of (%s) points to another alias' % (c['name'], c['alias-of'])) + + # aliases must not be static + self.assertFalse(c['static']) + + def validate_variant_aliases(self, cpus): + # -noTSX, -IBRS and -IBPB variants of CPU models are special: + # they shouldn't have their own versions: + self.assertNotIn("Haswell-noTSX-v1", cpus, + "Haswell-noTSX shouldn't be versioned") + self.assertNotIn("Broadwell-noTSX-v1", cpus, + "Broadwell-noTSX shouldn't be versioned") + self.assertNotIn("Nehalem-IBRS-v1", cpus, + "Nehalem-IBRS shouldn't be versioned") + self.assertNotIn("Westmere-IBRS-v1", cpus, + "Westmere-IBRS shouldn't be versioned") + self.assertNotIn("SandyBridge-IBRS-v1", cpus, + "SandyBridge-IBRS shouldn't be versioned") + self.assertNotIn("IvyBridge-IBRS-v1", cpus, + "IvyBridge-IBRS shouldn't be versioned") + self.assertNotIn("Haswell-noTSX-IBRS-v1", cpus, + "Haswell-noTSX-IBRS shouldn't be versioned") + self.assertNotIn("Haswell-IBRS-v1", cpus, + "Haswell-IBRS shouldn't be versioned") + self.assertNotIn("Broadwell-noTSX-IBRS-v1", cpus, + "Broadwell-noTSX-IBRS shouldn't be versioned") + self.assertNotIn("Broadwell-IBRS-v1", cpus, + "Broadwell-IBRS shouldn't be versioned") + self.assertNotIn("Skylake-Client-IBRS-v1", cpus, + "Skylake-Client-IBRS shouldn't be versioned") + self.assertNotIn("Skylake-Server-IBRS-v1", cpus, + "Skylake-Server-IBRS shouldn't be versioned") + self.assertNotIn("EPYC-IBPB-v1", cpus, + "EPYC-IBPB shouldn't be versioned") + + def test_4_0_alias_compatibility(self): + """ + Check if pc-*-4.0 unversioned CPU model won't be reported as aliases + + :avocado: tags=machine:pc-i440fx-4.0 + """ + # pc-*-4.0 won't expose non-versioned CPU models as aliases + # We do this to help management software to keep compatibility + # with older QEMU versions that didn't have the versioned CPU model + self.vm.add_args('-S') + self.vm.launch() + cpus = dict((m['name'], m) for m in + self.vm.cmd('query-cpu-definitions')) + + self.assertFalse(cpus['Cascadelake-Server']['static'], + 'unversioned Cascadelake-Server CPU model must not be static') + self.assertNotIn('alias-of', cpus['Cascadelake-Server'], + 'Cascadelake-Server must not be an alias') + self.assertNotIn('alias-of', cpus['Cascadelake-Server-v1'], + 'Cascadelake-Server-v1 must not be an alias') + + self.assertFalse(cpus['qemu64']['static'], + 'unversioned qemu64 CPU model must not be static') + self.assertNotIn('alias-of', cpus['qemu64'], + 'qemu64 must not be an alias') + self.assertNotIn('alias-of', cpus['qemu64-v1'], + 'qemu64-v1 must not be an alias') + + self.validate_variant_aliases(cpus) + + # On pc-*-4.0, no CPU model should be reported as an alias: + for name,c in cpus.items(): + self.assertNotIn('alias-of', c, "%s shouldn't be an alias" % (name)) + + def test_4_1_alias(self): + """ + Check if unversioned CPU model is an alias pointing to right version + + :avocado: tags=machine:pc-i440fx-4.1 + """ + self.vm.add_args('-S') + self.vm.launch() + + cpus = dict((m['name'], m) for m in + self.vm.cmd('query-cpu-definitions')) + + self.assertFalse(cpus['Cascadelake-Server']['static'], + 'unversioned Cascadelake-Server CPU model must not be static') + self.assertEqual(cpus['Cascadelake-Server'].get('alias-of'), + 'Cascadelake-Server-v1', + 'Cascadelake-Server must be an alias of Cascadelake-Server-v1') + self.assertNotIn('alias-of', cpus['Cascadelake-Server-v1'], + 'Cascadelake-Server-v1 must not be an alias') + + self.assertFalse(cpus['qemu64']['static'], + 'unversioned qemu64 CPU model must not be static') + self.assertEqual(cpus['qemu64'].get('alias-of'), 'qemu64-v1', + 'qemu64 must be an alias of qemu64-v1') + self.assertNotIn('alias-of', cpus['qemu64-v1'], + 'qemu64-v1 must not be an alias') + + self.validate_variant_aliases(cpus) + + # On pc-*-4.1, -noTSX and -IBRS models should be aliases: + self.assertEqual(cpus["Haswell"].get('alias-of'), + "Haswell-v1", + "Haswell must be an alias") + self.assertEqual(cpus["Haswell-noTSX"].get('alias-of'), + "Haswell-v2", + "Haswell-noTSX must be an alias") + self.assertEqual(cpus["Haswell-IBRS"].get('alias-of'), + "Haswell-v3", + "Haswell-IBRS must be an alias") + self.assertEqual(cpus["Haswell-noTSX-IBRS"].get('alias-of'), + "Haswell-v4", + "Haswell-noTSX-IBRS must be an alias") + + self.assertEqual(cpus["Broadwell"].get('alias-of'), + "Broadwell-v1", + "Broadwell must be an alias") + self.assertEqual(cpus["Broadwell-noTSX"].get('alias-of'), + "Broadwell-v2", + "Broadwell-noTSX must be an alias") + self.assertEqual(cpus["Broadwell-IBRS"].get('alias-of'), + "Broadwell-v3", + "Broadwell-IBRS must be an alias") + self.assertEqual(cpus["Broadwell-noTSX-IBRS"].get('alias-of'), + "Broadwell-v4", + "Broadwell-noTSX-IBRS must be an alias") + + self.assertEqual(cpus["Nehalem"].get('alias-of'), + "Nehalem-v1", + "Nehalem must be an alias") + self.assertEqual(cpus["Nehalem-IBRS"].get('alias-of'), + "Nehalem-v2", + "Nehalem-IBRS must be an alias") + + self.assertEqual(cpus["Westmere"].get('alias-of'), + "Westmere-v1", + "Westmere must be an alias") + self.assertEqual(cpus["Westmere-IBRS"].get('alias-of'), + "Westmere-v2", + "Westmere-IBRS must be an alias") + + self.assertEqual(cpus["SandyBridge"].get('alias-of'), + "SandyBridge-v1", + "SandyBridge must be an alias") + self.assertEqual(cpus["SandyBridge-IBRS"].get('alias-of'), + "SandyBridge-v2", + "SandyBridge-IBRS must be an alias") + + self.assertEqual(cpus["IvyBridge"].get('alias-of'), + "IvyBridge-v1", + "IvyBridge must be an alias") + self.assertEqual(cpus["IvyBridge-IBRS"].get('alias-of'), + "IvyBridge-v2", + "IvyBridge-IBRS must be an alias") + + self.assertEqual(cpus["Skylake-Client"].get('alias-of'), + "Skylake-Client-v1", + "Skylake-Client must be an alias") + self.assertEqual(cpus["Skylake-Client-IBRS"].get('alias-of'), + "Skylake-Client-v2", + "Skylake-Client-IBRS must be an alias") + + self.assertEqual(cpus["Skylake-Server"].get('alias-of'), + "Skylake-Server-v1", + "Skylake-Server must be an alias") + self.assertEqual(cpus["Skylake-Server-IBRS"].get('alias-of'), + "Skylake-Server-v2", + "Skylake-Server-IBRS must be an alias") + + self.assertEqual(cpus["EPYC"].get('alias-of'), + "EPYC-v1", + "EPYC must be an alias") + self.assertEqual(cpus["EPYC-IBPB"].get('alias-of'), + "EPYC-v2", + "EPYC-IBPB must be an alias") + + self.validate_aliases(cpus) + + def test_none_alias(self): + """ + Check if unversioned CPU model is an alias pointing to some version + + :avocado: tags=machine:none + """ + self.vm.add_args('-S') + self.vm.launch() + + cpus = dict((m['name'], m) for m in + self.vm.cmd('query-cpu-definitions')) + + self.assertFalse(cpus['Cascadelake-Server']['static'], + 'unversioned Cascadelake-Server CPU model must not be static') + self.assertTrue(re.match('Cascadelake-Server-v[0-9]+', cpus['Cascadelake-Server']['alias-of']), + 'Cascadelake-Server must be an alias of versioned CPU model') + self.assertNotIn('alias-of', cpus['Cascadelake-Server-v1'], + 'Cascadelake-Server-v1 must not be an alias') + + self.assertFalse(cpus['qemu64']['static'], + 'unversioned qemu64 CPU model must not be static') + self.assertTrue(re.match('qemu64-v[0-9]+', cpus['qemu64']['alias-of']), + 'qemu64 must be an alias of versioned CPU model') + self.assertNotIn('alias-of', cpus['qemu64-v1'], + 'qemu64-v1 must not be an alias') + + self.validate_aliases(cpus) + + +class CascadelakeArchCapabilities(avocado_qemu.QemuSystemTest): + """ + Validation of Cascadelake arch-capabilities + + :avocado: tags=arch:x86_64 + """ + def get_cpu_prop(self, prop): + cpu_path = self.vm.cmd('query-cpus-fast')[0].get('qom-path') + return self.vm.cmd('qom-get', path=cpu_path, property=prop) + + def test_4_1(self): + """ + :avocado: tags=machine:pc-i440fx-4.1 + :avocado: tags=cpu:Cascadelake-Server + """ + # machine-type only: + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server,x-force-features=on,check=off,' + 'enforce=off') + self.vm.launch() + self.assertFalse(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.1 + Cascadelake-Server should not have arch-capabilities') + + def test_4_0(self): + """ + :avocado: tags=machine:pc-i440fx-4.0 + :avocado: tags=cpu:Cascadelake-Server + """ + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server,x-force-features=on,check=off,' + 'enforce=off') + self.vm.launch() + self.assertFalse(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.0 + Cascadelake-Server should not have arch-capabilities') + + def test_set_4_0(self): + """ + :avocado: tags=machine:pc-i440fx-4.0 + :avocado: tags=cpu:Cascadelake-Server + """ + # command line must override machine-type if CPU model is not versioned: + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server,x-force-features=on,check=off,' + 'enforce=off,+arch-capabilities') + self.vm.launch() + self.assertTrue(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.0 + Cascadelake-Server,+arch-capabilities should have arch-capabilities') + + def test_unset_4_1(self): + """ + :avocado: tags=machine:pc-i440fx-4.1 + :avocado: tags=cpu:Cascadelake-Server + """ + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server,x-force-features=on,check=off,' + 'enforce=off,-arch-capabilities') + self.vm.launch() + self.assertFalse(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.1 + Cascadelake-Server,-arch-capabilities should not have arch-capabilities') + + def test_v1_4_0(self): + """ + :avocado: tags=machine:pc-i440fx-4.0 + :avocado: tags=cpu:Cascadelake-Server + """ + # versioned CPU model overrides machine-type: + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server-v1,x-force-features=on,check=off,' + 'enforce=off') + self.vm.launch() + self.assertFalse(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.0 + Cascadelake-Server-v1 should not have arch-capabilities') + + def test_v2_4_0(self): + """ + :avocado: tags=machine:pc-i440fx-4.0 + :avocado: tags=cpu:Cascadelake-Server + """ + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server-v2,x-force-features=on,check=off,' + 'enforce=off') + self.vm.launch() + self.assertTrue(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.0 + Cascadelake-Server-v2 should have arch-capabilities') + + def test_v1_set_4_0(self): + """ + :avocado: tags=machine:pc-i440fx-4.0 + :avocado: tags=cpu:Cascadelake-Server + """ + # command line must override machine-type and versioned CPU model: + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server-v1,x-force-features=on,check=off,' + 'enforce=off,+arch-capabilities') + self.vm.launch() + self.assertTrue(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.0 + Cascadelake-Server-v1,+arch-capabilities should have arch-capabilities') + + def test_v2_unset_4_1(self): + """ + :avocado: tags=machine:pc-i440fx-4.1 + :avocado: tags=cpu:Cascadelake-Server + """ + self.vm.add_args('-S') + self.set_vm_arg('-cpu', + 'Cascadelake-Server-v2,x-force-features=on,check=off,' + 'enforce=off,-arch-capabilities') + self.vm.launch() + self.assertFalse(self.get_cpu_prop('arch-capabilities'), + 'pc-i440fx-4.1 + Cascadelake-Server-v2,-arch-capabilities should not have arch-capabilities') |