blob: 10880ba6bb45da3b4c8f8cbbe1c1887afe08ab34 [file] [log] [blame]
lh9ed821d2023-04-07 01:36:19 -07001#!/usr/bin/env python3
2
3# Handle running OE images standalone with QEMU
4#
5# Copyright (C) 2006-2011 Linux Foundation
6# Copyright (c) 2016 Wind River Systems, Inc.
7#
8# SPDX-License-Identifier: GPL-2.0-only
9#
10
11import os
12import sys
13import logging
14import subprocess
15import re
16import fcntl
17import shutil
18import glob
19import configparser
20import signal
21
22class RunQemuError(Exception):
23 """Custom exception to raise on known errors."""
24 pass
25
26class OEPathError(RunQemuError):
27 """Custom Exception to give better guidance on missing binaries"""
28 def __init__(self, message):
29 super().__init__("In order for this script to dynamically infer paths\n \
30kernels or filesystem images, you either need bitbake in your PATH\n \
31or to source oe-init-build-env before running this script.\n\n \
32Dynamic path inference can be avoided by passing a *.qemuboot.conf to\n \
33runqemu, i.e. `runqemu /path/to/my-image-name.qemuboot.conf`\n\n %s" % message)
34
35
36def create_logger():
37 logger = logging.getLogger('runqemu')
38 logger.setLevel(logging.INFO)
39
40 # create console handler and set level to debug
41 ch = logging.StreamHandler()
42 ch.setLevel(logging.DEBUG)
43
44 # create formatter
45 formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
46
47 # add formatter to ch
48 ch.setFormatter(formatter)
49
50 # add ch to logger
51 logger.addHandler(ch)
52
53 return logger
54
55logger = create_logger()
56
57def print_usage():
58 print("""
59Usage: you can run this script with any valid combination
60of the following environment variables (in any order):
61 KERNEL - the kernel image file to use
62 BIOS - the bios image file to use
63 ROOTFS - the rootfs image file or nfsroot directory to use
64 DEVICE_TREE - the device tree blob to use
65 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified)
66 Simplified QEMU command-line options can be passed with:
67 nographic - disable video console
68 sdl - choose the SDL UI frontend
69 gtk - choose the Gtk UI frontend
70 gl - enable virgl-based GL acceleration (also needs gtk or sdl options)
71 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options)
72 egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it
73 serial - enable a serial console on /dev/ttyS0
74 serialstdio - enable a serial console on the console (regardless of graphics mode)
75 slirp - enable user networking, no root privileges is required
76 snapshot - don't write changes to back to images
77 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required)
78 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required)
79 publicvnc - enable a VNC server open to all hosts
80 audio - enable audio
81 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI
82 tcpserial=<port> - specify tcp serial port number
83 qemuparams=<xyz> - specify custom parameters to QEMU
84 bootparams=<xyz> - specify custom kernel parameters during boot
85 help, -h, --help: print this text
86 -d, --debug: Enable debug output
87 -q, --quiet: Hide most output except error messages
88
89Examples:
90 runqemu
91 runqemu qemuarm
92 runqemu tmp/deploy/images/qemuarm
93 runqemu tmp/deploy/images/qemux86/<qemuboot.conf>
94 runqemu qemux86-64 core-image-sato ext4
95 runqemu qemux86-64 wic-image-minimal wic
96 runqemu path/to/bzImage-qemux86.bin path/to/nfsrootdir/ serial
97 runqemu qemux86 iso/hddimg/wic.vmdk/wic.qcow2/wic.vdi/ramfs/cpio.gz...
98 runqemu qemux86 qemuparams="-m 256"
99 runqemu qemux86 bootparams="psplash=false"
100 runqemu path/to/<image>-<machine>.wic
101 runqemu path/to/<image>-<machine>.wic.vmdk
102""")
103
104def check_tun():
105 """Check /dev/net/tun"""
106 dev_tun = '/dev/net/tun'
107 if not os.path.exists(dev_tun):
108 raise RunQemuError("TUN control device %s is unavailable; you may need to enable TUN (e.g. sudo modprobe tun)" % dev_tun)
109
110 if not os.access(dev_tun, os.W_OK):
111 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun))
112
113def get_first_file(cmds):
114 """Return first file found in wildcard cmds"""
115 for cmd in cmds:
116 all_files = glob.glob(cmd)
117 if all_files:
118 for f in all_files:
119 if not os.path.isdir(f):
120 return f
121 return ''
122
123class BaseConfig(object):
124 def __init__(self):
125 # The self.d saved vars from self.set(), part of them are from qemuboot.conf
126 self.d = {'QB_KERNEL_ROOT': '/dev/vda'}
127
128 # Supported env vars, add it here if a var can be got from env,
129 # and don't use os.getenv in the code.
130 self.env_vars = ('MACHINE',
131 'ROOTFS',
132 'KERNEL',
133 'BIOS',
134 'DEVICE_TREE',
135 'DEPLOY_DIR_IMAGE',
136 'OE_TMPDIR',
137 'OECORE_NATIVE_SYSROOT',
138 'MULTICONFIG',
139 )
140
141 self.qemu_opt = ''
142 self.qemu_opt_script = ''
143 self.qemuparams = ''
144 self.clean_nfs_dir = False
145 self.nfs_server = ''
146 self.rootfs = ''
147 # File name(s) of a OVMF firmware file or variable store,
148 # to be added with -drive if=pflash.
149 # Found in the same places as the rootfs, with or without one of
150 # these suffices: qcow2, bin.
151 self.ovmf_bios = []
152 # When enrolling default Secure Boot keys, the hypervisor
153 # must provide the Platform Key and the first Key Exchange Key
154 # certificate in the Type 11 SMBIOS table.
155 self.ovmf_secboot_pkkek1 = ''
156 self.qemuboot = ''
157 self.qbconfload = False
158 self.kernel = ''
159 self.bios = ''
160 self.kernel_cmdline = ''
161 self.kernel_cmdline_script = ''
162 self.bootparams = ''
163 self.dtb = ''
164 self.fstype = ''
165 self.kvm_enabled = False
166 self.vhost_enabled = False
167 self.slirp_enabled = False
168 self.net_bridge = None
169 self.nfs_instance = 0
170 self.nfs_running = False
171 self.serialconsole = False
172 self.serialstdio = False
173 self.cleantap = False
174 self.saved_stty = ''
175 self.audio_enabled = False
176 self.tcpserial_portnum = ''
177 self.taplock = ''
178 self.taplock_descriptor = None
179 self.portlocks = {}
180 self.bitbake_e = ''
181 self.snapshot = False
182 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi')
183 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs',
184 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz')
185 self.vmtypes = ('hddimg', 'iso')
186 self.fsinfo = {}
187 self.network_device = "-device e1000,netdev=net0,mac=@MAC@"
188 self.cmdline_ip_slirp = "ip=dhcp"
189 self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0"
190 # Use different mac section for tap and slirp to avoid
191 # conflicts, e.g., when one is running with tap, the other is
192 # running with slirp.
193 # The last section is dynamic, which is for avoiding conflicts,
194 # when multiple qemus are running, e.g., when multiple tap or
195 # slirp qemus are running.
196 self.mac_tap = "52:54:00:12:34:"
197 self.mac_slirp = "52:54:00:12:35:"
198 # pid of the actual qemu process
199 self.qemupid = None
200 # avoid cleanup twice
201 self.cleaned = False
202
203 def acquire_taplock(self, error=True):
204 logger.debug("Acquiring lockfile %s..." % self.taplock)
205 try:
206 self.taplock_descriptor = open(self.taplock, 'w')
207 fcntl.flock(self.taplock_descriptor, fcntl.LOCK_EX|fcntl.LOCK_NB)
208 except Exception as e:
209 msg = "Acquiring lockfile %s failed: %s" % (self.taplock, e)
210 if error:
211 logger.error(msg)
212 else:
213 logger.info(msg)
214 if self.taplock_descriptor:
215 self.taplock_descriptor.close()
216 self.taplock_descriptor = None
217 return False
218 return True
219
220 def release_taplock(self):
221 if self.taplock_descriptor:
222 logger.debug("Releasing lockfile for tap device '%s'" % self.tap)
223 fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN)
224 self.taplock_descriptor.close()
225 os.remove(self.taplock)
226 self.taplock_descriptor = None
227
228 def check_free_port(self, host, port, lockdir):
229 """ Check whether the port is free or not """
230 import socket
231 from contextlib import closing
232
233 lockfile = os.path.join(lockdir, str(port) + '.lock')
234 if self.acquire_portlock(lockfile):
235 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
236 if sock.connect_ex((host, port)) == 0:
237 # Port is open, so not free
238 self.release_portlock(lockfile)
239 return False
240 else:
241 # Port is not open, so free
242 return True
243 else:
244 return False
245
246 def acquire_portlock(self, lockfile):
247 logger.debug("Acquiring lockfile %s..." % lockfile)
248 try:
249 portlock_descriptor = open(lockfile, 'w')
250 self.portlocks.update({lockfile: portlock_descriptor})
251 fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_EX|fcntl.LOCK_NB)
252 except Exception as e:
253 msg = "Acquiring lockfile %s failed: %s" % (lockfile, e)
254 logger.info(msg)
255 if lockfile in self.portlocks.keys() and self.portlocks[lockfile]:
256 self.portlocks[lockfile].close()
257 del self.portlocks[lockfile]
258 return False
259 return True
260
261 def release_portlock(self, lockfile=None):
262 if lockfile != None:
263 logger.debug("Releasing lockfile '%s'" % lockfile)
264 fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN)
265 self.portlocks[lockfile].close()
266 os.remove(lockfile)
267 del self.portlocks[lockfile]
268 elif len(self.portlocks):
269 for lockfile, descriptor in self.portlocks.items():
270 logger.debug("Releasing lockfile '%s'" % lockfile)
271 fcntl.flock(descriptor, fcntl.LOCK_UN)
272 descriptor.close()
273 os.remove(lockfile)
274 self.portlocks = {}
275
276 def get(self, key):
277 if key in self.d:
278 return self.d.get(key)
279 elif os.getenv(key):
280 return os.getenv(key)
281 else:
282 return ''
283
284 def set(self, key, value):
285 self.d[key] = value
286
287 def is_deploy_dir_image(self, p):
288 if os.path.isdir(p):
289 if not re.search('.qemuboot.conf$', '\n'.join(os.listdir(p)), re.M):
290 logger.debug("Can't find required *.qemuboot.conf in %s" % p)
291 return False
292 if not any(map(lambda name: '-image-' in name, os.listdir(p))):
293 logger.debug("Can't find *-image-* in %s" % p)
294 return False
295 return True
296 else:
297 return False
298
299 def check_arg_fstype(self, fst):
300 """Check and set FSTYPE"""
301 if fst not in self.fstypes + self.vmtypes + self.wictypes:
302 logger.warning("Maybe unsupported FSTYPE: %s" % fst)
303 if not self.fstype or self.fstype == fst:
304 if fst == 'ramfs':
305 fst = 'cpio.gz'
306 if fst in ('tar.bz2', 'tar.gz'):
307 fst = 'nfs'
308 self.fstype = fst
309 else:
310 raise RunQemuError("Conflicting: FSTYPE %s and %s" % (self.fstype, fst))
311
312 def set_machine_deploy_dir(self, machine, deploy_dir_image):
313 """Set MACHINE and DEPLOY_DIR_IMAGE"""
314 logger.debug('MACHINE: %s' % machine)
315 self.set("MACHINE", machine)
316 logger.debug('DEPLOY_DIR_IMAGE: %s' % deploy_dir_image)
317 self.set("DEPLOY_DIR_IMAGE", deploy_dir_image)
318
319 def check_arg_nfs(self, p):
320 if os.path.isdir(p):
321 self.rootfs = p
322 else:
323 m = re.match('(.*):(.*)', p)
324 self.nfs_server = m.group(1)
325 self.rootfs = m.group(2)
326 self.check_arg_fstype('nfs')
327
328 def check_arg_path(self, p):
329 """
330 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf
331 - Check whether is a kernel file
332 - Check whether is a image file
333 - Check whether it is a nfs dir
334 - Check whether it is a OVMF flash file
335 """
336 if p.endswith('.qemuboot.conf'):
337 self.qemuboot = p
338 self.qbconfload = True
339 elif re.search('\.bin$', p) or re.search('bzImage', p) or \
340 re.search('zImage', p) or re.search('vmlinux', p) or \
341 re.search('fitImage', p) or re.search('uImage', p):
342 self.kernel = p
343 elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p):
344 self.rootfs = p
345 # Check filename against self.fstypes can hanlde <file>.cpio.gz,
346 # otherwise, its type would be "gz", which is incorrect.
347 fst = ""
348 for t in self.fstypes:
349 if p.endswith(t):
350 fst = t
351 break
352 if not fst:
353 m = re.search('.*\.(.*)$', self.rootfs)
354 if m:
355 fst = m.group(1)
356 if fst:
357 self.check_arg_fstype(fst)
358 qb = re.sub('\.' + fst + "$", '', self.rootfs)
359 qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf')
360 if os.path.exists(qb):
361 self.qemuboot = qb
362 self.qbconfload = True
363 else:
364 logger.warning("%s doesn't exist" % qb)
365 else:
366 raise RunQemuError("Can't find FSTYPE from: %s" % p)
367
368 elif os.path.isdir(p) or re.search(':', p) and re.search('/', p):
369 if self.is_deploy_dir_image(p):
370 logger.debug('DEPLOY_DIR_IMAGE: %s' % p)
371 self.set("DEPLOY_DIR_IMAGE", p)
372 else:
373 logger.debug("Assuming %s is an nfs rootfs" % p)
374 self.check_arg_nfs(p)
375 elif os.path.basename(p).startswith('ovmf'):
376 self.ovmf_bios.append(p)
377 else:
378 raise RunQemuError("Unknown path arg %s" % p)
379
380 def check_arg_machine(self, arg):
381 """Check whether it is a machine"""
382 if self.get('MACHINE') == arg:
383 return
384 elif self.get('MACHINE') and self.get('MACHINE') != arg:
385 raise RunQemuError("Maybe conflicted MACHINE: %s vs %s" % (self.get('MACHINE'), arg))
386 elif re.search('/', arg):
387 raise RunQemuError("Unknown arg: %s" % arg)
388
389 logger.debug('Assuming MACHINE = %s' % arg)
390
391 # if we're running under testimage, or similarly as a child
392 # of an existing bitbake invocation, we can't invoke bitbake
393 # to validate the MACHINE setting and must assume it's correct...
394 # FIXME: testimage.bbclass exports these two variables into env,
395 # are there other scenarios in which we need to support being
396 # invoked by bitbake?
397 deploy = self.get('DEPLOY_DIR_IMAGE')
398 bbchild = deploy and self.get('OE_TMPDIR')
399 if bbchild:
400 self.set_machine_deploy_dir(arg, deploy)
401 return
402 # also check whether we're running under a sourced toolchain
403 # environment file
404 if self.get('OECORE_NATIVE_SYSROOT'):
405 self.set("MACHINE", arg)
406 return
407
408 self.bitbake_e = self.run_bitbake_env(arg)
409 # bitbake -e doesn't report invalid MACHINE as an error, so
410 # let's check DEPLOY_DIR_IMAGE to make sure that it is a valid
411 # MACHINE.
412 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
413 if s:
414 deploy_dir_image = s.group(1)
415 else:
416 raise RunQemuError("bitbake -e %s" % self.bitbake_e)
417 if self.is_deploy_dir_image(deploy_dir_image):
418 self.set_machine_deploy_dir(arg, deploy_dir_image)
419 else:
420 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image)
421 self.set("MACHINE", arg)
422
423 def check_args(self):
424 for debug in ("-d", "--debug"):
425 if debug in sys.argv:
426 logger.setLevel(logging.DEBUG)
427 sys.argv.remove(debug)
428
429 for quiet in ("-q", "--quiet"):
430 if quiet in sys.argv:
431 logger.setLevel(logging.ERROR)
432 sys.argv.remove(quiet)
433
434 unknown_arg = ""
435 for arg in sys.argv[1:]:
436 if arg in self.fstypes + self.vmtypes + self.wictypes:
437 self.check_arg_fstype(arg)
438 elif arg == 'nographic':
439 if ('sdl' in sys.argv):
440 raise RunQemuError('Option nographic makes no sense alongside the sdl option.' % (arg))
441 if ('gtk' in sys.argv):
442 raise RunQemuError('Option nographic makes no sense alongside the gtk option.' % (arg))
443 self.qemu_opt_script += ' -nographic'
444 self.kernel_cmdline_script += ' console=ttyS0'
445 elif arg == 'sdl':
446 if 'gl' in sys.argv[1:]:
447 self.qemu_opt_script += ' -vga virtio -display sdl,gl=on'
448 elif 'gl-es' in sys.argv[1:]:
449 self.qemu_opt_script += ' -vga virtio -display sdl,gl=es'
450 else:
451 self.qemu_opt_script += ' -display sdl'
452 elif arg == 'gtk':
453 if 'gl' in sys.argv[1:]:
454 self.qemu_opt_script += ' -vga virtio -display gtk,gl=on'
455 elif 'gl-es' in sys.argv[1:]:
456 self.qemu_opt_script += ' -vga virtio -display gtk,gl=es'
457 else:
458 self.qemu_opt_script += ' -display gtk'
459 elif arg == 'gl' or arg == 'gl-es':
460 # These args are handled inside sdl or gtk blocks above
461 if ('gtk' not in sys.argv) and ('sdl' not in sys.argv):
462 raise RunQemuError('Option %s also needs gtk or sdl option.' % (arg))
463 elif arg == 'egl-headless':
464 self.qemu_opt_script += ' -vga virtio -display egl-headless'
465 # As runqemu can be run within bitbake (when using testimage, for example),
466 # we need to ensure that we run host pkg-config, and that it does not
467 # get mis-directed to native build paths set by bitbake.
468 try:
469 del os.environ['PKG_CONFIG_PATH']
470 del os.environ['PKG_CONFIG_DIR']
471 del os.environ['PKG_CONFIG_LIBDIR']
472 del os.environ['PKG_CONFIG_SYSROOT_DIR']
473 except KeyError:
474 pass
475 try:
476 dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True)
477 except subprocess.CalledProcessError as e:
478 raise RunQemuError("Could not determine the path to dri drivers on the host via pkg-config.\nPlease install Mesa development files (particularly, dri.pc) on the host machine.")
479 os.environ['LIBGL_DRIVERS_PATH'] = dripath.decode('utf-8').strip()
480 elif arg == 'serial':
481 self.kernel_cmdline_script += ' console=ttyS0'
482 self.serialconsole = True
483 elif arg == "serialstdio":
484 self.kernel_cmdline_script += ' console=ttyS0'
485 self.serialstdio = True
486 elif arg == 'audio':
487 logger.info("Enabling audio in qemu")
488 logger.info("Please install sound drivers in linux host")
489 self.audio_enabled = True
490 elif arg == 'kvm':
491 self.kvm_enabled = True
492 elif arg == 'kvm-vhost':
493 self.vhost_enabled = True
494 elif arg == 'slirp':
495 self.slirp_enabled = True
496 elif arg.startswith('bridge='):
497 self.net_bridge = '%s' % arg[len('bridge='):]
498 elif arg == 'snapshot':
499 self.snapshot = True
500 elif arg == 'publicvnc':
501 self.qemu_opt_script += ' -vnc :0'
502 elif arg.startswith('tcpserial='):
503 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):]
504 elif arg.startswith('qemuparams='):
505 self.qemuparams = ' %s' % arg[len('qemuparams='):]
506 elif arg.startswith('bootparams='):
507 self.bootparams = arg[len('bootparams='):]
508 elif os.path.exists(arg) or (re.search(':', arg) and re.search('/', arg)):
509 self.check_arg_path(os.path.abspath(arg))
510 elif re.search(r'-image-|-image$', arg):
511 # Lazy rootfs
512 self.rootfs = arg
513 elif arg.startswith('ovmf'):
514 self.ovmf_bios.append(arg)
515 else:
516 # At last, assume it is the MACHINE
517 if (not unknown_arg) or unknown_arg == arg:
518 unknown_arg = arg
519 else:
520 raise RunQemuError("Can't handle two unknown args: %s %s\n"
521 "Try 'runqemu help' on how to use it" % \
522 (unknown_arg, arg))
523 # Check to make sure it is a valid machine
524 if unknown_arg and self.get('MACHINE') != unknown_arg:
525 if self.get('DEPLOY_DIR_IMAGE'):
526 machine = os.path.basename(self.get('DEPLOY_DIR_IMAGE'))
527 if unknown_arg == machine:
528 self.set("MACHINE", machine)
529
530 self.check_arg_machine(unknown_arg)
531
532 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload):
533 self.load_bitbake_env()
534 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
535 if s:
536 self.set("DEPLOY_DIR_IMAGE", s.group(1))
537
538 def check_kvm(self):
539 """Check kvm and kvm-host"""
540 if not (self.kvm_enabled or self.vhost_enabled):
541 self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'))
542 return
543
544 if not self.get('QB_CPU_KVM'):
545 raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm")
546
547 self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'))
548 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu"
549 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM"
550 dev_kvm = '/dev/kvm'
551 dev_vhost = '/dev/vhost-net'
552 if self.qemu_system.endswith(('i386', 'x86_64')):
553 with open('/proc/cpuinfo', 'r') as f:
554 kvm_cap = re.search('vmx|svm', "".join(f.readlines()))
555 if not kvm_cap:
556 logger.error("You are trying to enable KVM on a cpu without VT support.")
557 logger.error("Remove kvm from the command-line, or refer:")
558 raise RunQemuError(yocto_kvm_wiki)
559
560 if not os.path.exists(dev_kvm):
561 logger.error("Missing KVM device. Have you inserted kvm modules?")
562 logger.error("For further help see:")
563 raise RunQemuError(yocto_kvm_wiki)
564
565 if os.access(dev_kvm, os.W_OK|os.R_OK):
566 self.qemu_opt_script += ' -enable-kvm'
567 if self.get('MACHINE') == "qemux86":
568 # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs
569 # See YOCTO #12301
570 # On 64 bit we use x2apic
571 self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic"
572 else:
573 logger.error("You have no read or write permission on /dev/kvm.")
574 logger.error("Please change the ownership of this file as described at:")
575 raise RunQemuError(yocto_kvm_wiki)
576
577 if self.vhost_enabled:
578 if not os.path.exists(dev_vhost):
579 logger.error("Missing virtio net device. Have you inserted vhost-net module?")
580 logger.error("For further help see:")
581 raise RunQemuError(yocto_paravirt_kvm_wiki)
582
583 if not os.access(dev_kvm, os.W_OK|os.R_OK):
584 logger.error("You have no read or write permission on /dev/vhost-net.")
585 logger.error("Please change the ownership of this file as described at:")
586 raise RunQemuError(yocto_kvm_wiki)
587
588 def check_fstype(self):
589 """Check and setup FSTYPE"""
590 if not self.fstype:
591 fstype = self.get('QB_DEFAULT_FSTYPE')
592 if fstype:
593 self.fstype = fstype
594 else:
595 raise RunQemuError("FSTYPE is NULL!")
596
597 # parse QB_FSINFO into dict, e.g. { 'wic': ['no-kernel-in-fs', 'a-flag'], 'ext4': ['another-flag']}
598 wic_fs = False
599 qb_fsinfo = self.get('QB_FSINFO')
600 if qb_fsinfo:
601 qb_fsinfo = qb_fsinfo.split()
602 for fsinfo in qb_fsinfo:
603 try:
604 fstype, fsflag = fsinfo.split(':')
605
606 if fstype == 'wic':
607 if fsflag == 'no-kernel-in-fs':
608 wic_fs = True
609 elif fsflag == 'kernel-in-fs':
610 wic_fs = False
611 else:
612 logger.warn('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag)
613 continue
614 else:
615 logger.warn('QB_FSINFO is not supported for image type "%s"', fstype)
616 continue
617
618 if fstype in self.fsinfo:
619 self.fsinfo[fstype].append(fsflag)
620 else:
621 self.fsinfo[fstype] = [fsflag]
622 except Exception:
623 logger.error('Invalid parameter "%s" in QB_FSINFO', fsinfo)
624
625 # treat wic images as vmimages (with kernel) or as fsimages (rootfs only)
626 if wic_fs:
627 self.fstypes = self.fstypes + self.wictypes
628 else:
629 self.vmtypes = self.vmtypes + self.wictypes
630
631 def check_rootfs(self):
632 """Check and set rootfs"""
633
634 if self.fstype == "none":
635 return
636
637 if self.get('ROOTFS'):
638 if not self.rootfs:
639 self.rootfs = self.get('ROOTFS')
640 elif self.get('ROOTFS') != self.rootfs:
641 raise RunQemuError("Maybe conflicted ROOTFS: %s vs %s" % (self.get('ROOTFS'), self.rootfs))
642
643 if self.fstype == 'nfs':
644 return
645
646 if self.rootfs and not os.path.exists(self.rootfs):
647 # Lazy rootfs
648 self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'),
649 self.rootfs, self.get('MACHINE'),
650 self.fstype)
651 elif not self.rootfs:
652 cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype)
653 cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype)
654 cmds = (cmd_name, cmd_link)
655 self.rootfs = get_first_file(cmds)
656 if not self.rootfs:
657 raise RunQemuError("Failed to find rootfs: %s or %s" % cmds)
658
659 if not os.path.exists(self.rootfs):
660 raise RunQemuError("Can't find rootfs: %s" % self.rootfs)
661
662 def setup_pkkek1(self):
663 """
664 Extract from PEM certificate the Platform Key and first Key
665 Exchange Key certificate string. The hypervisor needs to provide
666 it in the Type 11 SMBIOS table
667 """
668 pemcert = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), 'OvmfPkKek1.pem')
669 try:
670 with open(pemcert, 'r') as pemfile:
671 key = pemfile.read().replace('\n', ''). \
672 replace('-----BEGIN CERTIFICATE-----', ''). \
673 replace('-----END CERTIFICATE-----', '')
674 self.ovmf_secboot_pkkek1 = key
675
676 except FileNotFoundError:
677 raise RunQemuError("Can't open PEM certificate %s " % pemcert)
678
679 def check_ovmf(self):
680 """Check and set full path for OVMF firmware and variable file(s)."""
681
682 for index, ovmf in enumerate(self.ovmf_bios):
683 if os.path.exists(ovmf):
684 continue
685 for suffix in ('qcow2', 'bin'):
686 path = '%s/%s.%s' % (self.get('DEPLOY_DIR_IMAGE'), ovmf, suffix)
687 if os.path.exists(path):
688 self.ovmf_bios[index] = path
689 if ovmf.endswith('secboot'):
690 self.setup_pkkek1()
691 break
692 else:
693 raise RunQemuError("Can't find OVMF firmware: %s" % ovmf)
694
695 def check_kernel(self):
696 """Check and set kernel"""
697 # The vm image doesn't need a kernel
698 if self.fstype in self.vmtypes:
699 return
700
701 # See if the user supplied a KERNEL option
702 if self.get('KERNEL'):
703 self.kernel = self.get('KERNEL')
704
705 # QB_DEFAULT_KERNEL is always a full file path
706 kernel_name = os.path.basename(self.get('QB_DEFAULT_KERNEL'))
707
708 # The user didn't want a kernel to be loaded
709 if kernel_name == "none" and not self.kernel:
710 return
711
712 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
713 if not self.kernel:
714 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name)
715 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
716 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
717 cmds = (kernel_match_name, kernel_match_link, kernel_startswith)
718 self.kernel = get_first_file(cmds)
719 if not self.kernel:
720 raise RunQemuError('KERNEL not found: %s, %s or %s' % cmds)
721
722 if not os.path.exists(self.kernel):
723 raise RunQemuError("KERNEL %s not found" % self.kernel)
724
725 def check_dtb(self):
726 """Check and set dtb"""
727 # Did the user specify a device tree?
728 if self.get('DEVICE_TREE'):
729 self.dtb = self.get('DEVICE_TREE')
730 if not os.path.exists(self.dtb):
731 raise RunQemuError('Specified DTB not found: %s' % self.dtb)
732 return
733
734 dtb = self.get('QB_DTB')
735 if dtb:
736 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
737 cmd_match = "%s/%s" % (deploy_dir_image, dtb)
738 cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb)
739 cmd_wild = "%s/*.dtb" % deploy_dir_image
740 cmds = (cmd_match, cmd_startswith, cmd_wild)
741 self.dtb = get_first_file(cmds)
742 if not os.path.exists(self.dtb):
743 raise RunQemuError('DTB not found: %s, %s or %s' % cmds)
744
745 def check_bios(self):
746 """Check and set bios"""
747
748 # See if the user supplied a BIOS option
749 if self.get('BIOS'):
750 self.bios = self.get('BIOS')
751
752 # QB_DEFAULT_BIOS is always a full file path
753 bios_name = os.path.basename(self.get('QB_DEFAULT_BIOS'))
754
755 # The user didn't want a bios to be loaded
756 if (bios_name == "" or bios_name == "none") and not self.bios:
757 return
758
759 if not self.bios:
760 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
761 self.bios = "%s/%s" % (deploy_dir_image, bios_name)
762
763 if not self.bios:
764 raise RunQemuError('BIOS not found: %s' % bios_match_name)
765
766 if not os.path.exists(self.bios):
767 raise RunQemuError("BIOS %s not found" % self.bios)
768
769
770 def check_mem(self):
771 """
772 Both qemu and kernel needs memory settings, so check QB_MEM and set it
773 for both.
774 """
775 s = re.search('-m +([0-9]+)', self.qemuparams)
776 if s:
777 self.set('QB_MEM', '-m %s' % s.group(1))
778 elif not self.get('QB_MEM'):
779 logger.info('QB_MEM is not set, use 256M by default')
780 self.set('QB_MEM', '-m 256')
781
782 # Check and remove M or m suffix
783 qb_mem = self.get('QB_MEM')
784 if qb_mem.endswith('M') or qb_mem.endswith('m'):
785 qb_mem = qb_mem[:-1]
786
787 # Add -m prefix it not present
788 if not qb_mem.startswith('-m'):
789 qb_mem = '-m %s' % qb_mem
790
791 self.set('QB_MEM', qb_mem)
792
793 mach = self.get('MACHINE')
794 if not mach.startswith('qemumips'):
795 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M'
796
797 self.qemu_opt_script += ' %s' % self.get('QB_MEM')
798
799 def check_tcpserial(self):
800 if self.tcpserial_portnum:
801 ports = self.tcpserial_portnum.split(':')
802 port = ports[0]
803 if self.get('QB_TCPSERIAL_OPT'):
804 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port)
805 else:
806 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port
807
808 if len(ports) > 1:
809 for port in ports[1:]:
810 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port
811
812 def check_and_set(self):
813 """Check configs sanity and set when needed"""
814 self.validate_paths()
815 if not self.slirp_enabled and not self.net_bridge:
816 check_tun()
817 # Check audio
818 if self.audio_enabled:
819 if not self.get('QB_AUDIO_DRV'):
820 raise RunQemuError("QB_AUDIO_DRV is NULL, this board doesn't support audio")
821 if not self.get('QB_AUDIO_OPT'):
822 logger.warning('QB_AUDIO_OPT is NULL, you may need define it to make audio work')
823 else:
824 self.qemu_opt_script += ' %s' % self.get('QB_AUDIO_OPT')
825 os.putenv('QEMU_AUDIO_DRV', self.get('QB_AUDIO_DRV'))
826 else:
827 os.putenv('QEMU_AUDIO_DRV', 'none')
828
829 self.check_qemu_system()
830 self.check_kvm()
831 self.check_fstype()
832 self.check_rootfs()
833 self.check_ovmf()
834 self.check_kernel()
835 self.check_dtb()
836 self.check_bios()
837 self.check_mem()
838 self.check_tcpserial()
839
840 def read_qemuboot(self):
841 if not self.qemuboot:
842 if self.get('DEPLOY_DIR_IMAGE'):
843 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
844 else:
845 logger.warning("Can't find qemuboot conf file, DEPLOY_DIR_IMAGE is NULL!")
846 return
847
848 if self.rootfs and not os.path.exists(self.rootfs):
849 # Lazy rootfs
850 machine = self.get('MACHINE')
851 if not machine:
852 machine = os.path.basename(deploy_dir_image)
853 self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image,
854 self.rootfs, machine)
855 else:
856 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image
857 logger.debug('Running %s...' % cmd)
858 try:
859 qbs = subprocess.check_output(cmd, shell=True).decode('utf-8')
860 except subprocess.CalledProcessError as err:
861 raise RunQemuError(err)
862 if qbs:
863 for qb in qbs.split():
864 # Don't use initramfs when other choices unless fstype is ramfs
865 if '-initramfs-' in os.path.basename(qb) and self.fstype != 'cpio.gz':
866 continue
867 self.qemuboot = qb
868 break
869 if not self.qemuboot:
870 # Use the first one when no choice
871 self.qemuboot = qbs.split()[0]
872 self.qbconfload = True
873
874 if not self.qemuboot:
875 # If we haven't found a .qemuboot.conf at this point it probably
876 # doesn't exist, continue without
877 return
878
879 if not os.path.exists(self.qemuboot):
880 raise RunQemuError("Failed to find %s (wrong image name or BSP does not support running under qemu?)." % self.qemuboot)
881
882 logger.debug('CONFFILE: %s' % self.qemuboot)
883
884 cf = configparser.ConfigParser()
885 cf.read(self.qemuboot)
886 for k, v in cf.items('config_bsp'):
887 k_upper = k.upper()
888 if v.startswith("../"):
889 v = os.path.abspath(os.path.dirname(self.qemuboot) + "/" + v)
890 elif v == ".":
891 v = os.path.dirname(self.qemuboot)
892 self.set(k_upper, v)
893
894 def validate_paths(self):
895 """Ensure all relevant path variables are set"""
896 # When we're started with a *.qemuboot.conf arg assume that image
897 # artefacts are relative to that file, rather than in whatever
898 # directory DEPLOY_DIR_IMAGE in the conf file points to.
899 if self.qbconfload:
900 imgdir = os.path.realpath(os.path.dirname(self.qemuboot))
901 if imgdir != os.path.realpath(self.get('DEPLOY_DIR_IMAGE')):
902 logger.info('Setting DEPLOY_DIR_IMAGE to folder containing %s (%s)' % (self.qemuboot, imgdir))
903 self.set('DEPLOY_DIR_IMAGE', imgdir)
904
905 # If the STAGING_*_NATIVE directories from the config file don't exist
906 # and we're in a sourced OE build directory try to extract the paths
907 # from `bitbake -e`
908 havenative = os.path.exists(self.get('STAGING_DIR_NATIVE')) and \
909 os.path.exists(self.get('STAGING_BINDIR_NATIVE'))
910
911 if not havenative:
912 if not self.bitbake_e:
913 self.load_bitbake_env()
914
915 if self.bitbake_e:
916 native_vars = ['STAGING_DIR_NATIVE']
917 for nv in native_vars:
918 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M)
919 if s and s.group(1) != self.get(nv):
920 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1)))
921 self.set(nv, s.group(1))
922 else:
923 # when we're invoked from a running bitbake instance we won't
924 # be able to call `bitbake -e`, then try:
925 # - get OE_TMPDIR from environment and guess paths based on it
926 # - get OECORE_NATIVE_SYSROOT from environment (for sdk)
927 tmpdir = self.get('OE_TMPDIR')
928 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT')
929 if tmpdir:
930 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir)
931 hostos, _, _, _, machine = os.uname()
932 buildsys = '%s-%s' % (machine, hostos.lower())
933 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys)
934 self.set('STAGING_DIR_NATIVE', staging_dir_native)
935 elif oecore_native_sysroot:
936 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot)
937 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot)
938 if self.get('STAGING_DIR_NATIVE'):
939 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin
940 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')
941 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native)
942 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE'))
943
944 def print_config(self):
945 logoutput = ['Continuing with the following parameters:']
946 if not self.fstype in self.vmtypes:
947 logoutput.append('KERNEL: [%s]' % self.kernel)
948 if self.bios:
949 logoutput.append('BIOS: [%s]' % self.bios)
950 if self.dtb:
951 logoutput.append('DTB: [%s]' % self.dtb)
952 logoutput.append('MACHINE: [%s]' % self.get('MACHINE'))
953 try:
954 fstype_flags = ' (' + ', '.join(self.fsinfo[self.fstype]) + ')'
955 except KeyError:
956 fstype_flags = ''
957 logoutput.append('FSTYPE: [%s%s]' % (self.fstype, fstype_flags))
958 if self.fstype == 'nfs':
959 logoutput.append('NFS_DIR: [%s]' % self.rootfs)
960 else:
961 logoutput.append('ROOTFS: [%s]' % self.rootfs)
962 if self.ovmf_bios:
963 logoutput.append('OVMF: %s' % self.ovmf_bios)
964 if (self.ovmf_secboot_pkkek1):
965 logoutput.append('SECBOOT PKKEK1: [%s...]' % self.ovmf_secboot_pkkek1[0:100])
966 logoutput.append('CONFFILE: [%s]' % self.qemuboot)
967 logoutput.append('')
968 logger.info('\n'.join(logoutput))
969
970 def setup_nfs(self):
971 if not self.nfs_server:
972 if self.slirp_enabled:
973 self.nfs_server = '10.0.2.2'
974 else:
975 self.nfs_server = '192.168.7.1'
976
977 # Figure out a new nfs_instance to allow multiple qemus running.
978 ps = subprocess.check_output(("ps", "auxww")).decode('utf-8')
979 pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) '
980 all_instances = re.findall(pattern, ps, re.M)
981 if all_instances:
982 all_instances.sort(key=int)
983 self.nfs_instance = int(all_instances.pop()) + 1
984
985 nfsd_port = 3049 + 2 * self.nfs_instance
986 mountd_port = 3048 + 2 * self.nfs_instance
987
988 # Export vars for runqemu-export-rootfs
989 export_dict = {
990 'NFS_INSTANCE': self.nfs_instance,
991 'NFSD_PORT': nfsd_port,
992 'MOUNTD_PORT': mountd_port,
993 }
994 for k, v in export_dict.items():
995 # Use '%s' since they are integers
996 os.putenv(k, '%s' % v)
997
998 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s" % (nfsd_port, mountd_port)
999
1000 # Extract .tar.bz2 or .tar.bz if no nfs dir
1001 if not (self.rootfs and os.path.isdir(self.rootfs)):
1002 src_prefix = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'))
1003 dest = "%s-nfsroot" % src_prefix
1004 if os.path.exists('%s.pseudo_state' % dest):
1005 logger.info('Use %s as NFS_DIR' % dest)
1006 self.rootfs = dest
1007 else:
1008 src = ""
1009 src1 = '%s.tar.bz2' % src_prefix
1010 src2 = '%s.tar.gz' % src_prefix
1011 if os.path.exists(src1):
1012 src = src1
1013 elif os.path.exists(src2):
1014 src = src2
1015 if not src:
1016 raise RunQemuError("No NFS_DIR is set, and can't find %s or %s to extract" % (src1, src2))
1017 logger.info('NFS_DIR not found, extracting %s to %s' % (src, dest))
1018 cmd = ('runqemu-extract-sdk', src, dest)
1019 logger.info('Running %s...' % str(cmd))
1020 if subprocess.call(cmd) != 0:
1021 raise RunQemuError('Failed to run %s' % cmd)
1022 self.clean_nfs_dir = True
1023 self.rootfs = dest
1024
1025 # Start the userspace NFS server
1026 cmd = ('runqemu-export-rootfs', 'start', self.rootfs)
1027 logger.info('Running %s...' % str(cmd))
1028 if subprocess.call(cmd) != 0:
1029 raise RunQemuError('Failed to run %s' % cmd)
1030
1031 self.nfs_running = True
1032
1033 def setup_net_bridge(self):
1034 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % (
1035 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper')))
1036
1037 def setup_slirp(self):
1038 """Setup user networking"""
1039
1040 if self.fstype == 'nfs':
1041 self.setup_nfs()
1042 netconf = " " + self.cmdline_ip_slirp
1043 logger.info("Network configuration:%s", netconf)
1044 self.kernel_cmdline_script += netconf
1045 # Port mapping
1046 hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23"
1047 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE'))
1048 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default
1049 # Figure out the port
1050 ports = re.findall('hostfwd=[^-]*:([0-9]+)-[^,-]*', qb_slirp_opt)
1051 ports = [int(i) for i in ports]
1052 mac = 2
1053
1054 lockdir = "/tmp/qemu-port-locks"
1055 if not os.path.exists(lockdir):
1056 # There might be a race issue when multi runqemu processess are
1057 # running at the same time.
1058 try:
1059 os.mkdir(lockdir)
1060 os.chmod(lockdir, 0o777)
1061 except FileExistsError:
1062 pass
1063
1064 # Find a free port to avoid conflicts
1065 for p in ports[:]:
1066 p_new = p
1067 while not self.check_free_port('localhost', p_new, lockdir):
1068 p_new += 1
1069 mac += 1
1070 while p_new in ports:
1071 p_new += 1
1072 mac += 1
1073 if p != p_new:
1074 ports.append(p_new)
1075 qb_slirp_opt = re.sub(':%s-' % p, ':%s-' % p_new, qb_slirp_opt)
1076 logger.info("Port forward changed: %s -> %s" % (p, p_new))
1077 mac = "%s%02x" % (self.mac_slirp, mac)
1078 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qb_slirp_opt))
1079 # Print out port foward
1080 hostfwd = re.findall('(hostfwd=[^,]*)', qb_slirp_opt)
1081 if hostfwd:
1082 logger.info('Port forward: %s' % ' '.join(hostfwd))
1083
1084 def setup_tap(self):
1085 """Setup tap"""
1086
1087 # This file is created when runqemu-gen-tapdevs creates a bank of tap
1088 # devices, indicating that the user should not bring up new ones using
1089 # sudo.
1090 nosudo_flag = '/etc/runqemu-nosudo'
1091 self.qemuifup = shutil.which('runqemu-ifup')
1092 self.qemuifdown = shutil.which('runqemu-ifdown')
1093 ip = shutil.which('ip')
1094 lockdir = "/tmp/qemu-tap-locks"
1095
1096 if not (self.qemuifup and self.qemuifdown and ip):
1097 logger.error("runqemu-ifup: %s" % self.qemuifup)
1098 logger.error("runqemu-ifdown: %s" % self.qemuifdown)
1099 logger.error("ip: %s" % ip)
1100 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found")
1101
1102 if not os.path.exists(lockdir):
1103 # There might be a race issue when multi runqemu processess are
1104 # running at the same time.
1105 try:
1106 os.mkdir(lockdir)
1107 os.chmod(lockdir, 0o777)
1108 except FileExistsError:
1109 pass
1110
1111 cmd = (ip, 'link')
1112 logger.debug('Running %s...' % str(cmd))
1113 ip_link = subprocess.check_output(cmd).decode('utf-8')
1114 # Matches line like: 6: tap0: <foo>
1115 possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M)
1116 tap = ""
1117 for p in possibles:
1118 lockfile = os.path.join(lockdir, p)
1119 if os.path.exists('%s.skip' % lockfile):
1120 logger.info('Found %s.skip, skipping %s' % (lockfile, p))
1121 continue
1122 self.taplock = lockfile + '.lock'
1123 if self.acquire_taplock(error=False):
1124 tap = p
1125 logger.info("Using preconfigured tap device %s" % tap)
1126 logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap))
1127 break
1128
1129 if not tap:
1130 if os.path.exists(nosudo_flag):
1131 logger.error("Error: There are no available tap devices to use for networking,")
1132 logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag)
1133 raise RunQemuError("a new one with sudo.")
1134
1135 gid = os.getgid()
1136 uid = os.getuid()
1137 logger.info("Setting up tap interface under sudo")
1138 cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native)
1139 try:
1140 tap = subprocess.check_output(cmd).decode('utf-8').strip()
1141 except subprocess.CalledProcessError as e:
1142 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e))
1143 sys.exit(1)
1144 lockfile = os.path.join(lockdir, tap)
1145 self.taplock = lockfile + '.lock'
1146 self.acquire_taplock()
1147 self.cleantap = True
1148 logger.debug('Created tap: %s' % tap)
1149
1150 if not tap:
1151 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
1152 sys.exit(1)
1153 self.tap = tap
1154 tapnum = int(tap[3:])
1155 gateway = tapnum * 2 + 1
1156 client = gateway + 1
1157 if self.fstype == 'nfs':
1158 self.setup_nfs()
1159 netconf = " " + self.cmdline_ip_tap
1160 netconf = netconf.replace('@CLIENT@', str(client))
1161 netconf = netconf.replace('@GATEWAY@', str(gateway))
1162 logger.info("Network configuration:%s", netconf)
1163 self.kernel_cmdline_script += netconf
1164 mac = "%s%02x" % (self.mac_tap, client)
1165 qb_tap_opt = self.get('QB_TAP_OPT')
1166 if qb_tap_opt:
1167 qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap)
1168 else:
1169 qemu_tap_opt = "-netdev tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap)
1170
1171 if self.vhost_enabled:
1172 qemu_tap_opt += ',vhost=on'
1173
1174 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt))
1175
1176 def setup_network(self):
1177 if self.get('QB_NET') == 'none':
1178 return
1179 if sys.stdin.isatty():
1180 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip()
1181 self.network_device = self.get('QB_NETWORK_DEVICE') or self.network_device
1182 if self.net_bridge:
1183 self.setup_net_bridge()
1184 elif self.slirp_enabled:
1185 self.cmdline_ip_slirp = self.get('QB_CMDLINE_IP_SLIRP') or self.cmdline_ip_slirp
1186 self.setup_slirp()
1187 else:
1188 self.cmdline_ip_tap = self.get('QB_CMDLINE_IP_TAP') or self.cmdline_ip_tap
1189 self.setup_tap()
1190
1191 def setup_rootfs(self):
1192 if self.get('QB_ROOTFS') == 'none':
1193 return
1194 if 'wic.' in self.fstype:
1195 self.fstype = self.fstype[4:]
1196 rootfs_format = self.fstype if self.fstype in ('vmdk', 'qcow2', 'vdi') else 'raw'
1197
1198 qb_rootfs_opt = self.get('QB_ROOTFS_OPT')
1199 if qb_rootfs_opt:
1200 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs)
1201 else:
1202 self.rootfs_options = '-drive file=%s,if=virtio,format=%s' % (self.rootfs, rootfs_format)
1203
1204 qb_rootfs_extra_opt = self.get("QB_ROOTFS_EXTRA_OPT")
1205 if qb_rootfs_extra_opt and not qb_rootfs_extra_opt.startswith(","):
1206 qb_rootfs_extra_opt = "," + qb_rootfs_extra_opt
1207
1208 if self.fstype in ('cpio.gz', 'cpio'):
1209 self.kernel_cmdline = 'root=/dev/ram0 rw debugshell'
1210 self.rootfs_options = '-initrd %s' % self.rootfs
1211 else:
1212 vm_drive = ''
1213 if self.fstype in self.vmtypes:
1214 if self.fstype == 'iso':
1215 vm_drive = '-drive file=%s,if=virtio,media=cdrom' % self.rootfs
1216 elif self.get('QB_DRIVE_TYPE'):
1217 drive_type = self.get('QB_DRIVE_TYPE')
1218 if drive_type.startswith("/dev/sd"):
1219 logger.info('Using scsi drive')
1220 vm_drive = '-drive if=none,id=hd,file=%s,format=%s -device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd%s' \
1221 % (self.rootfs, rootfs_format, qb_rootfs_extra_opt)
1222 elif drive_type.startswith("/dev/hd"):
1223 logger.info('Using ide drive')
1224 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format)
1225 elif drive_type.startswith("/dev/vdb"):
1226 logger.info('Using block virtio drive');
1227 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \
1228 % (self.rootfs, rootfs_format,qb_rootfs_extra_opt)
1229 else:
1230 # virtio might have been selected explicitly (just use it), or
1231 # is used as fallback (then warn about that).
1232 if not drive_type.startswith("/dev/vd"):
1233 logger.warning("Unknown QB_DRIVE_TYPE: %s" % drive_type)
1234 logger.warning("Failed to figure out drive type, consider define or fix QB_DRIVE_TYPE")
1235 logger.warning('Trying to use virtio block drive')
1236 vm_drive = '-drive if=virtio,file=%s,format=%s' % (self.rootfs, rootfs_format)
1237
1238 # All branches above set vm_drive.
1239 self.rootfs_options = '%s -no-reboot' % vm_drive
1240 self.kernel_cmdline = 'root=%s rw' % (self.get('QB_KERNEL_ROOT'))
1241
1242 if self.fstype == 'nfs':
1243 self.rootfs_options = ''
1244 k_root = '/dev/nfs nfsroot=%s:%s,%s' % (self.nfs_server, os.path.abspath(self.rootfs), self.unfs_opts)
1245 self.kernel_cmdline = 'root=%s rw' % k_root
1246
1247 if self.fstype == 'none':
1248 self.rootfs_options = ''
1249
1250 self.set('ROOTFS_OPTIONS', self.rootfs_options)
1251
1252 def guess_qb_system(self):
1253 """attempt to determine the appropriate qemu-system binary"""
1254 mach = self.get('MACHINE')
1255 if not mach:
1256 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*'
1257 if self.rootfs:
1258 match = re.match(search, self.rootfs)
1259 if match:
1260 mach = match.group(1)
1261 elif self.kernel:
1262 match = re.match(search, self.kernel)
1263 if match:
1264 mach = match.group(1)
1265
1266 if not mach:
1267 return None
1268
1269 if mach == 'qemuarm':
1270 qbsys = 'arm'
1271 elif mach == 'qemuarm64':
1272 qbsys = 'aarch64'
1273 elif mach == 'qemux86':
1274 qbsys = 'i386'
1275 elif mach == 'qemux86-64':
1276 qbsys = 'x86_64'
1277 elif mach == 'qemuppc':
1278 qbsys = 'ppc'
1279 elif mach == 'qemumips':
1280 qbsys = 'mips'
1281 elif mach == 'qemumips64':
1282 qbsys = 'mips64'
1283 elif mach == 'qemumipsel':
1284 qbsys = 'mipsel'
1285 elif mach == 'qemumips64el':
1286 qbsys = 'mips64el'
1287 elif mach == 'qemuriscv64':
1288 qbsys = 'riscv64'
1289 elif mach == 'qemuriscv32':
1290 qbsys = 'riscv32'
1291 else:
1292 logger.error("Unable to determine QEMU PC System emulator for %s machine." % mach)
1293 logger.error("As %s is not among valid QEMU machines such as," % mach)
1294 logger.error("qemux86-64, qemux86, qemuarm64, qemuarm, qemumips64, qemumips64el, qemumipsel, qemumips, qemuppc")
1295 raise RunQemuError("Set qb_system_name with suitable QEMU PC System emulator in .*qemuboot.conf.")
1296
1297 return 'qemu-system-%s' % qbsys
1298
1299 def check_qemu_system(self):
1300 qemu_system = self.get('QB_SYSTEM_NAME')
1301 if not qemu_system:
1302 qemu_system = self.guess_qb_system()
1303 if not qemu_system:
1304 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!")
1305 self.qemu_system = qemu_system
1306
1307 def setup_final(self):
1308 qemu_bin = os.path.join(self.bindir_native, self.qemu_system)
1309
1310 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't
1311 # find QEMU in sysroot, it needs to use host's qemu.
1312 if not os.path.exists(qemu_bin):
1313 logger.info("QEMU binary not found in %s, trying host's QEMU" % qemu_bin)
1314 for path in (os.environ['PATH'] or '').split(':'):
1315 qemu_bin_tmp = os.path.join(path, self.qemu_system)
1316 logger.info("Trying: %s" % qemu_bin_tmp)
1317 if os.path.exists(qemu_bin_tmp):
1318 qemu_bin = qemu_bin_tmp
1319 if not os.path.isabs(qemu_bin):
1320 qemu_bin = os.path.abspath(qemu_bin)
1321 logger.info("Using host's QEMU: %s" % qemu_bin)
1322 break
1323
1324 if not os.access(qemu_bin, os.X_OK):
1325 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin)
1326
1327 self.qemu_opt = "%s %s %s %s" % (qemu_bin, self.get('NETWORK_CMD'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND'))
1328
1329 for ovmf in self.ovmf_bios:
1330 format = ovmf.rsplit('.', 1)[-1]
1331 if format == "bin":
1332 format = "raw"
1333 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf)
1334
1335 self.qemu_opt += ' ' + self.qemu_opt_script
1336
1337 if self.ovmf_secboot_pkkek1:
1338 # Provide the Platform Key and first Key Exchange Key certificate as an
1339 # OEM string in the SMBIOS Type 11 table. Prepend the certificate string
1340 # with "application prefix" of the EnrollDefaultKeys.efi application
1341 self.qemu_opt += ' -smbios type=11,value=4e32566d-8e9e-4f52-81d3-5bb9715f9727:' \
1342 + self.ovmf_secboot_pkkek1
1343
1344 # Append qemuparams to override previous settings
1345 if self.qemuparams:
1346 self.qemu_opt += ' ' + self.qemuparams
1347
1348 if self.snapshot:
1349 self.qemu_opt += " -snapshot"
1350
1351 if self.serialconsole:
1352 if sys.stdin.isatty():
1353 subprocess.check_call(("stty", "intr", "^]"))
1354 logger.info("Interrupt character is '^]'")
1355
1356 first_serial = ""
1357 if not re.search("-nographic", self.qemu_opt):
1358 first_serial = "-serial mon:vc"
1359 # We always want a ttyS1. Since qemu by default adds a serial
1360 # port when nodefaults is not specified, it seems that all that
1361 # would be needed is to make sure a "-serial" is there. However,
1362 # it appears that when "-serial" is specified, it ignores the
1363 # default serial port that is normally added. So here we make
1364 # sure to add two -serial if there are none. And only one if
1365 # there is one -serial already.
1366 serial_num = len(re.findall("-serial", self.qemu_opt))
1367 if serial_num == 0:
1368 self.qemu_opt += " %s %s" % (first_serial, self.get("QB_SERIAL_OPT"))
1369 elif serial_num == 1:
1370 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1371
1372 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES),
1373 # if not serial or serialtcp options was specified only ttyS0 is created
1374 # and sysvinit shows an error trying to enable ttyS1:
1375 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1376 serial_num = len(re.findall("-serial", self.qemu_opt))
1377 if serial_num == 0:
1378 if re.search("-nographic", self.qemu_opt) or self.serialstdio:
1379 self.qemu_opt += " -serial mon:stdio -serial null"
1380 else:
1381 self.qemu_opt += " -serial mon:vc -serial null"
1382
1383 def start_qemu(self):
1384 import shlex
1385 if self.kernel:
1386 kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline,
1387 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'),
1388 self.bootparams)
1389 if self.bios:
1390 kernel_opts += " -bios %s" % self.bios
1391 if self.dtb:
1392 kernel_opts += " -dtb %s" % self.dtb
1393 else:
1394 kernel_opts = ""
1395 cmd = "%s %s" % (self.qemu_opt, kernel_opts)
1396 cmds = shlex.split(cmd)
1397 logger.info('Running %s\n' % cmd)
1398 pass_fds = []
1399 if self.taplock_descriptor:
1400 pass_fds = [self.taplock_descriptor.fileno()]
1401 if len(self.portlocks):
1402 for descriptor in self.portlocks.values():
1403 pass_fds.append(descriptor.fileno())
1404 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds)
1405 self.qemupid = process.pid
1406 retcode = process.wait()
1407 if retcode:
1408 if retcode == -signal.SIGTERM:
1409 logger.info("Qemu terminated by SIGTERM")
1410 else:
1411 logger.error("Failed to run qemu: %s", process.stderr.read().decode())
1412
1413 def cleanup(self):
1414 if self.cleaned:
1415 return
1416
1417 # avoid dealing with SIGTERM when cleanup function is running
1418 signal.signal(signal.SIGTERM, signal.SIG_IGN)
1419
1420 logger.info("Cleaning up")
1421 if self.cleantap:
1422 cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native)
1423 logger.debug('Running %s' % str(cmd))
1424 subprocess.check_call(cmd)
1425 self.release_taplock()
1426 self.release_portlock()
1427
1428 if self.nfs_running:
1429 logger.info("Shutting down the userspace NFS server...")
1430 cmd = ("runqemu-export-rootfs", "stop", self.rootfs)
1431 logger.debug('Running %s' % str(cmd))
1432 subprocess.check_call(cmd)
1433
1434 if self.saved_stty:
1435 subprocess.check_call(("stty", self.saved_stty))
1436
1437 if self.clean_nfs_dir:
1438 logger.info('Removing %s' % self.rootfs)
1439 shutil.rmtree(self.rootfs)
1440 shutil.rmtree('%s.pseudo_state' % self.rootfs)
1441
1442 self.cleaned = True
1443
1444 def run_bitbake_env(self, mach=None):
1445 bitbake = shutil.which('bitbake')
1446 if not bitbake:
1447 return
1448
1449 if not mach:
1450 mach = self.get('MACHINE')
1451
1452 multiconfig = self.get('MULTICONFIG')
1453 if multiconfig:
1454 multiconfig = "mc:%s" % multiconfig
1455
1456 if mach:
1457 cmd = 'MACHINE=%s bitbake -e %s' % (mach, multiconfig)
1458 else:
1459 cmd = 'bitbake -e %s' % multiconfig
1460
1461 logger.info('Running %s...' % cmd)
1462 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1463
1464 def load_bitbake_env(self, mach=None):
1465 if self.bitbake_e:
1466 return
1467
1468 try:
1469 self.bitbake_e = self.run_bitbake_env(mach=mach)
1470 except subprocess.CalledProcessError as err:
1471 self.bitbake_e = ''
1472 logger.warning("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8'))
1473
1474 def validate_combos(self):
1475 if (self.fstype in self.vmtypes) and self.kernel:
1476 raise RunQemuError("%s doesn't need kernel %s!" % (self.fstype, self.kernel))
1477
1478 @property
1479 def bindir_native(self):
1480 result = self.get('STAGING_BINDIR_NATIVE')
1481 if result and os.path.exists(result):
1482 return result
1483
1484 cmd = ['bitbake', '-e']
1485 multiconfig = self.get('MULTICONFIG')
1486 if multiconfig:
1487 cmd.append('mc:%s:qemu-helper-native' % multiconfig)
1488 else:
1489 cmd.append('qemu-helper-native')
1490
1491 logger.info('Running %s...' % str(cmd))
1492 out = subprocess.check_output(cmd).decode('utf-8')
1493
1494 match = re.search('^STAGING_BINDIR_NATIVE="(.*)"', out, re.M)
1495 if match:
1496 result = match.group(1)
1497 if os.path.exists(result):
1498 self.set('STAGING_BINDIR_NATIVE', result)
1499 return result
1500 raise RunQemuError("Native sysroot directory %s doesn't exist" % result)
1501 else:
1502 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd)
1503
1504
1505def main():
1506 if "help" in sys.argv or '-h' in sys.argv or '--help' in sys.argv:
1507 print_usage()
1508 return 0
1509 try:
1510 config = BaseConfig()
1511
1512 renice = os.path.expanduser("~/bin/runqemu-renice")
1513 if os.path.exists(renice):
1514 logger.info('Using %s to renice' % renice)
1515 subprocess.check_call([renice, str(os.getpid())])
1516
1517 def sigterm_handler(signum, frame):
1518 logger.info("SIGTERM received")
1519 os.kill(config.qemupid, signal.SIGTERM)
1520 config.cleanup()
1521 # Deliberately ignore the return code of 'tput smam'.
1522 subprocess.call(["tput", "smam"])
1523 signal.signal(signal.SIGTERM, sigterm_handler)
1524
1525 config.check_args()
1526 config.read_qemuboot()
1527 config.check_and_set()
1528 # Check whether the combos is valid or not
1529 config.validate_combos()
1530 config.print_config()
1531 config.setup_network()
1532 config.setup_rootfs()
1533 config.setup_final()
1534 config.start_qemu()
1535 except RunQemuError as err:
1536 logger.error(err)
1537 return 1
1538 except Exception as err:
1539 import traceback
1540 traceback.print_exc()
1541 return 1
1542 finally:
1543 config.cleanup()
1544 # Deliberately ignore the return code of 'tput smam'.
1545 subprocess.call(["tput", "smam"])
1546
1547if __name__ == "__main__":
1548 sys.exit(main())