1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# Copyright (C) 2015 Intel Corporation 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# SPDX-License-Identifier: MIT 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun 7*4882a593Smuzhiyun# This module provides a class for starting qemu images of poky tiny. 8*4882a593Smuzhiyun# It's used by testimage.bbclass. 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport subprocess 11*4882a593Smuzhiyunimport os 12*4882a593Smuzhiyunimport time 13*4882a593Smuzhiyunimport signal 14*4882a593Smuzhiyunimport re 15*4882a593Smuzhiyunimport socket 16*4882a593Smuzhiyunimport select 17*4882a593Smuzhiyunimport bb 18*4882a593Smuzhiyunfrom .qemurunner import QemuRunner 19*4882a593Smuzhiyun 20*4882a593Smuzhiyunclass QemuTinyRunner(QemuRunner): 21*4882a593Smuzhiyun 22*4882a593Smuzhiyun def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, kernel, boottime, logger, tmpfsdir=None): 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun # Popen object for runqemu 25*4882a593Smuzhiyun self.runqemu = None 26*4882a593Smuzhiyun # pid of the qemu process that runqemu will start 27*4882a593Smuzhiyun self.qemupid = None 28*4882a593Smuzhiyun # target ip - from the command line 29*4882a593Smuzhiyun self.ip = None 30*4882a593Smuzhiyun # host ip - where qemu is running 31*4882a593Smuzhiyun self.server_ip = None 32*4882a593Smuzhiyun 33*4882a593Smuzhiyun self.machine = machine 34*4882a593Smuzhiyun self.rootfs = rootfs 35*4882a593Smuzhiyun self.display = display 36*4882a593Smuzhiyun self.tmpdir = tmpdir 37*4882a593Smuzhiyun self.deploy_dir_image = deploy_dir_image 38*4882a593Smuzhiyun self.logfile = logfile 39*4882a593Smuzhiyun self.boottime = boottime 40*4882a593Smuzhiyun self.tmpfsdir = tmpfsdir 41*4882a593Smuzhiyun 42*4882a593Smuzhiyun self.runqemutime = 60 43*4882a593Smuzhiyun self.socketfile = "console.sock" 44*4882a593Smuzhiyun self.server_socket = None 45*4882a593Smuzhiyun self.kernel = kernel 46*4882a593Smuzhiyun self.logger = logger 47*4882a593Smuzhiyun 48*4882a593Smuzhiyun 49*4882a593Smuzhiyun def create_socket(self): 50*4882a593Smuzhiyun tries = 3 51*4882a593Smuzhiyun while tries > 0: 52*4882a593Smuzhiyun try: 53*4882a593Smuzhiyun self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 54*4882a593Smuzhiyun self.server_socket.connect(self.socketfile) 55*4882a593Smuzhiyun bb.note("Created listening socket for qemu serial console.") 56*4882a593Smuzhiyun tries = 0 57*4882a593Smuzhiyun except socket.error as msg: 58*4882a593Smuzhiyun self.server_socket.close() 59*4882a593Smuzhiyun bb.fatal("Failed to create listening socket.") 60*4882a593Smuzhiyun tries -= 1 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun def log(self, msg): 63*4882a593Smuzhiyun if self.logfile: 64*4882a593Smuzhiyun with open(self.logfile, "a") as f: 65*4882a593Smuzhiyun f.write("%s" % msg) 66*4882a593Smuzhiyun 67*4882a593Smuzhiyun def start(self, qemuparams = None, ssh=True, extra_bootparams=None, runqemuparams='', discard_writes=True): 68*4882a593Smuzhiyun 69*4882a593Smuzhiyun if self.display: 70*4882a593Smuzhiyun os.environ["DISPLAY"] = self.display 71*4882a593Smuzhiyun else: 72*4882a593Smuzhiyun bb.error("To start qemu I need a X desktop, please set DISPLAY correctly (e.g. DISPLAY=:1)") 73*4882a593Smuzhiyun return False 74*4882a593Smuzhiyun if not os.path.exists(self.rootfs): 75*4882a593Smuzhiyun bb.error("Invalid rootfs %s" % self.rootfs) 76*4882a593Smuzhiyun return False 77*4882a593Smuzhiyun if not os.path.exists(self.tmpdir): 78*4882a593Smuzhiyun bb.error("Invalid TMPDIR path %s" % self.tmpdir) 79*4882a593Smuzhiyun return False 80*4882a593Smuzhiyun else: 81*4882a593Smuzhiyun os.environ["OE_TMPDIR"] = self.tmpdir 82*4882a593Smuzhiyun if not os.path.exists(self.deploy_dir_image): 83*4882a593Smuzhiyun bb.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image) 84*4882a593Smuzhiyun return False 85*4882a593Smuzhiyun else: 86*4882a593Smuzhiyun os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image 87*4882a593Smuzhiyun if self.tmpfsdir: 88*4882a593Smuzhiyun env["RUNQEMU_TMPFS_DIR"] = self.tmpfsdir 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun 91*4882a593Smuzhiyun # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact 92*4882a593Smuzhiyun # badly with screensavers. 93*4882a593Smuzhiyun os.environ["QEMU_DONT_GRAB"] = "1" 94*4882a593Smuzhiyun self.qemuparams = '--append "root=/dev/ram0 console=ttyS0" -nographic -serial unix:%s,server,nowait' % self.socketfile 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun launch_cmd = 'qemu-system-i386 -kernel %s -initrd %s %s' % (self.kernel, self.rootfs, self.qemuparams) 97*4882a593Smuzhiyun self.runqemu = subprocess.Popen(launch_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,preexec_fn=os.setpgrp) 98*4882a593Smuzhiyun 99*4882a593Smuzhiyun bb.note("runqemu started, pid is %s" % self.runqemu.pid) 100*4882a593Smuzhiyun bb.note("waiting at most %s seconds for qemu pid" % self.runqemutime) 101*4882a593Smuzhiyun endtime = time.time() + self.runqemutime 102*4882a593Smuzhiyun while not self.is_alive() and time.time() < endtime: 103*4882a593Smuzhiyun time.sleep(1) 104*4882a593Smuzhiyun 105*4882a593Smuzhiyun if self.is_alive(): 106*4882a593Smuzhiyun bb.note("qemu started - qemu procces pid is %s" % self.qemupid) 107*4882a593Smuzhiyun self.create_socket() 108*4882a593Smuzhiyun else: 109*4882a593Smuzhiyun bb.note("Qemu pid didn't appeared in %s seconds" % self.runqemutime) 110*4882a593Smuzhiyun output = self.runqemu.stdout 111*4882a593Smuzhiyun self.stop() 112*4882a593Smuzhiyun bb.note("Output from runqemu:\n%s" % output.read().decode("utf-8")) 113*4882a593Smuzhiyun return False 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun return self.is_alive() 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun def run_serial(self, command, timeout=60): 118*4882a593Smuzhiyun self.server_socket.sendall(command+'\n') 119*4882a593Smuzhiyun data = '' 120*4882a593Smuzhiyun status = 0 121*4882a593Smuzhiyun stopread = False 122*4882a593Smuzhiyun endtime = time.time()+timeout 123*4882a593Smuzhiyun while time.time()<endtime and not stopread: 124*4882a593Smuzhiyun try: 125*4882a593Smuzhiyun sread, _, _ = select.select([self.server_socket],[],[],1) 126*4882a593Smuzhiyun except InterruptedError: 127*4882a593Smuzhiyun continue 128*4882a593Smuzhiyun for sock in sread: 129*4882a593Smuzhiyun answer = sock.recv(1024) 130*4882a593Smuzhiyun if answer: 131*4882a593Smuzhiyun data += answer 132*4882a593Smuzhiyun else: 133*4882a593Smuzhiyun sock.close() 134*4882a593Smuzhiyun stopread = True 135*4882a593Smuzhiyun if not data: 136*4882a593Smuzhiyun status = 1 137*4882a593Smuzhiyun if not stopread: 138*4882a593Smuzhiyun data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout 139*4882a593Smuzhiyun return (status, str(data)) 140*4882a593Smuzhiyun 141*4882a593Smuzhiyun def find_child(self,parent_pid): 142*4882a593Smuzhiyun # 143*4882a593Smuzhiyun # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd] 144*4882a593Smuzhiyun # 145*4882a593Smuzhiyun ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,pri,ni,command'], stdout=subprocess.PIPE).communicate()[0] 146*4882a593Smuzhiyun processes = ps.decode("utf-8").split('\n') 147*4882a593Smuzhiyun nfields = len(processes[0].split()) - 1 148*4882a593Smuzhiyun pids = {} 149*4882a593Smuzhiyun commands = {} 150*4882a593Smuzhiyun for row in processes[1:]: 151*4882a593Smuzhiyun data = row.split(None, nfields) 152*4882a593Smuzhiyun if len(data) != 3: 153*4882a593Smuzhiyun continue 154*4882a593Smuzhiyun if data[1] not in pids: 155*4882a593Smuzhiyun pids[data[1]] = [] 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun pids[data[1]].append(data[0]) 158*4882a593Smuzhiyun commands[data[0]] = data[2] 159*4882a593Smuzhiyun 160*4882a593Smuzhiyun if parent_pid not in pids: 161*4882a593Smuzhiyun return [] 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun parents = [] 164*4882a593Smuzhiyun newparents = pids[parent_pid] 165*4882a593Smuzhiyun while newparents: 166*4882a593Smuzhiyun next = [] 167*4882a593Smuzhiyun for p in newparents: 168*4882a593Smuzhiyun if p in pids: 169*4882a593Smuzhiyun for n in pids[p]: 170*4882a593Smuzhiyun if n not in parents and n not in next: 171*4882a593Smuzhiyun next.append(n) 172*4882a593Smuzhiyun if p not in parents: 173*4882a593Smuzhiyun parents.append(p) 174*4882a593Smuzhiyun newparents = next 175*4882a593Smuzhiyun #print("Children matching %s:" % str(parents)) 176*4882a593Smuzhiyun for p in parents: 177*4882a593Smuzhiyun # Need to be careful here since runqemu runs "ldd qemu-system-xxxx" 178*4882a593Smuzhiyun # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" 179*4882a593Smuzhiyun basecmd = commands[p].split()[0] 180*4882a593Smuzhiyun basecmd = os.path.basename(basecmd) 181*4882a593Smuzhiyun if "qemu-system" in basecmd and "-serial unix" in commands[p]: 182*4882a593Smuzhiyun return [int(p),commands[p]] 183