1*4882a593Smuzhiyunimport pexpect 2*4882a593Smuzhiyun 3*4882a593Smuzhiyunimport infra 4*4882a593Smuzhiyun 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunclass Emulator(object): 7*4882a593Smuzhiyun 8*4882a593Smuzhiyun def __init__(self, builddir, downloaddir, logtofile, timeout_multiplier): 9*4882a593Smuzhiyun self.qemu = None 10*4882a593Smuzhiyun self.downloaddir = downloaddir 11*4882a593Smuzhiyun self.logfile = infra.open_log_file(builddir, "run", logtofile) 12*4882a593Smuzhiyun # We use elastic runners on the cloud to runs our tests. Those runners 13*4882a593Smuzhiyun # can take a long time to run the emulator. Use a timeout multiplier 14*4882a593Smuzhiyun # when running the tests to avoid sporadic failures. 15*4882a593Smuzhiyun self.timeout_multiplier = timeout_multiplier 16*4882a593Smuzhiyun 17*4882a593Smuzhiyun # Start Qemu to boot the system 18*4882a593Smuzhiyun # 19*4882a593Smuzhiyun # arch: Qemu architecture to use 20*4882a593Smuzhiyun # 21*4882a593Smuzhiyun # kernel: path to the kernel image, or the special string 22*4882a593Smuzhiyun # 'builtin'. 'builtin' means a pre-built kernel image will be 23*4882a593Smuzhiyun # downloaded from ARTEFACTS_URL and suitable options are 24*4882a593Smuzhiyun # automatically passed to qemu and added to the kernel cmdline. So 25*4882a593Smuzhiyun # far only armv5, armv7 and i386 builtin kernels are available. 26*4882a593Smuzhiyun # If None, then no kernel is used, and we assume a bootable device 27*4882a593Smuzhiyun # will be specified. 28*4882a593Smuzhiyun # 29*4882a593Smuzhiyun # kernel_cmdline: array of kernel arguments to pass to Qemu -append option 30*4882a593Smuzhiyun # 31*4882a593Smuzhiyun # options: array of command line options to pass to Qemu 32*4882a593Smuzhiyun # 33*4882a593Smuzhiyun def boot(self, arch, kernel=None, kernel_cmdline=None, options=None): 34*4882a593Smuzhiyun if arch in ["armv7", "armv5"]: 35*4882a593Smuzhiyun qemu_arch = "arm" 36*4882a593Smuzhiyun else: 37*4882a593Smuzhiyun qemu_arch = arch 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun qemu_cmd = ["qemu-system-{}".format(qemu_arch), 40*4882a593Smuzhiyun "-serial", "stdio", 41*4882a593Smuzhiyun "-display", "none", 42*4882a593Smuzhiyun "-m", "256"] 43*4882a593Smuzhiyun 44*4882a593Smuzhiyun if options: 45*4882a593Smuzhiyun qemu_cmd += options 46*4882a593Smuzhiyun 47*4882a593Smuzhiyun if kernel_cmdline is None: 48*4882a593Smuzhiyun kernel_cmdline = [] 49*4882a593Smuzhiyun 50*4882a593Smuzhiyun if kernel: 51*4882a593Smuzhiyun if kernel == "builtin": 52*4882a593Smuzhiyun if arch in ["armv7", "armv5"]: 53*4882a593Smuzhiyun kernel_cmdline.append("console=ttyAMA0") 54*4882a593Smuzhiyun 55*4882a593Smuzhiyun if arch == "armv7": 56*4882a593Smuzhiyun kernel = infra.download(self.downloaddir, 57*4882a593Smuzhiyun "kernel-vexpress-5.10.7") 58*4882a593Smuzhiyun dtb = infra.download(self.downloaddir, 59*4882a593Smuzhiyun "vexpress-v2p-ca9-5.10.7.dtb") 60*4882a593Smuzhiyun qemu_cmd += ["-dtb", dtb] 61*4882a593Smuzhiyun qemu_cmd += ["-M", "vexpress-a9"] 62*4882a593Smuzhiyun elif arch == "armv5": 63*4882a593Smuzhiyun kernel = infra.download(self.downloaddir, 64*4882a593Smuzhiyun "kernel-versatile-5.10.7") 65*4882a593Smuzhiyun dtb = infra.download(self.downloaddir, 66*4882a593Smuzhiyun "versatile-pb-5.10.7.dtb") 67*4882a593Smuzhiyun qemu_cmd += ["-dtb", dtb] 68*4882a593Smuzhiyun qemu_cmd += ["-M", "versatilepb"] 69*4882a593Smuzhiyun qemu_cmd += ["-device", "virtio-rng-pci"] 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun qemu_cmd += ["-kernel", kernel] 72*4882a593Smuzhiyun 73*4882a593Smuzhiyun if kernel_cmdline: 74*4882a593Smuzhiyun qemu_cmd += ["-append", " ".join(kernel_cmdline)] 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun self.logfile.write("> starting qemu with '%s'\n" % " ".join(qemu_cmd)) 77*4882a593Smuzhiyun self.qemu = pexpect.spawn(qemu_cmd[0], qemu_cmd[1:], 78*4882a593Smuzhiyun timeout=5 * self.timeout_multiplier, 79*4882a593Smuzhiyun encoding='utf-8', 80*4882a593Smuzhiyun codec_errors='replace', 81*4882a593Smuzhiyun env={"QEMU_AUDIO_DRV": "none"}) 82*4882a593Smuzhiyun # We want only stdout into the log to avoid double echo 83*4882a593Smuzhiyun self.qemu.logfile_read = self.logfile 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun # Wait for the login prompt to appear, and then login as root with 86*4882a593Smuzhiyun # the provided password, or no password if not specified. 87*4882a593Smuzhiyun def login(self, password=None): 88*4882a593Smuzhiyun # The login prompt can take some time to appear when running multiple 89*4882a593Smuzhiyun # instances in parallel, so set the timeout to a large value 90*4882a593Smuzhiyun index = self.qemu.expect(["buildroot login:", pexpect.TIMEOUT], 91*4882a593Smuzhiyun timeout=60 * self.timeout_multiplier) 92*4882a593Smuzhiyun if index != 0: 93*4882a593Smuzhiyun self.logfile.write("==> System does not boot") 94*4882a593Smuzhiyun raise SystemError("System does not boot") 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun self.qemu.sendline("root") 97*4882a593Smuzhiyun if password: 98*4882a593Smuzhiyun self.qemu.expect("Password:") 99*4882a593Smuzhiyun self.qemu.sendline(password) 100*4882a593Smuzhiyun index = self.qemu.expect(["# ", pexpect.TIMEOUT]) 101*4882a593Smuzhiyun if index != 0: 102*4882a593Smuzhiyun raise SystemError("Cannot login") 103*4882a593Smuzhiyun self.run("dmesg -n 1") 104*4882a593Smuzhiyun # Prevent the shell from wrapping the commands at 80 columns. 105*4882a593Smuzhiyun self.run("stty columns 29999") 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun # Run the given 'cmd' with a 'timeout' on the target 108*4882a593Smuzhiyun # return a tuple (output, exit_code) 109*4882a593Smuzhiyun def run(self, cmd, timeout=-1): 110*4882a593Smuzhiyun self.qemu.sendline(cmd) 111*4882a593Smuzhiyun if timeout != -1: 112*4882a593Smuzhiyun timeout *= self.timeout_multiplier 113*4882a593Smuzhiyun self.qemu.expect("# ", timeout=timeout) 114*4882a593Smuzhiyun # Remove double carriage return from qemu stdout so str.splitlines() 115*4882a593Smuzhiyun # works as expected. 116*4882a593Smuzhiyun output = self.qemu.before.replace("\r\r", "\r").splitlines()[1:] 117*4882a593Smuzhiyun 118*4882a593Smuzhiyun self.qemu.sendline("echo $?") 119*4882a593Smuzhiyun self.qemu.expect("# ") 120*4882a593Smuzhiyun exit_code = self.qemu.before.splitlines()[2] 121*4882a593Smuzhiyun exit_code = int(exit_code) 122*4882a593Smuzhiyun 123*4882a593Smuzhiyun return output, exit_code 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun def stop(self): 126*4882a593Smuzhiyun if self.qemu is None: 127*4882a593Smuzhiyun return 128*4882a593Smuzhiyun self.qemu.terminate(force=True) 129