Merge commit '883847ef2f6479f3639b522f2b5f7a195670b591' into 14542.0.0

BUG=b/222349736
TEST=local BE run
RELEASE_NOTE=None

Change-Id: I84600109e60dbcff59a374acc97ea8d0c67d09d9
diff --git a/lib/vm.py b/lib/vm.py
index c9e2642..3975134 100644
--- a/lib/vm.py
+++ b/lib/vm.py
@@ -139,11 +139,15 @@
   """Class for managing a VM."""
 
   SSH_PORT = 9222
-  SSH_NON_KVM_CONNECT_TIMEOUT = 120
+  SSH_NON_KVM_CONNECT_TIMEOUT = 240
   IMAGE_FORMAT = 'raw'
   # kvm_* should match kvm_intel, kvm_amd, etc.
   NESTED_KVM_GLOB = '/sys/module/kvm_*/parameters/nested'
 
+  # Target architecture
+  ARCH_X86_64 = 'x86_64'
+  ARCH_AARCH64 = 'aarch64'
+
   def __init__(self, opts):
     """Initialize VM.
 
@@ -155,18 +159,30 @@
     self.qemu_path = opts.qemu_path
     self.qemu_img_path = opts.qemu_img_path
     self.qemu_bios_path = opts.qemu_bios_path
+    self.qemu_arch = opts.qemu_arch
     self.qemu_m = opts.qemu_m
     self.qemu_cpu = opts.qemu_cpu
+    if self.qemu_cpu is None:
+      # TODO(pwang): replace SandyBridge to Haswell-noTSX once lab machine
+      # running VMTest all migrate to GCE.
+      if self.qemu_arch == VM.ARCH_X86_64:
+        self.qemu_cpu = 'SandyBridge,-invpcid,-tsc-deadline'
+      elif self.qemu_arch == VM.ARCH_AARCH64:
+        self.qemu_cpu = 'cortex-a57'
+    self.qemu_arch = opts.qemu_arch
     self.qemu_smp = opts.qemu_smp
     if self.qemu_smp == 0:
       self.qemu_smp = min(8, multiprocessing.cpu_count())
     self.qemu_hostfwd = opts.qemu_hostfwd
     self.qemu_args = opts.qemu_args
 
-    if opts.enable_kvm is None:
-      self.enable_kvm = os.path.exists('/dev/kvm')
+    if self.qemu_arch == VM.ARCH_X86_64:
+      if opts.enable_kvm is None:
+        self.enable_kvm = os.path.exists('/dev/kvm')
+      else:
+        self.enable_kvm = opts.enable_kvm
     else:
-      self.enable_kvm = opts.enable_kvm
+      self.enable_kvm = False
     self.copy_on_write = opts.copy_on_write
     # We don't need sudo access for software emulation or if /dev/kvm is
     # writeable.
@@ -197,6 +213,8 @@
     self.kvm_pipe_in = '%s.in' % self.kvm_monitor  # to KVM
     self.kvm_pipe_out = '%s.out' % self.kvm_monitor  # from KVM
     self.kvm_serial = '%s.serial' % self.kvm_monitor
+    self.flash0_file = os.path.join(self.vm_dir, 'flash0.img')
+    self.flash1_file = os.path.join(self.vm_dir, 'flash1.img')
 
     self.copy_image_on_shutdown = False
     self.image_copy_dir = None
@@ -216,6 +234,58 @@
       assert not os.path.islink(self.vm_dir), error_str
       assert os.stat(self.vm_dir).st_uid == os.getuid(), error_str
 
+  def _CreatePflashFiles(self):
+    """Creates parallel flash images in the temporary VM dir.
+
+    One image is used for the UEFI firmware, the other as non-volatile
+    storage for UEFI variables.
+
+    These images will get removed on VM shutdown.
+
+    Returns:
+      Nothing
+    """
+    standard_uefi_paths = {
+        VM.ARCH_X86_64: [
+            '/usr/share/qemu/edk2-x86_64-code.fd',
+            '/usr/share/OVMF/OVMF_CODE_4M.fd',
+        ],
+        VM.ARCH_AARCH64: [
+            '/usr/share/qemu/edk2-aarch64-code.fd',
+            '/usr/share/qemu-efi-aarch64/QEMU_EFI.fd',
+        ],
+    }
+    uefi_path = None
+    for p in standard_uefi_paths[self.qemu_arch]:
+      if os.path.exists(p):
+        uefi_path = p
+        break
+
+    if not uefi_path:
+      raise VMError('EDK2 QEMU firmware not found.')
+
+    if self.qemu_arch == VM.ARCH_AARCH64:
+      # UEFI firmware for ARM64 has 64Mb pflash size hardcoded
+      flash_size = 64
+      cros_build_lib.run(
+          ['dd', 'if=%s' % uefi_path, 'of=%s' % self.flash0_file,
+           'count=1', 'bs=%dM' % (flash_size), 'conv=sync',
+          ], dryrun=self.dryrun)
+    elif self.qemu_arch == VM.ARCH_X86_64:
+      # X86 UEFI firmware allows up to 8Mb code + vars size combined
+      flash_size = 4
+      # X86 UEFI also sensitive to the pflash image size and shouldn't
+      # be padded to a larger boundary.
+      cros_build_lib.run(
+          ['dd', 'if=%s' % uefi_path, 'of=%s' % self.flash0_file,
+           'bs=4K', 'conv=sync',
+          ], dryrun=self.dryrun)
+    logging.info('flash0 image created at %s.', self.flash0_file)
+
+    with open(self.flash1_file, 'wb+') as f:
+      f.truncate(flash_size * 1024 * 1024)
+    logging.info('flash1 image created at %s.', self.flash1_file)
+
   def _CreateQcow2Image(self):
     """Creates a qcow2-formatted image in the temporary VM dir.
 
