| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Implements commands for running and interacting with Fuchsia on QEMU.""" |
| |
| import boot_data |
| import logging |
| import target |
| import os |
| import platform |
| import socket |
| import subprocess |
| import time |
| |
| from common import SDK_ROOT, EnsurePathExists |
| |
| |
| # Virtual networking configuration data for QEMU. |
| GUEST_NET = '192.168.3.0/24' |
| GUEST_IP_ADDRESS = '192.168.3.9' |
| HOST_IP_ADDRESS = '192.168.3.2' |
| GUEST_MAC_ADDRESS = '52:54:00:63:5e:7b' |
| |
| |
| def _GetAvailableTcpPort(): |
| """Finds a (probably) open port by opening and closing a listen socket.""" |
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| sock.bind(("", 0)) |
| port = sock.getsockname()[1] |
| sock.close() |
| return port |
| |
| |
| class QemuTarget(target.Target): |
| def __init__(self, output_dir, target_cpu, |
| ram_size_mb=2048): |
| """output_dir: The directory which will contain the files that are |
| generated to support the QEMU deployment. |
| target_cpu: The emulated target CPU architecture. |
| Can be 'x64' or 'arm64'.""" |
| super(QemuTarget, self).__init__(output_dir, target_cpu) |
| self._qemu_process = None |
| self._ram_size_mb = ram_size_mb |
| |
| def __enter__(self): |
| return self |
| |
| # Used by the context manager to ensure that QEMU is killed when the Python |
| # process exits. |
| def __exit__(self, exc_type, exc_val, exc_tb): |
| if self.IsStarted(): |
| self.Shutdown() |
| |
| def Start(self): |
| qemu_path = os.path.join(SDK_ROOT, 'qemu', 'bin', |
| 'qemu-system-' + self._GetTargetSdkArch()) |
| kernel_args = boot_data.GetKernelArgs(self._output_dir) |
| |
| # TERM=dumb tells the guest OS to not emit ANSI commands that trigger |
| # noisy ANSI spew from the user's terminal emulator. |
| kernel_args.append('TERM=dumb') |
| |
| qemu_command = [qemu_path, |
| '-m', str(self._ram_size_mb), |
| '-nographic', |
| '-kernel', EnsurePathExists( |
| boot_data.GetTargetFile(self._GetTargetSdkArch(), |
| 'zircon.bin')), |
| '-initrd', EnsurePathExists( |
| boot_data.GetTargetFile(self._GetTargetSdkArch(), |
| 'bootdata-blob.bin')), |
| '-smp', '4', |
| |
| # Attach the blobstore and data volumes. Use snapshot mode to discard |
| # any changes. |
| '-snapshot', |
| '-drive', 'file=%s,format=qcow2,if=none,id=data,snapshot=on' % |
| EnsurePathExists(os.path.join(self._output_dir, |
| 'fvm.blk.qcow2')), |
| '-drive', 'file=%s,format=qcow2,if=none,id=blobstore,snapshot=on' % |
| EnsurePathExists( |
| boot_data.ConfigureDataFVM(self._output_dir, |
| boot_data.FVM_TYPE_QCOW)), |
| '-device', 'virtio-blk-pci,drive=data', |
| '-device', 'virtio-blk-pci,drive=blobstore', |
| |
| # Use stdio for the guest OS only; don't attach the QEMU interactive |
| # monitor. |
| '-serial', 'stdio', |
| '-monitor', 'none', |
| |
| '-append', ' '.join(kernel_args) |
| ] |
| |
| # Configure the machine & CPU to emulate, based on the target architecture. |
| # Enable lightweight virtualization (KVM) if the host and guest OS run on |
| # the same architecture. |
| if self._target_cpu == 'arm64': |
| qemu_command.extend([ |
| '-machine','virt', |
| '-cpu', 'cortex-a53', |
| ]) |
| netdev_type = 'virtio-net-pci' |
| if platform.machine() == 'aarch64': |
| qemu_command.append('-enable-kvm') |
| else: |
| qemu_command.extend([ |
| '-machine', 'q35', |
| '-cpu', 'host,migratable=no', |
| ]) |
| netdev_type = 'e1000' |
| if platform.machine() == 'x86_64': |
| qemu_command.append('-enable-kvm') |
| |
| # Configure virtual network. It is used in the tests to connect to |
| # testserver running on the host. |
| netdev_config = 'user,id=net0,net=%s,dhcpstart=%s,host=%s' % \ |
| (GUEST_NET, GUEST_IP_ADDRESS, HOST_IP_ADDRESS) |
| |
| self._host_ssh_port = _GetAvailableTcpPort() |
| netdev_config += ",hostfwd=tcp::%s-:22" % self._host_ssh_port |
| qemu_command.extend([ |
| '-netdev', netdev_config, |
| '-device', '%s,netdev=net0,mac=%s' % (netdev_type, GUEST_MAC_ADDRESS), |
| ]) |
| |
| # We pass a separate stdin stream to qemu. Sharing stdin across processes |
| # leads to flakiness due to the OS prematurely killing the stream and the |
| # Python script panicking and aborting. |
| # The precise root cause is still nebulous, but this fix works. |
| # See crbug.com/741194. |
| logging.debug('Launching QEMU.') |
| logging.debug(' '.join(qemu_command)) |
| |
| stdio_flags = {'stdin': open(os.devnull), |
| 'stdout': open(os.devnull), |
| 'stderr': open(os.devnull)} |
| self._qemu_process = subprocess.Popen(qemu_command, **stdio_flags) |
| self._WaitUntilReady(); |
| |
| def Shutdown(self): |
| logging.info('Shutting down QEMU.') |
| self._qemu_process.kill() |
| |
| def GetQemuStdout(self): |
| return self._qemu_process.stdout |
| |
| def _GetEndpoint(self): |
| return ('localhost', self._host_ssh_port) |
| |
| def _GetSshConfigPath(self): |
| return boot_data.GetSSHConfigPath(self._output_dir) |
| |