blob: 9b6d330f1f0d8e380712bd6a932ba50a980fde55 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001#!/usr/bin/env python3
2
Patrick Williamsc124f4f2015-09-15 14:41:29 -05003# Handle running OE images standalone with QEMU
4#
5# Copyright (C) 2006-2011 Linux Foundation
Patrick Williamsc0f7c042017-02-23 20:41:17 -06006# Copyright (c) 2016 Wind River Systems, Inc.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021import os
22import sys
23import logging
24import subprocess
25import re
26import fcntl
27import shutil
28import glob
29import configparser
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050030
Patrick Williamsc0f7c042017-02-23 20:41:17 -060031class OEPathError(Exception):
32 """Custom Exception to give better guidance on missing binaries"""
33 def __init__(self, message):
34 self.message = "In order for this script to dynamically infer paths\n \
35kernels or filesystem images, you either need bitbake in your PATH\n \
36or to source oe-init-build-env before running this script.\n\n \
37Dynamic path inference can be avoided by passing a *.qemuboot.conf to\n \
38runqemu, i.e. `runqemu /path/to/my-image-name.qemuboot.conf`\n\n %s" % message
39
40
41def create_logger():
42 logger = logging.getLogger('runqemu')
43 logger.setLevel(logging.INFO)
44
45 # create console handler and set level to debug
46 ch = logging.StreamHandler()
47 ch.setLevel(logging.INFO)
48
49 # create formatter
50 formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
51
52 # add formatter to ch
53 ch.setFormatter(formatter)
54
55 # add ch to logger
56 logger.addHandler(ch)
57
58 return logger
59
60logger = create_logger()
61
62def print_usage():
63 print("""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050064Usage: you can run this script with any valid combination
65of the following environment variables (in any order):
66 KERNEL - the kernel image file to use
67 ROOTFS - the rootfs image file or nfsroot directory to use
68 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified)
69 Simplified QEMU command-line options can be passed with:
Patrick Williamsc0f7c042017-02-23 20:41:17 -060070 nographic - disable video console
71 serial - enable a serial console on /dev/ttyS0
72 slirp - enable user networking, no root privileges is required
73 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required)
74 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050075 publicvnc - enable a VNC server open to all hosts
Patrick Williamsc0f7c042017-02-23 20:41:17 -060076 audio - enable audio
Brad Bishop6e60e8b2018-02-01 10:27:11 -050077 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI
Patrick Williamsc0f7c042017-02-23 20:41:17 -060078 tcpserial=<port> - specify tcp serial port number
79 biosdir=<dir> - specify custom bios dir
80 biosfilename=<filename> - specify bios filename
81 qemuparams=<xyz> - specify custom parameters to QEMU
82 bootparams=<xyz> - specify custom kernel parameters during boot
Brad Bishop6e60e8b2018-02-01 10:27:11 -050083 help, -h, --help: print this text
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050084
85Examples:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050086 runqemu
Patrick Williamsc0f7c042017-02-23 20:41:17 -060087 runqemu qemuarm
88 runqemu tmp/deploy/images/qemuarm
Brad Bishop6e60e8b2018-02-01 10:27:11 -050089 runqemu tmp/deploy/images/qemux86/<qemuboot.conf>
Patrick Williamsc0f7c042017-02-23 20:41:17 -060090 runqemu qemux86-64 core-image-sato ext4
91 runqemu qemux86-64 wic-image-minimal wic
92 runqemu path/to/bzImage-qemux86.bin path/to/nfsrootdir/ serial
93 runqemu qemux86 iso/hddimg/vmdk/qcow2/vdi/ramfs/cpio.gz...
94 runqemu qemux86 qemuparams="-m 256"
95 runqemu qemux86 bootparams="psplash=false"
96 runqemu path/to/<image>-<machine>.vmdk
97 runqemu path/to/<image>-<machine>.wic
98""")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600100def check_tun():
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500101 """Check /dev/net/tun"""
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600102 dev_tun = '/dev/net/tun'
103 if not os.path.exists(dev_tun):
104 raise Exception("TUN control device %s is unavailable; you may need to enable TUN (e.g. sudo modprobe tun)" % dev_tun)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500105
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600106 if not os.access(dev_tun, os.W_OK):
107 raise Exception("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600109def check_libgl(qemu_bin):
110 cmd = 'ldd %s' % qemu_bin
111 logger.info('Running %s...' % cmd)
112 need_gl = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
113 if re.search('libGLU', need_gl):
114 # We can't run without a libGL.so
115 libgl = False
116 check_files = (('/usr/lib/libGL.so', '/usr/lib/libGLU.so'), \
117 ('/usr/lib64/libGL.so', '/usr/lib64/libGLU.so'), \
118 ('/usr/lib/*-linux-gnu/libGL.so', '/usr/lib/*-linux-gnu/libGLU.so'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600120 for (f1, f2) in check_files:
121 if re.search('\*', f1):
122 for g1 in glob.glob(f1):
123 if libgl:
124 break
125 if os.path.exists(g1):
126 for g2 in glob.glob(f2):
127 if os.path.exists(g2):
128 libgl = True
129 break
130 if libgl:
131 break
132 else:
133 if os.path.exists(f1) and os.path.exists(f2):
134 libgl = True
135 break
136 if not libgl:
137 logger.error("You need libGL.so and libGLU.so to exist in your library path to run the QEMU emulator.")
138 logger.error("Ubuntu package names are: libgl1-mesa-dev and libglu1-mesa-dev.")
139 logger.error("Fedora package names are: mesa-libGL-devel mesa-libGLU-devel.")
140 raise Exception('%s requires libGLU, but not found' % qemu_bin)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600142def get_first_file(cmds):
143 """Return first file found in wildcard cmds"""
144 for cmd in cmds:
145 all_files = glob.glob(cmd)
146 if all_files:
147 for f in all_files:
148 if not os.path.isdir(f):
149 return f
150 return ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500151
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500152def check_free_port(host, port):
153 """ Check whether the port is free or not """
154 import socket
155 from contextlib import closing
156
157 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
158 if sock.connect_ex((host, port)) == 0:
159 # Port is open, so not free
160 return False
161 else:
162 # Port is not open, so free
163 return True
164
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600165class BaseConfig(object):
166 def __init__(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500167 # The self.d saved vars from self.set(), part of them are from qemuboot.conf
168 self.d = {'QB_KERNEL_ROOT': '/dev/vda'}
169
170 # Supported env vars, add it here if a var can be got from env,
171 # and don't use os.getenv in the code.
172 self.env_vars = ('MACHINE',
173 'ROOTFS',
174 'KERNEL',
175 'DEPLOY_DIR_IMAGE',
176 'OE_TMPDIR',
177 'OECORE_NATIVE_SYSROOT',
178 )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500179
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180 self.qemu_opt = ''
181 self.qemu_opt_script = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600182 self.clean_nfs_dir = False
183 self.nfs_server = ''
184 self.rootfs = ''
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500185 # File name(s) of a OVMF firmware file or variable store,
186 # to be added with -drive if=pflash.
187 # Found in the same places as the rootfs, with or without one of
188 # these suffices: qcow2, bin.
189 # Setting one also adds "-vga std" because that is all that
190 # OVMF supports.
191 self.ovmf_bios = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600192 self.qemuboot = ''
193 self.qbconfload = False
194 self.kernel = ''
195 self.kernel_cmdline = ''
196 self.kernel_cmdline_script = ''
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500197 self.bootparams = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600198 self.dtb = ''
199 self.fstype = ''
200 self.kvm_enabled = False
201 self.vhost_enabled = False
202 self.slirp_enabled = False
203 self.nfs_instance = 0
204 self.nfs_running = False
205 self.serialstdio = False
206 self.cleantap = False
207 self.saved_stty = ''
208 self.audio_enabled = False
209 self.tcpserial_portnum = ''
210 self.custombiosdir = ''
211 self.lock = ''
212 self.lock_descriptor = ''
213 self.bitbake_e = ''
214 self.snapshot = False
215 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', 'cpio.gz', 'cpio', 'ramfs')
216 self.vmtypes = ('hddimg', 'hdddirect', 'wic', 'vmdk', 'qcow2', 'vdi', 'iso')
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500217 self.network_device = "-device e1000,netdev=net0,mac=@MAC@"
218 # Use different mac section for tap and slirp to avoid
219 # conflicts, e.g., when one is running with tap, the other is
220 # running with slirp.
221 # The last section is dynamic, which is for avoiding conflicts,
222 # when multiple qemus are running, e.g., when multiple tap or
223 # slirp qemus are running.
224 self.mac_tap = "52:54:00:12:34:"
225 self.mac_slirp = "52:54:00:12:35:"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500226
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600227 def acquire_lock(self):
228 logger.info("Acquiring lockfile %s..." % self.lock)
229 try:
230 self.lock_descriptor = open(self.lock, 'w')
231 fcntl.flock(self.lock_descriptor, fcntl.LOCK_EX|fcntl.LOCK_NB)
232 except Exception as e:
233 logger.info("Acquiring lockfile %s failed: %s" % (self.lock, e))
234 if self.lock_descriptor:
235 self.lock_descriptor.close()
236 return False
237 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500238
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600239 def release_lock(self):
240 fcntl.flock(self.lock_descriptor, fcntl.LOCK_UN)
241 self.lock_descriptor.close()
242 os.remove(self.lock)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500243
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600244 def get(self, key):
245 if key in self.d:
246 return self.d.get(key)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500247 elif os.getenv(key):
248 return os.getenv(key)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600249 else:
250 return ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600252 def set(self, key, value):
253 self.d[key] = value
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600255 def is_deploy_dir_image(self, p):
256 if os.path.isdir(p):
257 if not re.search('.qemuboot.conf$', '\n'.join(os.listdir(p)), re.M):
258 logger.info("Can't find required *.qemuboot.conf in %s" % p)
259 return False
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500260 if not any(map(lambda name: '-image-' in name, os.listdir(p))):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261 logger.info("Can't find *-image-* in %s" % p)
262 return False
263 return True
264 else:
265 return False
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500266
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600267 def check_arg_fstype(self, fst):
268 """Check and set FSTYPE"""
269 if fst not in self.fstypes + self.vmtypes:
270 logger.warn("Maybe unsupported FSTYPE: %s" % fst)
271 if not self.fstype or self.fstype == fst:
272 if fst == 'ramfs':
273 fst = 'cpio.gz'
274 self.fstype = fst
275 else:
276 raise Exception("Conflicting: FSTYPE %s and %s" % (self.fstype, fst))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500277
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600278 def set_machine_deploy_dir(self, machine, deploy_dir_image):
279 """Set MACHINE and DEPLOY_DIR_IMAGE"""
280 logger.info('MACHINE: %s' % machine)
281 self.set("MACHINE", machine)
282 logger.info('DEPLOY_DIR_IMAGE: %s' % deploy_dir_image)
283 self.set("DEPLOY_DIR_IMAGE", deploy_dir_image)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600285 def check_arg_nfs(self, p):
286 if os.path.isdir(p):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500287 self.rootfs = p
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600288 else:
289 m = re.match('(.*):(.*)', p)
290 self.nfs_server = m.group(1)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500291 self.rootfs = m.group(2)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600292 self.check_arg_fstype('nfs')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500293
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600294 def check_arg_path(self, p):
295 """
296 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf
297 - Check whether is a kernel file
298 - Check whether is a image file
299 - Check whether it is a nfs dir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500300 - Check whether it is a OVMF flash file
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600301 """
302 if p.endswith('.qemuboot.conf'):
303 self.qemuboot = p
304 self.qbconfload = True
305 elif re.search('\.bin$', p) or re.search('bzImage', p) or \
306 re.search('zImage', p) or re.search('vmlinux', p) or \
307 re.search('fitImage', p) or re.search('uImage', p):
308 self.kernel = p
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500309 elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600310 self.rootfs = p
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500311 # Check filename against self.fstypes can hanlde <file>.cpio.gz,
312 # otherwise, its type would be "gz", which is incorrect.
313 fst = ""
314 for t in self.fstypes:
315 if p.endswith(t):
316 fst = t
317 break
318 if not fst:
319 m = re.search('.*\.(.*)$', self.rootfs)
320 if m:
321 fst = m.group(1)
322 if fst:
323 self.check_arg_fstype(fst)
324 qb = re.sub('\.' + fst + "$", '', self.rootfs)
325 qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326 if os.path.exists(qb):
327 self.qemuboot = qb
328 self.qbconfload = True
329 else:
330 logger.warn("%s doesn't exist" % qb)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600331 else:
332 raise Exception("Can't find FSTYPE from: %s" % p)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500333
334 elif os.path.isdir(p) or re.search(':', p) and re.search('/', p):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600335 if self.is_deploy_dir_image(p):
336 logger.info('DEPLOY_DIR_IMAGE: %s' % p)
337 self.set("DEPLOY_DIR_IMAGE", p)
338 else:
339 logger.info("Assuming %s is an nfs rootfs" % p)
340 self.check_arg_nfs(p)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500341 elif os.path.basename(p).startswith('ovmf'):
342 self.ovmf_bios.append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600343 else:
344 raise Exception("Unknown path arg %s" % p)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500345
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600346 def check_arg_machine(self, arg):
347 """Check whether it is a machine"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500348 if self.get('MACHINE') == arg:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600349 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500350 elif self.get('MACHINE') and self.get('MACHINE') != arg:
351 raise Exception("Maybe conflicted MACHINE: %s vs %s" % (self.get('MACHINE'), arg))
352 elif re.search('/', arg):
353 raise Exception("Unknown arg: %s" % arg)
354
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600355 logger.info('Assuming MACHINE = %s' % arg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500356
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600357 # if we're running under testimage, or similarly as a child
358 # of an existing bitbake invocation, we can't invoke bitbake
359 # to validate the MACHINE setting and must assume it's correct...
360 # FIXME: testimage.bbclass exports these two variables into env,
361 # are there other scenarios in which we need to support being
362 # invoked by bitbake?
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500363 deploy = self.get('DEPLOY_DIR_IMAGE')
364 bbchild = deploy and self.get('OE_TMPDIR')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600365 if bbchild:
366 self.set_machine_deploy_dir(arg, deploy)
367 return
368 # also check whether we're running under a sourced toolchain
369 # environment file
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500370 if self.get('OECORE_NATIVE_SYSROOT'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600371 self.set("MACHINE", arg)
372 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500373
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600374 cmd = 'MACHINE=%s bitbake -e' % arg
375 logger.info('Running %s...' % cmd)
376 self.bitbake_e = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
377 # bitbake -e doesn't report invalid MACHINE as an error, so
378 # let's check DEPLOY_DIR_IMAGE to make sure that it is a valid
379 # MACHINE.
380 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
381 if s:
382 deploy_dir_image = s.group(1)
383 else:
384 raise Exception("bitbake -e %s" % self.bitbake_e)
385 if self.is_deploy_dir_image(deploy_dir_image):
386 self.set_machine_deploy_dir(arg, deploy_dir_image)
387 else:
388 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image)
389 self.set("MACHINE", arg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500390
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600391 def check_args(self):
392 unknown_arg = ""
393 for arg in sys.argv[1:]:
394 if arg in self.fstypes + self.vmtypes:
395 self.check_arg_fstype(arg)
396 elif arg == 'nographic':
397 self.qemu_opt_script += ' -nographic'
398 self.kernel_cmdline_script += ' console=ttyS0'
399 elif arg == 'serial':
400 self.kernel_cmdline_script += ' console=ttyS0'
401 self.serialstdio = True
402 elif arg == 'audio':
403 logger.info("Enabling audio in qemu")
404 logger.info("Please install sound drivers in linux host")
405 self.audio_enabled = True
406 elif arg == 'kvm':
407 self.kvm_enabled = True
408 elif arg == 'kvm-vhost':
409 self.vhost_enabled = True
410 elif arg == 'slirp':
411 self.slirp_enabled = True
412 elif arg == 'snapshot':
413 self.snapshot = True
414 elif arg == 'publicvnc':
415 self.qemu_opt_script += ' -vnc :0'
416 elif arg.startswith('tcpserial='):
417 self.tcpserial_portnum = arg[len('tcpserial='):]
418 elif arg.startswith('biosdir='):
419 self.custombiosdir = arg[len('biosdir='):]
420 elif arg.startswith('biosfilename='):
421 self.qemu_opt_script += ' -bios %s' % arg[len('biosfilename='):]
422 elif arg.startswith('qemuparams='):
423 self.qemu_opt_script += ' %s' % arg[len('qemuparams='):]
424 elif arg.startswith('bootparams='):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500425 self.bootparams = arg[len('bootparams='):]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600426 elif os.path.exists(arg) or (re.search(':', arg) and re.search('/', arg)):
427 self.check_arg_path(os.path.abspath(arg))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500428 elif re.search(r'-image-|-image$', arg):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600429 # Lazy rootfs
430 self.rootfs = arg
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500431 elif arg.startswith('ovmf'):
432 self.ovmf_bios.append(arg)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600433 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500434 # At last, assume it is the MACHINE
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600435 if (not unknown_arg) or unknown_arg == arg:
436 unknown_arg = arg
437 else:
438 raise Exception("Can't handle two unknown args: %s %s" % (unknown_arg, arg))
439 # Check to make sure it is a valid machine
440 if unknown_arg:
441 if self.get('MACHINE') == unknown_arg:
442 return
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500443 if self.get('DEPLOY_DIR_IMAGE'):
444 machine = os.path.basename(self.get('DEPLOY_DIR_IMAGE'))
445 if unknown_arg == machine:
446 self.set("MACHINE", machine)
447 return
448
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600449 self.check_arg_machine(unknown_arg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500451 if not self.get('DEPLOY_DIR_IMAGE'):
452 self.load_bitbake_env()
453 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
454 if s:
455 self.set("DEPLOY_DIR_IMAGE", s.group(1))
456
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600457 def check_kvm(self):
458 """Check kvm and kvm-host"""
459 if not (self.kvm_enabled or self.vhost_enabled):
460 self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'))
461 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500462
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600463 if not self.get('QB_CPU_KVM'):
464 raise Exception("QB_CPU_KVM is NULL, this board doesn't support kvm")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500465
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600466 self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'))
467 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu"
468 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM"
469 dev_kvm = '/dev/kvm'
470 dev_vhost = '/dev/vhost-net'
471 with open('/proc/cpuinfo', 'r') as f:
472 kvm_cap = re.search('vmx|svm', "".join(f.readlines()))
473 if not kvm_cap:
474 logger.error("You are trying to enable KVM on a cpu without VT support.")
475 logger.error("Remove kvm from the command-line, or refer:")
476 raise Exception(yocto_kvm_wiki)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500477
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600478 if not os.path.exists(dev_kvm):
479 logger.error("Missing KVM device. Have you inserted kvm modules?")
480 logger.error("For further help see:")
481 raise Exception(yocto_kvm_wiki)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500482
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600483 if os.access(dev_kvm, os.W_OK|os.R_OK):
484 self.qemu_opt_script += ' -enable-kvm'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500485 if self.get('MACHINE') == "qemux86":
486 # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs
487 # See YOCTO #12301
488 # On 64 bit we use x2apic
489 self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic"
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600490 else:
491 logger.error("You have no read or write permission on /dev/kvm.")
492 logger.error("Please change the ownership of this file as described at:")
493 raise Exception(yocto_kvm_wiki)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500494
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600495 if self.vhost_enabled:
496 if not os.path.exists(dev_vhost):
497 logger.error("Missing virtio net device. Have you inserted vhost-net module?")
498 logger.error("For further help see:")
499 raise Exception(yocto_paravirt_kvm_wiki)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600501 if not os.access(dev_kvm, os.W_OK|os.R_OK):
502 logger.error("You have no read or write permission on /dev/vhost-net.")
503 logger.error("Please change the ownership of this file as described at:")
504 raise Exception(yocto_kvm_wiki)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500505
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600506 def check_fstype(self):
507 """Check and setup FSTYPE"""
508 if not self.fstype:
509 fstype = self.get('QB_DEFAULT_FSTYPE')
510 if fstype:
511 self.fstype = fstype
512 else:
513 raise Exception("FSTYPE is NULL!")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500514
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600515 def check_rootfs(self):
516 """Check and set rootfs"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500517
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500518 if self.fstype == "none":
519 return
520
521 if self.get('ROOTFS'):
522 if not self.rootfs:
523 self.rootfs = self.get('ROOTFS')
524 elif self.get('ROOTFS') != self.rootfs:
525 raise Exception("Maybe conflicted ROOTFS: %s vs %s" % (self.get('ROOTFS'), self.rootfs))
526
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600527 if self.fstype == 'nfs':
528 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500529
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600530 if self.rootfs and not os.path.exists(self.rootfs):
531 # Lazy rootfs
532 self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'),
533 self.rootfs, self.get('MACHINE'),
534 self.fstype)
535 elif not self.rootfs:
536 cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype)
537 cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype)
538 cmds = (cmd_name, cmd_link)
539 self.rootfs = get_first_file(cmds)
540 if not self.rootfs:
541 raise Exception("Failed to find rootfs: %s or %s" % cmds)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600543 if not os.path.exists(self.rootfs):
544 raise Exception("Can't find rootfs: %s" % self.rootfs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500545
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500546 def check_ovmf(self):
547 """Check and set full path for OVMF firmware and variable file(s)."""
548
549 for index, ovmf in enumerate(self.ovmf_bios):
550 if os.path.exists(ovmf):
551 continue
552 for suffix in ('qcow2', 'bin'):
553 path = '%s/%s.%s' % (self.get('DEPLOY_DIR_IMAGE'), ovmf, suffix)
554 if os.path.exists(path):
555 self.ovmf_bios[index] = path
556 break
557 else:
558 raise Exception("Can't find OVMF firmware: %s" % ovmf)
559
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600560 def check_kernel(self):
561 """Check and set kernel, dtb"""
562 # The vm image doesn't need a kernel
563 if self.fstype in self.vmtypes:
564 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500566 # QB_DEFAULT_KERNEL is always a full file path
567 kernel_name = os.path.basename(self.get('QB_DEFAULT_KERNEL'))
568
569 # The user didn't want a kernel to be loaded
570 if kernel_name == "none":
571 return
572
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600573 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
574 if not self.kernel:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500575 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600576 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
577 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
578 cmds = (kernel_match_name, kernel_match_link, kernel_startswith)
579 self.kernel = get_first_file(cmds)
580 if not self.kernel:
581 raise Exception('KERNEL not found: %s, %s or %s' % cmds)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500582
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600583 if not os.path.exists(self.kernel):
584 raise Exception("KERNEL %s not found" % self.kernel)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600586 dtb = self.get('QB_DTB')
587 if dtb:
588 cmd_match = "%s/%s" % (deploy_dir_image, dtb)
589 cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb)
590 cmd_wild = "%s/*.dtb" % deploy_dir_image
591 cmds = (cmd_match, cmd_startswith, cmd_wild)
592 self.dtb = get_first_file(cmds)
593 if not os.path.exists(self.dtb):
594 raise Exception('DTB not found: %s, %s or %s' % cmds)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600596 def check_biosdir(self):
597 """Check custombiosdir"""
598 if not self.custombiosdir:
599 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500600
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600601 biosdir = ""
602 biosdir_native = "%s/%s" % (self.get('STAGING_DIR_NATIVE'), self.custombiosdir)
603 biosdir_host = "%s/%s" % (self.get('STAGING_DIR_HOST'), self.custombiosdir)
604 for i in (self.custombiosdir, biosdir_native, biosdir_host):
605 if os.path.isdir(i):
606 biosdir = i
607 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600609 if biosdir:
610 logger.info("Assuming biosdir is: %s" % biosdir)
611 self.qemu_opt_script += ' -L %s' % biosdir
612 else:
613 logger.error("Custom BIOS directory not found. Tried: %s, %s, and %s" % (self.custombiosdir, biosdir_native, biosdir_host))
614 raise Exception("Invalid custombiosdir: %s" % self.custombiosdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600616 def check_mem(self):
617 s = re.search('-m +([0-9]+)', self.qemu_opt_script)
618 if s:
619 self.set('QB_MEM', '-m %s' % s.group(1))
620 elif not self.get('QB_MEM'):
621 logger.info('QB_MEM is not set, use 512M by default')
622 self.set('QB_MEM', '-m 512')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500623
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600624 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M'
625 self.qemu_opt_script += ' %s' % self.get('QB_MEM')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500626
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600627 def check_tcpserial(self):
628 if self.tcpserial_portnum:
629 if self.get('QB_TCPSERIAL_OPT'):
630 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', self.tcpserial_portnum)
631 else:
632 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % self.tcpserial_portnum
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600634 def check_and_set(self):
635 """Check configs sanity and set when needed"""
636 self.validate_paths()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500637 if not self.slirp_enabled:
638 check_tun()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600639 # Check audio
640 if self.audio_enabled:
641 if not self.get('QB_AUDIO_DRV'):
642 raise Exception("QB_AUDIO_DRV is NULL, this board doesn't support audio")
643 if not self.get('QB_AUDIO_OPT'):
644 logger.warn('QB_AUDIO_OPT is NULL, you may need define it to make audio work')
645 else:
646 self.qemu_opt_script += ' %s' % self.get('QB_AUDIO_OPT')
647 os.putenv('QEMU_AUDIO_DRV', self.get('QB_AUDIO_DRV'))
648 else:
649 os.putenv('QEMU_AUDIO_DRV', 'none')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500650
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600651 self.check_kvm()
652 self.check_fstype()
653 self.check_rootfs()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500654 self.check_ovmf()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600655 self.check_kernel()
656 self.check_biosdir()
657 self.check_mem()
658 self.check_tcpserial()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500659
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600660 def read_qemuboot(self):
661 if not self.qemuboot:
662 if self.get('DEPLOY_DIR_IMAGE'):
663 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600664 else:
665 logger.info("Can't find qemuboot conf file, DEPLOY_DIR_IMAGE is NULL!")
666 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600668 if self.rootfs and not os.path.exists(self.rootfs):
669 # Lazy rootfs
670 machine = self.get('MACHINE')
671 if not machine:
672 machine = os.path.basename(deploy_dir_image)
673 self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image,
674 self.rootfs, machine)
675 else:
676 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image
677 logger.info('Running %s...' % cmd)
678 qbs = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
679 if qbs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500680 for qb in qbs.split():
681 # Don't use initramfs when other choices unless fstype is ramfs
682 if '-initramfs-' in os.path.basename(qb) and self.fstype != 'cpio.gz':
683 continue
684 self.qemuboot = qb
685 break
686 if not self.qemuboot:
687 # Use the first one when no choice
688 self.qemuboot = qbs.split()[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600689 self.qbconfload = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500690
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600691 if not self.qemuboot:
692 # If we haven't found a .qemuboot.conf at this point it probably
693 # doesn't exist, continue without
694 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500695
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600696 if not os.path.exists(self.qemuboot):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500697 raise Exception("Failed to find %s (wrong image name or BSP does not support running under qemu?)." % self.qemuboot)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500698
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600699 logger.info('CONFFILE: %s' % self.qemuboot)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500700
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600701 cf = configparser.ConfigParser()
702 cf.read(self.qemuboot)
703 for k, v in cf.items('config_bsp'):
704 k_upper = k.upper()
705 self.set(k_upper, v)
706
707 def validate_paths(self):
708 """Ensure all relevant path variables are set"""
709 # When we're started with a *.qemuboot.conf arg assume that image
710 # artefacts are relative to that file, rather than in whatever
711 # directory DEPLOY_DIR_IMAGE in the conf file points to.
712 if self.qbconfload:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500713 imgdir = os.path.realpath(os.path.dirname(self.qemuboot))
714 if imgdir != os.path.realpath(self.get('DEPLOY_DIR_IMAGE')):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600715 logger.info('Setting DEPLOY_DIR_IMAGE to folder containing %s (%s)' % (self.qemuboot, imgdir))
716 self.set('DEPLOY_DIR_IMAGE', imgdir)
717
718 # If the STAGING_*_NATIVE directories from the config file don't exist
719 # and we're in a sourced OE build directory try to extract the paths
720 # from `bitbake -e`
721 havenative = os.path.exists(self.get('STAGING_DIR_NATIVE')) and \
722 os.path.exists(self.get('STAGING_BINDIR_NATIVE'))
723
724 if not havenative:
725 if not self.bitbake_e:
726 self.load_bitbake_env()
727
728 if self.bitbake_e:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500729 native_vars = ['STAGING_DIR_NATIVE']
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600730 for nv in native_vars:
731 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M)
732 if s and s.group(1) != self.get(nv):
733 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1)))
734 self.set(nv, s.group(1))
735 else:
736 # when we're invoked from a running bitbake instance we won't
737 # be able to call `bitbake -e`, then try:
738 # - get OE_TMPDIR from environment and guess paths based on it
739 # - get OECORE_NATIVE_SYSROOT from environment (for sdk)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500740 tmpdir = self.get('OE_TMPDIR')
741 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600742 if tmpdir:
743 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir)
744 hostos, _, _, _, machine = os.uname()
745 buildsys = '%s-%s' % (machine, hostos.lower())
746 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys)
747 self.set('STAGING_DIR_NATIVE', staging_dir_native)
748 elif oecore_native_sysroot:
749 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot)
750 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot)
751 if self.get('STAGING_DIR_NATIVE'):
752 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin
753 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')
754 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native)
755 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE'))
756
757 def print_config(self):
758 logger.info('Continuing with the following parameters:\n')
759 if not self.fstype in self.vmtypes:
760 print('KERNEL: [%s]' % self.kernel)
761 if self.dtb:
762 print('DTB: [%s]' % self.dtb)
763 print('MACHINE: [%s]' % self.get('MACHINE'))
764 print('FSTYPE: [%s]' % self.fstype)
765 if self.fstype == 'nfs':
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500766 print('NFS_DIR: [%s]' % self.rootfs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600767 else:
768 print('ROOTFS: [%s]' % self.rootfs)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500769 if self.ovmf_bios:
770 print('OVMF: %s' % self.ovmf_bios)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600771 print('CONFFILE: [%s]' % self.qemuboot)
772 print('')
773
774 def setup_nfs(self):
775 if not self.nfs_server:
776 if self.slirp_enabled:
777 self.nfs_server = '10.0.2.2'
778 else:
779 self.nfs_server = '192.168.7.1'
780
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500781 # Figure out a new nfs_instance to allow multiple qemus running.
782 # CentOS 7.1's ps doesn't print full command line without "ww"
783 # when invoke by subprocess.Popen().
784 cmd = "ps auxww"
785 ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
786 pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) '
787 all_instances = re.findall(pattern, ps, re.M)
788 if all_instances:
789 all_instances.sort(key=int)
790 self.nfs_instance = int(all_instances.pop()) + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600791
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500792 mountd_rpcport = 21111 + self.nfs_instance
793 nfsd_rpcport = 11111 + self.nfs_instance
794 nfsd_port = 3049 + 2 * self.nfs_instance
795 mountd_port = 3048 + 2 * self.nfs_instance
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600796
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500797 # Export vars for runqemu-export-rootfs
798 export_dict = {
799 'NFS_INSTANCE': self.nfs_instance,
800 'MOUNTD_RPCPORT': mountd_rpcport,
801 'NFSD_RPCPORT': nfsd_rpcport,
802 'NFSD_PORT': nfsd_port,
803 'MOUNTD_PORT': mountd_port,
804 }
805 for k, v in export_dict.items():
806 # Use '%s' since they are integers
807 os.putenv(k, '%s' % v)
808
809 self.unfs_opts="nfsvers=3,port=%s,mountprog=%s,nfsprog=%s,udp,mountport=%s" % (nfsd_port, mountd_rpcport, nfsd_rpcport, mountd_port)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600810
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500811 # Extract .tar.bz2 or .tar.bz if no nfs dir
812 if not (self.rootfs and os.path.isdir(self.rootfs)):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600813 src_prefix = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'))
814 dest = "%s-nfsroot" % src_prefix
815 if os.path.exists('%s.pseudo_state' % dest):
816 logger.info('Use %s as NFS_DIR' % dest)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500817 self.rootfs = dest
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600818 else:
819 src = ""
820 src1 = '%s.tar.bz2' % src_prefix
821 src2 = '%s.tar.gz' % src_prefix
822 if os.path.exists(src1):
823 src = src1
824 elif os.path.exists(src2):
825 src = src2
826 if not src:
827 raise Exception("No NFS_DIR is set, and can't find %s or %s to extract" % (src1, src2))
828 logger.info('NFS_DIR not found, extracting %s to %s' % (src, dest))
829 cmd = 'runqemu-extract-sdk %s %s' % (src, dest)
830 logger.info('Running %s...' % cmd)
831 if subprocess.call(cmd, shell=True) != 0:
832 raise Exception('Failed to run %s' % cmd)
833 self.clean_nfs_dir = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500834 self.rootfs = dest
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600835
836 # Start the userspace NFS server
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500837 cmd = 'runqemu-export-rootfs start %s' % self.rootfs
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600838 logger.info('Running %s...' % cmd)
839 if subprocess.call(cmd, shell=True) != 0:
840 raise Exception('Failed to run %s' % cmd)
841
842 self.nfs_running = True
843
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600844 def setup_slirp(self):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500845 """Setup user networking"""
846
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600847 if self.fstype == 'nfs':
848 self.setup_nfs()
849 self.kernel_cmdline_script += ' ip=dhcp'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500850 # Port mapping
851 hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23"
852 qb_slirp_opt_default = "-netdev user,id=net0%s" % hostfwd
853 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default
854 # Figure out the port
855 ports = re.findall('hostfwd=[^-]*:([0-9]+)-[^,-]*', qb_slirp_opt)
856 ports = [int(i) for i in ports]
857 mac = 2
858 # Find a free port to avoid conflicts
859 for p in ports[:]:
860 p_new = p
861 while not check_free_port('localhost', p_new):
862 p_new += 1
863 mac += 1
864 while p_new in ports:
865 p_new += 1
866 mac += 1
867 if p != p_new:
868 ports.append(p_new)
869 qb_slirp_opt = re.sub(':%s-' % p, ':%s-' % p_new, qb_slirp_opt)
870 logger.info("Port forward changed: %s -> %s" % (p, p_new))
871 mac = "%s%02x" % (self.mac_slirp, mac)
872 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qb_slirp_opt))
873 # Print out port foward
874 hostfwd = re.findall('(hostfwd=[^,]*)', qb_slirp_opt)
875 if hostfwd:
876 logger.info('Port forward: %s' % ' '.join(hostfwd))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600877
878 def setup_tap(self):
879 """Setup tap"""
880
881 # This file is created when runqemu-gen-tapdevs creates a bank of tap
882 # devices, indicating that the user should not bring up new ones using
883 # sudo.
884 nosudo_flag = '/etc/runqemu-nosudo'
885 self.qemuifup = shutil.which('runqemu-ifup')
886 self.qemuifdown = shutil.which('runqemu-ifdown')
887 ip = shutil.which('ip')
888 lockdir = "/tmp/qemu-tap-locks"
889
890 if not (self.qemuifup and self.qemuifdown and ip):
891 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found")
892
893 if not os.path.exists(lockdir):
894 # There might be a race issue when multi runqemu processess are
895 # running at the same time.
896 try:
897 os.mkdir(lockdir)
898 except FileExistsError:
899 pass
900
901 cmd = '%s link' % ip
902 logger.info('Running %s...' % cmd)
903 ip_link = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
904 # Matches line like: 6: tap0: <foo>
905 possibles = re.findall('^[1-9]+: +(tap[0-9]+): <.*', ip_link, re.M)
906 tap = ""
907 for p in possibles:
908 lockfile = os.path.join(lockdir, p)
909 if os.path.exists('%s.skip' % lockfile):
910 logger.info('Found %s.skip, skipping %s' % (lockfile, p))
911 continue
912 self.lock = lockfile + '.lock'
913 if self.acquire_lock():
914 tap = p
915 logger.info("Using preconfigured tap device %s" % tap)
916 logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap))
917 break
918
919 if not tap:
920 if os.path.exists(nosudo_flag):
921 logger.error("Error: There are no available tap devices to use for networking,")
922 logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag)
923 raise Exception("a new one with sudo.")
924
925 gid = os.getgid()
926 uid = os.getuid()
927 logger.info("Setting up tap interface under sudo")
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500928 cmd = 'sudo %s %s %s %s' % (self.qemuifup, uid, gid, self.bindir_native)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600929 tap = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8').rstrip('\n')
930 lockfile = os.path.join(lockdir, tap)
931 self.lock = lockfile + '.lock'
932 self.acquire_lock()
933 self.cleantap = True
934 logger.info('Created tap: %s' % tap)
935
936 if not tap:
937 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
938 return 1
939 self.tap = tap
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500940 tapnum = int(tap[3:])
941 gateway = tapnum * 2 + 1
942 client = gateway + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600943 if self.fstype == 'nfs':
944 self.setup_nfs()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500945 netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0" % (client, gateway)
946 logger.info("Network configuration: %s", netconf)
947 self.kernel_cmdline_script += " ip=%s" % netconf
948 mac = "%s%02x" % (self.mac_tap, client)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600949 qb_tap_opt = self.get('QB_TAP_OPT')
950 if qb_tap_opt:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500951 qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600952 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500953 qemu_tap_opt = "-netdev tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600954
955 if self.vhost_enabled:
956 qemu_tap_opt += ',vhost=on'
957
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500958 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600959
960 def setup_network(self):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500961 if self.get('QB_NET') == 'none':
962 return
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600963 cmd = "stty -g"
964 self.saved_stty = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500965 self.network_device = self.get('QB_NETWORK_DEVICE') or self.network_device
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600966 if self.slirp_enabled:
967 self.setup_slirp()
968 else:
969 self.setup_tap()
970
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500971 def setup_rootfs(self):
972 if self.get('QB_ROOTFS') == 'none':
973 return
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600974 rootfs_format = self.fstype if self.fstype in ('vmdk', 'qcow2', 'vdi') else 'raw'
975
976 qb_rootfs_opt = self.get('QB_ROOTFS_OPT')
977 if qb_rootfs_opt:
978 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs)
979 else:
980 self.rootfs_options = '-drive file=%s,if=virtio,format=%s' % (self.rootfs, rootfs_format)
981
982 if self.fstype in ('cpio.gz', 'cpio'):
983 self.kernel_cmdline = 'root=/dev/ram0 rw debugshell'
984 self.rootfs_options = '-initrd %s' % self.rootfs
985 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500986 vm_drive = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600987 if self.fstype in self.vmtypes:
988 if self.fstype == 'iso':
989 vm_drive = '-cdrom %s' % self.rootfs
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500990 elif self.get('QB_DRIVE_TYPE'):
991 drive_type = self.get('QB_DRIVE_TYPE')
992 if drive_type.startswith("/dev/sd"):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600993 logger.info('Using scsi drive')
994 vm_drive = '-drive if=none,id=hd,file=%s,format=%s -device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd' \
995 % (self.rootfs, rootfs_format)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500996 elif drive_type.startswith("/dev/hd"):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500997 logger.info('Using ide drive')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600998 vm_drive = "%s,format=%s" % (self.rootfs, rootfs_format)
999 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001000 # virtio might have been selected explicitly (just use it), or
1001 # is used as fallback (then warn about that).
1002 if not drive_type.startswith("/dev/vd"):
1003 logger.warn("Unknown QB_DRIVE_TYPE: %s" % drive_type)
1004 logger.warn("Failed to figure out drive type, consider define or fix QB_DRIVE_TYPE")
1005 logger.warn('Trying to use virtio block drive')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001006 vm_drive = '-drive if=virtio,file=%s,format=%s' % (self.rootfs, rootfs_format)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001007
1008 # All branches above set vm_drive.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001009 self.rootfs_options = '%s -no-reboot' % vm_drive
1010 self.kernel_cmdline = 'root=%s rw highres=off' % (self.get('QB_KERNEL_ROOT'))
1011
1012 if self.fstype == 'nfs':
1013 self.rootfs_options = ''
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001014 k_root = '/dev/nfs nfsroot=%s:%s,%s' % (self.nfs_server, self.rootfs, self.unfs_opts)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001015 self.kernel_cmdline = 'root=%s rw highres=off' % k_root
1016
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001017 if self.fstype == 'none':
1018 self.rootfs_options = ''
1019
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001020 self.set('ROOTFS_OPTIONS', self.rootfs_options)
1021
1022 def guess_qb_system(self):
1023 """attempt to determine the appropriate qemu-system binary"""
1024 mach = self.get('MACHINE')
1025 if not mach:
1026 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*'
1027 if self.rootfs:
1028 match = re.match(search, self.rootfs)
1029 if match:
1030 mach = match.group(1)
1031 elif self.kernel:
1032 match = re.match(search, self.kernel)
1033 if match:
1034 mach = match.group(1)
1035
1036 if not mach:
1037 return None
1038
1039 if mach == 'qemuarm':
1040 qbsys = 'arm'
1041 elif mach == 'qemuarm64':
1042 qbsys = 'aarch64'
1043 elif mach == 'qemux86':
1044 qbsys = 'i386'
1045 elif mach == 'qemux86-64':
1046 qbsys = 'x86_64'
1047 elif mach == 'qemuppc':
1048 qbsys = 'ppc'
1049 elif mach == 'qemumips':
1050 qbsys = 'mips'
1051 elif mach == 'qemumips64':
1052 qbsys = 'mips64'
1053 elif mach == 'qemumipsel':
1054 qbsys = 'mipsel'
1055 elif mach == 'qemumips64el':
1056 qbsys = 'mips64el'
1057
1058 return 'qemu-system-%s' % qbsys
1059
1060 def setup_final(self):
1061 qemu_system = self.get('QB_SYSTEM_NAME')
1062 if not qemu_system:
1063 qemu_system = self.guess_qb_system()
1064 if not qemu_system:
1065 raise Exception("Failed to boot, QB_SYSTEM_NAME is NULL!")
1066
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001067 qemu_bin = '%s/%s' % (self.bindir_native, qemu_system)
1068
1069 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't
1070 # find QEMU in sysroot, it needs to use host's qemu.
1071 if not os.path.exists(qemu_bin):
1072 logger.info("QEMU binary not found in %s, trying host's QEMU" % qemu_bin)
1073 for path in (os.environ['PATH'] or '').split(':'):
1074 qemu_bin_tmp = os.path.join(path, qemu_system)
1075 logger.info("Trying: %s" % qemu_bin_tmp)
1076 if os.path.exists(qemu_bin_tmp):
1077 qemu_bin = qemu_bin_tmp
1078 if not os.path.isabs(qemu_bin):
1079 qemu_bin = os.path.abspath(qemu_bin)
1080 logger.info("Using host's QEMU: %s" % qemu_bin)
1081 break
1082
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001083 if not os.access(qemu_bin, os.X_OK):
1084 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin)
1085
1086 check_libgl(qemu_bin)
1087
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001088 self.qemu_opt = "%s %s %s %s" % (qemu_bin, self.get('NETWORK_CMD'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND'))
1089
1090 for ovmf in self.ovmf_bios:
1091 format = ovmf.rsplit('.', 1)[-1]
1092 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf)
1093 if self.ovmf_bios:
1094 # OVMF only supports normal VGA, i.e. we need to override a -vga vmware
1095 # that gets added for example for normal qemux86.
1096 self.qemu_opt += ' -vga std'
1097
1098 self.qemu_opt += ' ' + self.qemu_opt_script
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001099
1100 if self.snapshot:
1101 self.qemu_opt += " -snapshot"
1102
1103 if self.serialstdio:
1104 logger.info("Interrupt character is '^]'")
1105 cmd = "stty intr ^]"
1106 subprocess.call(cmd, shell=True)
1107
1108 first_serial = ""
1109 if not re.search("-nographic", self.qemu_opt):
1110 first_serial = "-serial mon:vc"
1111 # We always want a ttyS1. Since qemu by default adds a serial
1112 # port when nodefaults is not specified, it seems that all that
1113 # would be needed is to make sure a "-serial" is there. However,
1114 # it appears that when "-serial" is specified, it ignores the
1115 # default serial port that is normally added. So here we make
1116 # sure to add two -serial if there are none. And only one if
1117 # there is one -serial already.
1118 serial_num = len(re.findall("-serial", self.qemu_opt))
1119 if serial_num == 0:
1120 self.qemu_opt += " %s %s" % (first_serial, self.get("QB_SERIAL_OPT"))
1121 elif serial_num == 1:
1122 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1123
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001124 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES),
1125 # if not serial or serialtcp options was specified only ttyS0 is created
1126 # and sysvinit shows an error trying to enable ttyS1:
1127 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1128 serial_num = len(re.findall("-serial", self.qemu_opt))
1129 if serial_num == 0:
1130 if re.search("-nographic", self.qemu_opt):
1131 self.qemu_opt += " -serial mon:stdio -serial null"
1132 else:
1133 self.qemu_opt += " -serial mon:vc -serial null"
1134
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001135 def start_qemu(self):
1136 if self.kernel:
Brad Bishop37a0e4d2017-12-04 01:01:44 -05001137 kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline,
1138 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'),
1139 self.bootparams)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001140 if self.dtb:
1141 kernel_opts += " -dtb %s" % self.dtb
1142 else:
1143 kernel_opts = ""
1144 cmd = "%s %s" % (self.qemu_opt, kernel_opts)
1145 logger.info('Running %s' % cmd)
1146 if subprocess.call(cmd, shell=True) != 0:
1147 raise Exception('Failed to run %s' % cmd)
1148
1149 def cleanup(self):
1150 if self.cleantap:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001151 cmd = 'sudo %s %s %s' % (self.qemuifdown, self.tap, self.bindir_native)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001152 logger.info('Running %s' % cmd)
1153 subprocess.call(cmd, shell=True)
1154 if self.lock_descriptor:
1155 logger.info("Releasing lockfile for tap device '%s'" % self.tap)
1156 self.release_lock()
1157
1158 if self.nfs_running:
1159 logger.info("Shutting down the userspace NFS server...")
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001160 cmd = "runqemu-export-rootfs stop %s" % self.rootfs
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001161 logger.info('Running %s' % cmd)
1162 subprocess.call(cmd, shell=True)
1163
1164 if self.saved_stty:
1165 cmd = "stty %s" % self.saved_stty
1166 subprocess.call(cmd, shell=True)
1167
1168 if self.clean_nfs_dir:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001169 logger.info('Removing %s' % self.rootfs)
1170 shutil.rmtree(self.rootfs)
1171 shutil.rmtree('%s.pseudo_state' % self.rootfs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001172
1173 def load_bitbake_env(self, mach=None):
1174 if self.bitbake_e:
1175 return
1176
1177 bitbake = shutil.which('bitbake')
1178 if not bitbake:
1179 return
1180
1181 if not mach:
1182 mach = self.get('MACHINE')
1183
1184 if mach:
1185 cmd = 'MACHINE=%s bitbake -e' % mach
1186 else:
1187 cmd = 'bitbake -e'
1188
1189 logger.info('Running %s...' % cmd)
1190 try:
1191 self.bitbake_e = subprocess.check_output(cmd, shell=True).decode('utf-8')
1192 except subprocess.CalledProcessError as err:
1193 self.bitbake_e = ''
1194 logger.warn("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8'))
1195
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001196 @property
1197 def bindir_native(self):
1198 result = self.get('STAGING_BINDIR_NATIVE')
1199 if result and os.path.exists(result):
1200 return result
1201
1202 cmd = 'bitbake qemu-helper-native -e'
1203 logger.info('Running %s...' % cmd)
1204 out = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
1205 out = out.stdout.read().decode('utf-8')
1206
1207 match = re.search('^STAGING_BINDIR_NATIVE="(.*)"', out, re.M)
1208 if match:
1209 result = match.group(1)
1210 if os.path.exists(result):
1211 self.set('STAGING_BINDIR_NATIVE', result)
1212 return result
1213 raise Exception("Native sysroot directory %s doesn't exist" % result)
1214 else:
1215 raise Exception("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd)
1216
1217
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001218def main():
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001219 if "help" in sys.argv or '-h' in sys.argv or '--help' in sys.argv:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001220 print_usage()
1221 return 0
1222 config = BaseConfig()
1223 try:
1224 config.check_args()
1225 except Exception as esc:
1226 logger.error(esc)
1227 logger.error("Try 'runqemu help' on how to use it")
1228 return 1
1229 config.read_qemuboot()
1230 config.check_and_set()
1231 config.print_config()
1232 try:
1233 config.setup_network()
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001234 config.setup_rootfs()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001235 config.setup_final()
1236 config.start_qemu()
1237 finally:
1238 config.cleanup()
1239 return 0
1240
1241if __name__ == "__main__":
1242 try:
1243 ret = main()
1244 except OEPathError as err:
1245 ret = 1
1246 logger.error(err.message)
1247 except Exception as esc:
1248 ret = 1
1249 import traceback
1250 traceback.print_exc()
1251 sys.exit(ret)