@@ -275,7 +345,10 @@
 
   def _SetQemuPath(self):
     """Find a suitable Qemu executable."""
-    qemu_exe = 'qemu-system-x86_64'
+    if self.qemu_arch == VM.ARCH_X86_64:
+      qemu_exe = 'qemu-system-x86_64'
+    elif self.qemu_arch == VM.ARCH_AARCH64:
+      qemu_exe = 'qemu-system-aarch64'
     # Newer CrOS qemu builds provide a standalone version under libexec.
     qemu_wrapper_path = os.path.join('usr/libexec/qemu/bin', qemu_exe)
     qemu_exe_path = os.path.join('usr/bin', qemu_exe)
@@ -414,8 +487,10 @@
       image_format: Format of the image.
     """
     # Append 'check' to warn if the requested CPU is not fully supported.
-    if 'check' not in self.qemu_cpu.split(','):
-      self.qemu_cpu += ',check'
+    logging.info('CPU: %s', str(self.qemu_cpu))
+    if self.qemu_arch == VM.ARCH_X86_64:
+      if 'check' not in self.qemu_cpu.split(','):
+        self.qemu_cpu += ',check'
     # Append 'vmx=on' if the host supports nested virtualization. It can be
     # enabled via 'vmx+' or 'vmx=on' (or similarly disabled) so just test for
     # the presence of 'vmx'. For more details, see:
@@ -433,21 +508,36 @@
       qemu_args += ['-L', self.qemu_bios_path]
 
     qemu_args += [
-        '-m', self.qemu_m, '-smp', str(self.qemu_smp), '-vga', 'virtio',
+        '-m', self.qemu_m, '-smp', str(self.qemu_smp),
         '-daemonize',
         '-pidfile', self.pidfile,
         '-chardev', 'pipe,id=control_pipe,path=%s' % self.kvm_monitor,
         '-serial', 'file:%s' % self.kvm_serial,
         '-mon', 'chardev=control_pipe',
-        '-cpu', self.qemu_cpu,
-        '-usb', '-device', 'usb-tablet',
         '-device', 'virtio-net,netdev=eth0',
         '-device', 'virtio-scsi-pci,id=scsi',
         '-device', 'virtio-rng',
-        '-device', 'scsi-hd,drive=hd',
+        '-device', 'scsi-hd,drive=hd,bootindex=0',
         '-drive', 'if=none,id=hd,file=%s,cache=unsafe,format=%s'
         % (image_path, image_format),
     ]
+    if self.qemu_arch == VM.ARCH_X86_64:
+      qemu_args += [
+          '-vga', 'virtio',
+          '-usb', '-device', 'usb-tablet',
+          '-drive', 'file=%s,if=pflash,format=raw,unit=0,readonly=on'
+          % self.flash0_file,
+          '-drive', 'file=%s,if=pflash,format=raw,unit=1'
+          % self.flash1_file,
+      ]
+    if self.qemu_arch == VM.ARCH_AARCH64:
+      qemu_args += [
+          '-M', 'virt',
+          '-pflash', self.flash0_file,
+          '-pflash', self.flash1_file,
+      ]
+    if self.qemu_cpu:
+      qemu_args += ['-cpu', self.qemu_cpu]
     # netdev args, including hostfwds.
     netdev_args = ('user,id=eth0,net=10.0.2.0/27,hostfwd=tcp:%s:%d-:%d'
                    % (remote_access.LOCALHOST_IP, self.ssh_port,
@@ -486,6 +576,8 @@
       logging.debug('Start VM, attempt #%d', attempt)
 
       self._CreateVMDir()
+      self._CreatePflashFiles()
+
       image_path = self.image_path
       image_format = self.image_format
       if self.copy_on_write:
@@ -641,7 +733,7 @@
     # utility-process, 3 renderers.
     _WaitForProc('chrome', 8)
 
-  def WaitForBoot(self, max_retry=3, sleep=5):
+  def WaitForBoot(self, max_retry=5, sleep=5):
     """Wait for the VM to boot up.
 
     Wait for ssh connection to become active, and wait for all expected chrome
@@ -653,9 +745,10 @@
 
     super().WaitForBoot(sleep=sleep, max_retry=max_retry)
 
+    # XXX: this seems to be chromeos-secific, WaitForProcs waits for chrome
     # Chrome can take a while to start with software emulation.
-    if not self.enable_kvm:
-      self._WaitForProcs()
+    # if not self.enable_kvm:
+    #   self._WaitForProcs()
 
   @staticmethod
   def GetParser():
@@ -683,11 +776,11 @@
     parser.add_argument('--qemu-smp', type=int, default='0',
                         help='SMP argument that will be passed to qemu. (0 '
                              'means auto-detection.)')
-    # TODO(pwang): replace SandyBridge to Haswell-noTSX once lab machine
-    # running VMTest all migrate to GCE.
     parser.add_argument('--qemu-cpu', type=str,
-                        default='SandyBridge,-invpcid,-tsc-deadline',
                         help='CPU argument that will be passed to qemu.')
+    parser.add_argument('--qemu-arch', type=str, default=VM.ARCH_X86_64,
+                        choices=(VM.ARCH_AARCH64, VM.ARCH_X86_64),
+                        help='VM architecture: ')
     parser.add_argument('--qemu-bios-path', type='path',
                         help='Path of directory with qemu bios files.')
     parser.add_argument('--qemu-hostfwd', action='append',