| # 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) | 
 |  |