summaryrefslogtreecommitdiffstats
path: root/scripts/runqemu
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/runqemu')
-rwxr-xr-xscripts/runqemu560
1 files changed, 371 insertions, 189 deletions
diff --git a/scripts/runqemu b/scripts/runqemu
index efb98ab9e0..2be7a0f286 100755
--- a/scripts/runqemu
+++ b/scripts/runqemu
@@ -66,6 +66,7 @@ of the following environment variables (in any order):
66 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) 66 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified)
67 Simplified QEMU command-line options can be passed with: 67 Simplified QEMU command-line options can be passed with:
68 nographic - disable video console 68 nographic - disable video console
69 nonetwork - disable network connectivity
69 novga - Disable VGA emulation completely 70 novga - Disable VGA emulation completely
70 sdl - choose the SDL UI frontend 71 sdl - choose the SDL UI frontend
71 gtk - choose the Gtk UI frontend 72 gtk - choose the Gtk UI frontend
@@ -73,15 +74,17 @@ of the following environment variables (in any order):
73 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options) 74 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options)
74 egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it 75 egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it
75 (hint: if /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create 76 (hint: if /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create
76 one sutable for mesa llvmpipe sofware renderer) 77 one suitable for mesa llvmpipe software renderer)
77 serial - enable a serial console on /dev/ttyS0 78 serial - enable a serial console on /dev/ttyS0
78 serialstdio - enable a serial console on the console (regardless of graphics mode) 79 serialstdio - enable a serial console on the console (regardless of graphics mode)
79 slirp - enable user networking, no root privileges is required 80 slirp - enable user networking, no root privilege is required
80 snapshot - don't write changes to back to images 81 snapshot - don't write changes back to images
81 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required) 82 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required)
82 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required) 83 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required)
83 publicvnc - enable a VNC server open to all hosts 84 publicvnc - enable a VNC server open to all hosts
84 audio - enable audio 85 audio - enable audio
86 guestagent - enable guest agent communication
87 qmp=<path> - create a QMP socket (defaults to unix:qmp.sock if unspecified)
85 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI 88 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI
86 tcpserial=<port> - specify tcp serial port number 89 tcpserial=<port> - specify tcp serial port number
87 qemuparams=<xyz> - specify custom parameters to QEMU 90 qemuparams=<xyz> - specify custom parameters to QEMU
@@ -116,10 +119,10 @@ def check_tun():
116 if not os.access(dev_tun, os.W_OK): 119 if not os.access(dev_tun, os.W_OK):
117 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun)) 120 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun))
118 121
119def get_first_file(cmds): 122def get_first_file(globs):
120 """Return first file found in wildcard cmds""" 123 """Return first file found in wildcard globs"""
121 for cmd in cmds: 124 for g in globs:
122 all_files = glob.glob(cmd) 125 all_files = glob.glob(g)
123 if all_files: 126 if all_files:
124 for f in all_files: 127 for f in all_files:
125 if not os.path.isdir(f): 128 if not os.path.isdir(f):
@@ -177,11 +180,13 @@ class BaseConfig(object):
177 self.serialconsole = False 180 self.serialconsole = False
178 self.serialstdio = False 181 self.serialstdio = False
179 self.nographic = False 182 self.nographic = False
183 self.nonetwork = False
180 self.sdl = False 184 self.sdl = False
181 self.gtk = False 185 self.gtk = False
182 self.gl = False 186 self.gl = False
183 self.gl_es = False 187 self.gl_es = False
184 self.egl_headless = False 188 self.egl_headless = False
189 self.publicvnc = False
185 self.novga = False 190 self.novga = False
186 self.cleantap = False 191 self.cleantap = False
187 self.saved_stty = '' 192 self.saved_stty = ''
@@ -192,14 +197,18 @@ class BaseConfig(object):
192 self.portlocks = {} 197 self.portlocks = {}
193 self.bitbake_e = '' 198 self.bitbake_e = ''
194 self.snapshot = False 199 self.snapshot = False
195 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") 200 self.wictypes = ('wic.zst', 'wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx")
196 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', 201 self.fstypes = ('ext2', 'ext3', 'ext4', 'ext2.zst', 'ext3.zst', 'ext4.zst',
197 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz') 202 'jffs2', 'nfs', 'btrfs', 'cpio.gz', 'cpio', 'ramfs',
203 'tar.bz2', 'tar.gz', 'tar.zst',
204 'squashfs', 'squashfs-xz', 'squashfs-lzo',
205 'squashfs-lz4', 'squashfs-zst',
206 'erofs', 'erofs-lz4', 'erofs-lz4hc')
198 self.vmtypes = ('hddimg', 'iso') 207 self.vmtypes = ('hddimg', 'iso')
199 self.fsinfo = {} 208 self.fsinfo = {}
200 self.network_device = "-device e1000,netdev=net0,mac=@MAC@" 209 self.network_device = "-device e1000,netdev=net0,mac=@MAC@"
201 self.cmdline_ip_slirp = "ip=dhcp" 210 self.cmdline_ip_slirp = "ip=dhcp"
202 self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0" 211 self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0::eth0:off:8.8.8.8 net.ifnames=0"
203 # Use different mac section for tap and slirp to avoid 212 # Use different mac section for tap and slirp to avoid
204 # conflicts, e.g., when one is running with tap, the other is 213 # conflicts, e.g., when one is running with tap, the other is
205 # running with slirp. 214 # running with slirp.
@@ -209,11 +218,15 @@ class BaseConfig(object):
209 self.mac_tap = "52:54:00:12:34:" 218 self.mac_tap = "52:54:00:12:34:"
210 self.mac_slirp = "52:54:00:12:35:" 219 self.mac_slirp = "52:54:00:12:35:"
211 # pid of the actual qemu process 220 # pid of the actual qemu process
212 self.qemupid = None 221 self.qemu_environ = os.environ.copy()
222 self.qemuprocess = None
213 # avoid cleanup twice 223 # avoid cleanup twice
214 self.cleaned = False 224 self.cleaned = False
215 # Files to cleanup after run 225 # Files to cleanup after run
216 self.cleanup_files = [] 226 self.cleanup_files = []
227 self.qmp = None
228 self.guest_agent = False
229 self.guest_agent_sockpath = '/tmp/qga.sock'
217 230
218 def acquire_taplock(self, error=True): 231 def acquire_taplock(self, error=True):
219 logger.debug("Acquiring lockfile %s..." % self.taplock) 232 logger.debug("Acquiring lockfile %s..." % self.taplock)
@@ -352,40 +365,47 @@ class BaseConfig(object):
352 def check_arg_path(self, p): 365 def check_arg_path(self, p):
353 """ 366 """
354 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf 367 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf
355 - Check whether is a kernel file 368 - Check whether it is a kernel file
356 - Check whether is a image file 369 - Check whether it is an image file
357 - Check whether it is a nfs dir 370 - Check whether it is an NFS dir
358 - Check whether it is a OVMF flash file 371 - Check whether it is an OVMF flash file
359 """ 372 """
373 n = os.path.basename(p)
360 if p.endswith('.qemuboot.conf'): 374 if p.endswith('.qemuboot.conf'):
361 self.qemuboot = p 375 self.qemuboot = p
362 self.qbconfload = True 376 self.qbconfload = True
363 elif re.search('\.bin$', p) or re.search('bzImage', p) or \ 377 elif re.search('\\.bin$', n) or re.search('bzImage', n) or \
364 re.search('zImage', p) or re.search('vmlinux', p) or \ 378 re.search('zImage', n) or re.search('vmlinux', n) or \
365 re.search('fitImage', p) or re.search('uImage', p): 379 re.search('fitImage', n) or re.search('uImage', n):
366 self.kernel = p 380 self.kernel = p
367 elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p): 381 elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)):
368 self.rootfs = p 382 self.rootfs = p
369 # Check filename against self.fstypes can hanlde <file>.cpio.gz, 383 # Check filename against self.fstypes can handle <file>.cpio.gz,
370 # otherwise, its type would be "gz", which is incorrect. 384 # otherwise, its type would be "gz", which is incorrect.
371 fst = "" 385 fst = ""
372 for t in self.fstypes: 386 for t in self.fstypes + self.vmtypes + self.wictypes:
373 if p.endswith(t): 387 if p.endswith(t):
374 fst = t 388 fst = t
375 break 389 break
376 if not fst: 390 if not fst:
377 m = re.search('.*\.(.*)$', self.rootfs) 391 m = re.search('.*\\.(.*)$', self.rootfs)
378 if m: 392 if m:
379 fst = m.group(1) 393 fst = m.group(1)
380 if fst: 394 if fst:
381 self.check_arg_fstype(fst) 395 self.check_arg_fstype(fst)
382 qb = re.sub('\.' + fst + "$", '', self.rootfs) 396 qb = re.sub('\\.' + fst + "$", '.qemuboot.conf', self.rootfs)
383 qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf')
384 if os.path.exists(qb): 397 if os.path.exists(qb):
385 self.qemuboot = qb 398 self.qemuboot = qb
386 self.qbconfload = True 399 self.qbconfload = True
387 else: 400 else:
388 logger.warning("%s doesn't exist" % qb) 401 logger.warning("%s doesn't exist, will try to remove '.rootfs' from filename" % qb)
402 # They to remove .rootfs (IMAGE_NAME_SUFFIX) as well
403 qb = re.sub('\\.rootfs.qemuboot.conf$', '.qemuboot.conf', qb)
404 if os.path.exists(qb):
405 self.qemuboot = qb
406 self.qbconfload = True
407 else:
408 logger.warning("%s doesn't exist" % qb)
389 else: 409 else:
390 raise RunQemuError("Can't find FSTYPE from: %s" % p) 410 raise RunQemuError("Can't find FSTYPE from: %s" % p)
391 411
@@ -401,6 +421,46 @@ class BaseConfig(object):
401 else: 421 else:
402 raise RunQemuError("Unknown path arg %s" % p) 422 raise RunQemuError("Unknown path arg %s" % p)
403 423
424 def uncompress_rootfs(self):
425 """Decompress ZST rootfs image if needed"""
426 if not self.rootfs or not self.fstype.endswith('.zst'):
427 return
428
429 # Ensure snapshot mode is active before allowing decompression.
430 if not self.snapshot:
431 raise RunQemuError(".zst images are only supported with snapshot mode. " \
432 "You can either use the \"snapshot\" option or use an uncompressed image.")
433
434 # Get the real path to the image to avoid issues when a symbolic link is passed.
435 # This ensures we always operate on the actual file.
436 image_path = os.path.realpath(self.rootfs)
437 # Extract target filename by removing .zst
438 image_dir = os.path.dirname(image_path)
439 uncompressed_name = os.path.basename(image_path).replace(".zst", "")
440 uncompressed_path = os.path.join(image_dir, uncompressed_name)
441
442 # If the decompressed image already exists (e.g., in the deploy directory),
443 # we use it directly to avoid overwriting artifacts generated by the build system.
444 # This prevents redundant decompression and preserves build outputs.
445 if os.path.exists(uncompressed_path):
446 logger.warning(f"Found existing decompressed image: {uncompressed_path}, Using it directly.")
447 else:
448 logger.info(f"Decompressing {self.rootfs} to {uncompressed_path}")
449 # Ensure the 'zstd' tool is installed before attempting to decompress.
450 if not shutil.which('zstd'):
451 raise RunQemuError(f"'zstd' is required to decompress {self.rootfs} but was not found in PATH")
452 try:
453 subprocess.check_call(['zstd', '-d', image_path, '-o', uncompressed_path])
454 except subprocess.CalledProcessError as e:
455 raise RunQemuError(f"Failed to decompress {self.rootfs}: {e}")
456 finally:
457 # Mark temporary file for deletion
458 self.cleanup_files.append(uncompressed_path)
459
460 # Use the decompressed image as the rootfs
461 self.rootfs = uncompressed_path
462 self.fstype = self.fstype.removesuffix(".zst")
463
404 def check_arg_machine(self, arg): 464 def check_arg_machine(self, arg):
405 """Check whether it is a machine""" 465 """Check whether it is a machine"""
406 if self.get('MACHINE') == arg: 466 if self.get('MACHINE') == arg:
@@ -419,6 +479,7 @@ class BaseConfig(object):
419 # are there other scenarios in which we need to support being 479 # are there other scenarios in which we need to support being
420 # invoked by bitbake? 480 # invoked by bitbake?
421 deploy = self.get('DEPLOY_DIR_IMAGE') 481 deploy = self.get('DEPLOY_DIR_IMAGE')
482 image_link_name = self.get('IMAGE_LINK_NAME')
422 bbchild = deploy and self.get('OE_TMPDIR') 483 bbchild = deploy and self.get('OE_TMPDIR')
423 if bbchild: 484 if bbchild:
424 self.set_machine_deploy_dir(arg, deploy) 485 self.set_machine_deploy_dir(arg, deploy)
@@ -443,23 +504,27 @@ class BaseConfig(object):
443 else: 504 else:
444 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) 505 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image)
445 self.set("MACHINE", arg) 506 self.set("MACHINE", arg)
446 507 if not image_link_name:
447 def set_dri_path(self): 508 s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M)
448 # As runqemu can be run within bitbake (when using testimage, for example), 509 if s:
449 # we need to ensure that we run host pkg-config, and that it does not 510 image_link_name = s.group(1)
450 # get mis-directed to native build paths set by bitbake. 511 self.set("IMAGE_LINK_NAME", image_link_name)
451 try: 512 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name)
452 del os.environ['PKG_CONFIG_PATH'] 513
453 del os.environ['PKG_CONFIG_DIR'] 514 def set_mesa_paths(self):
454 del os.environ['PKG_CONFIG_LIBDIR'] 515 drivers_path = os.path.join(self.bindir_native, '../lib/dri')
455 del os.environ['PKG_CONFIG_SYSROOT_DIR'] 516 gbm_path = os.path.join(self.bindir_native, '../lib/gbm')
456 except KeyError: 517 if not os.path.exists(drivers_path) or not os.listdir(drivers_path) \
457 pass 518 or not os.path.exists(gbm_path) or not os.listdir(gbm_path):
458 try: 519 raise RunQemuError("""
459 dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True) 520qemu has been built without opengl support and accelerated graphics support is not available.
460 except subprocess.CalledProcessError as e: 521To enable it, add:
461 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.") 522DISTRO_FEATURES_NATIVE:append = " opengl"
462 os.environ['LIBGL_DRIVERS_PATH'] = dripath.decode('utf-8').strip() 523DISTRO_FEATURES_NATIVESDK:append = " opengl"
524to your build configuration.
525""")
526 self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path
527 self.qemu_environ['GBM_BACKENDS_PATH'] = gbm_path
463 528
464 def check_args(self): 529 def check_args(self):
465 for debug in ("-d", "--debug"): 530 for debug in ("-d", "--debug"):
@@ -473,7 +538,8 @@ class BaseConfig(object):
473 sys.argv.remove(quiet) 538 sys.argv.remove(quiet)
474 539
475 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: 540 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]:
476 os.environ['SDL_RENDER_DRIVER'] = 'software' 541 self.qemu_environ['SDL_RENDER_DRIVER'] = 'software'
542 self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false'
477 543
478 unknown_arg = "" 544 unknown_arg = ""
479 for arg in sys.argv[1:]: 545 for arg in sys.argv[1:]:
@@ -481,13 +547,15 @@ class BaseConfig(object):
481 self.check_arg_fstype(arg) 547 self.check_arg_fstype(arg)
482 elif arg == 'nographic': 548 elif arg == 'nographic':
483 self.nographic = True 549 self.nographic = True
550 elif arg == "nonetwork":
551 self.nonetwork = True
484 elif arg == 'sdl': 552 elif arg == 'sdl':
485 self.sdl = True 553 self.sdl = True
486 elif arg == 'gtk': 554 elif arg == 'gtk':
487 self.gtk = True 555 self.gtk = True
488 elif arg == 'gl': 556 elif arg == 'gl':
489 self.gl = True 557 self.gl = True
490 elif 'gl-es' in sys.argv[1:]: 558 elif arg == 'gl-es':
491 self.gl_es = True 559 self.gl_es = True
492 elif arg == 'egl-headless': 560 elif arg == 'egl-headless':
493 self.egl_headless = True 561 self.egl_headless = True
@@ -512,7 +580,16 @@ class BaseConfig(object):
512 elif arg == 'snapshot': 580 elif arg == 'snapshot':
513 self.snapshot = True 581 self.snapshot = True
514 elif arg == 'publicvnc': 582 elif arg == 'publicvnc':
583 self.publicvnc = True
515 self.qemu_opt_script += ' -vnc :0' 584 self.qemu_opt_script += ' -vnc :0'
585 elif arg == 'guestagent':
586 self.guest_agent = True
587 elif arg == "qmp":
588 self.qmp = "unix:qmp.sock"
589 elif arg.startswith("qmp="):
590 self.qmp = arg[len('qmp='):]
591 elif arg.startswith('guestagent-sockpath='):
592 self.guest_agent_sockpath = '%s' % arg[len('guestagent-sockpath='):]
516 elif arg.startswith('tcpserial='): 593 elif arg.startswith('tcpserial='):
517 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] 594 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):]
518 elif arg.startswith('qemuparams='): 595 elif arg.startswith('qemuparams='):
@@ -544,11 +621,18 @@ class BaseConfig(object):
544 self.check_arg_machine(unknown_arg) 621 self.check_arg_machine(unknown_arg)
545 622
546 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): 623 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload):
547 self.load_bitbake_env() 624 self.load_bitbake_env(target=self.rootfs)
548 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) 625 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
549 if s: 626 if s:
550 self.set("DEPLOY_DIR_IMAGE", s.group(1)) 627 self.set("DEPLOY_DIR_IMAGE", s.group(1))
551 628
629 if not self.get('IMAGE_LINK_NAME') and self.rootfs:
630 s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M)
631 if s:
632 image_link_name = s.group(1)
633 self.set("IMAGE_LINK_NAME", image_link_name)
634 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name)
635
552 def check_kvm(self): 636 def check_kvm(self):
553 """Check kvm and kvm-host""" 637 """Check kvm and kvm-host"""
554 if not (self.kvm_enabled or self.vhost_enabled): 638 if not (self.kvm_enabled or self.vhost_enabled):
@@ -578,11 +662,6 @@ class BaseConfig(object):
578 662
579 if os.access(dev_kvm, os.W_OK|os.R_OK): 663 if os.access(dev_kvm, os.W_OK|os.R_OK):
580 self.qemu_opt_script += ' -enable-kvm' 664 self.qemu_opt_script += ' -enable-kvm'
581 if self.get('MACHINE') == "qemux86":
582 # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs
583 # See YOCTO #12301
584 # On 64 bit we use x2apic
585 self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic"
586 else: 665 else:
587 logger.error("You have no read or write permission on /dev/kvm.") 666 logger.error("You have no read or write permission on /dev/kvm.")
588 logger.error("Please change the ownership of this file as described at:") 667 logger.error("Please change the ownership of this file as described at:")
@@ -623,10 +702,10 @@ class BaseConfig(object):
623 elif fsflag == 'kernel-in-fs': 702 elif fsflag == 'kernel-in-fs':
624 wic_fs = False 703 wic_fs = False
625 else: 704 else:
626 logger.warn('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) 705 logger.warning('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag)
627 continue 706 continue
628 else: 707 else:
629 logger.warn('QB_FSINFO is not supported for image type "%s"', fstype) 708 logger.warning('QB_FSINFO is not supported for image type "%s"', fstype)
630 continue 709 continue
631 710
632 if fstype in self.fsinfo: 711 if fstype in self.fsinfo:
@@ -659,16 +738,16 @@ class BaseConfig(object):
659 738
660 if self.rootfs and not os.path.exists(self.rootfs): 739 if self.rootfs and not os.path.exists(self.rootfs):
661 # Lazy rootfs 740 # Lazy rootfs
662 self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), 741 self.rootfs = "%s/%s.%s" % (self.get('DEPLOY_DIR_IMAGE'),
663 self.rootfs, self.get('MACHINE'), 742 self.get('IMAGE_LINK_NAME'),
664 self.fstype) 743 self.fstype)
665 elif not self.rootfs: 744 elif not self.rootfs:
666 cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) 745 glob_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype)
667 cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) 746 glob_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype)
668 cmds = (cmd_name, cmd_link) 747 globs = (glob_name, glob_link)
669 self.rootfs = get_first_file(cmds) 748 self.rootfs = get_first_file(globs)
670 if not self.rootfs: 749 if not self.rootfs:
671 raise RunQemuError("Failed to find rootfs: %s or %s" % cmds) 750 raise RunQemuError("Failed to find rootfs: %s or %s" % globs)
672 751
673 if not os.path.exists(self.rootfs): 752 if not os.path.exists(self.rootfs):
674 raise RunQemuError("Can't find rootfs: %s" % self.rootfs) 753 raise RunQemuError("Can't find rootfs: %s" % self.rootfs)
@@ -728,10 +807,10 @@ class BaseConfig(object):
728 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) 807 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name)
729 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 808 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
730 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 809 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
731 cmds = (kernel_match_name, kernel_match_link, kernel_startswith) 810 globs = (kernel_match_name, kernel_match_link, kernel_startswith)
732 self.kernel = get_first_file(cmds) 811 self.kernel = get_first_file(globs)
733 if not self.kernel: 812 if not self.kernel:
734 raise RunQemuError('KERNEL not found: %s, %s or %s' % cmds) 813 raise RunQemuError('KERNEL not found: %s, %s or %s' % globs)
735 814
736 if not os.path.exists(self.kernel): 815 if not os.path.exists(self.kernel):
737 raise RunQemuError("KERNEL %s not found" % self.kernel) 816 raise RunQemuError("KERNEL %s not found" % self.kernel)
@@ -748,13 +827,13 @@ class BaseConfig(object):
748 dtb = self.get('QB_DTB') 827 dtb = self.get('QB_DTB')
749 if dtb: 828 if dtb:
750 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 829 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
751 cmd_match = "%s/%s" % (deploy_dir_image, dtb) 830 glob_match = "%s/%s" % (deploy_dir_image, dtb)
752 cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb) 831 glob_startswith = "%s/%s*" % (deploy_dir_image, dtb)
753 cmd_wild = "%s/*.dtb" % deploy_dir_image 832 glob_wild = "%s/*.dtb" % deploy_dir_image
754 cmds = (cmd_match, cmd_startswith, cmd_wild) 833 globs = (glob_match, glob_startswith, glob_wild)
755 self.dtb = get_first_file(cmds) 834 self.dtb = get_first_file(globs)
756 if not os.path.exists(self.dtb): 835 if not os.path.exists(self.dtb):
757 raise RunQemuError('DTB not found: %s, %s or %s' % cmds) 836 raise RunQemuError('DTB not found: %s, %s or %s' % globs)
758 837
759 def check_bios(self): 838 def check_bios(self):
760 """Check and set bios""" 839 """Check and set bios"""
@@ -805,7 +884,7 @@ class BaseConfig(object):
805 self.set('QB_MEM', qb_mem) 884 self.set('QB_MEM', qb_mem)
806 885
807 mach = self.get('MACHINE') 886 mach = self.get('MACHINE')
808 if not mach.startswith('qemumips'): 887 if not mach.startswith(('qemumips', 'qemux86', 'qemuloongarch64')):
809 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' 888 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M'
810 889
811 self.qemu_opt_script += ' %s' % self.get('QB_MEM') 890 self.qemu_opt_script += ' %s' % self.get('QB_MEM')
@@ -817,11 +896,11 @@ class BaseConfig(object):
817 if self.get('QB_TCPSERIAL_OPT'): 896 if self.get('QB_TCPSERIAL_OPT'):
818 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) 897 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port)
819 else: 898 else:
820 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port 899 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port
821 900
822 if len(ports) > 1: 901 if len(ports) > 1:
823 for port in ports[1:]: 902 for port in ports[1:]:
824 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port 903 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port
825 904
826 def check_and_set(self): 905 def check_and_set(self):
827 """Check configs sanity and set when needed""" 906 """Check configs sanity and set when needed"""
@@ -864,8 +943,10 @@ class BaseConfig(object):
864 machine = self.get('MACHINE') 943 machine = self.get('MACHINE')
865 if not machine: 944 if not machine:
866 machine = os.path.basename(deploy_dir_image) 945 machine = os.path.basename(deploy_dir_image)
867 self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image, 946 if not self.get('IMAGE_LINK_NAME'):
868 self.rootfs, machine) 947 raise RunQemuError("IMAGE_LINK_NAME wasn't set to find corresponding .qemuboot.conf file")
948 self.qemuboot = "%s/%s.qemuboot.conf" % (deploy_dir_image,
949 self.get('IMAGE_LINK_NAME'))
869 else: 950 else:
870 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image 951 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image
871 logger.debug('Running %s...' % cmd) 952 logger.debug('Running %s...' % cmd)
@@ -926,34 +1007,12 @@ class BaseConfig(object):
926 if not self.bitbake_e: 1007 if not self.bitbake_e:
927 self.load_bitbake_env() 1008 self.load_bitbake_env()
928 1009
929 if self.bitbake_e: 1010 native_vars = ['STAGING_DIR_NATIVE']
930 native_vars = ['STAGING_DIR_NATIVE'] 1011 for nv in native_vars:
931 for nv in native_vars: 1012 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M)
932 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M) 1013 if s and s.group(1) != self.get(nv):
933 if s and s.group(1) != self.get(nv): 1014 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1)))
934 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1))) 1015 self.set(nv, s.group(1))
935 self.set(nv, s.group(1))
936 else:
937 # when we're invoked from a running bitbake instance we won't
938 # be able to call `bitbake -e`, then try:
939 # - get OE_TMPDIR from environment and guess paths based on it
940 # - get OECORE_NATIVE_SYSROOT from environment (for sdk)
941 tmpdir = self.get('OE_TMPDIR')
942 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT')
943 if tmpdir:
944 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir)
945 hostos, _, _, _, machine = os.uname()
946 buildsys = '%s-%s' % (machine, hostos.lower())
947 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys)
948 self.set('STAGING_DIR_NATIVE', staging_dir_native)
949 elif oecore_native_sysroot:
950 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot)
951 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot)
952 if self.get('STAGING_DIR_NATIVE'):
953 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin
954 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')
955 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native)
956 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE'))
957 1016
958 def print_config(self): 1017 def print_config(self):
959 logoutput = ['Continuing with the following parameters:'] 1018 logoutput = ['Continuing with the following parameters:']
@@ -973,6 +1032,9 @@ class BaseConfig(object):
973 logoutput.append('NFS_DIR: [%s]' % self.rootfs) 1032 logoutput.append('NFS_DIR: [%s]' % self.rootfs)
974 else: 1033 else:
975 logoutput.append('ROOTFS: [%s]' % self.rootfs) 1034 logoutput.append('ROOTFS: [%s]' % self.rootfs)
1035 logoutput.append('SNAPSHOT: [%s]' %
1036 "Enabled. Changes on rootfs won't be kept after QEMU shutdown." if self.snapshot
1037 else "Disabled. Changes on rootfs will be kept after QEMU shutdown.")
976 if self.ovmf_bios: 1038 if self.ovmf_bios:
977 logoutput.append('OVMF: %s' % self.ovmf_bios) 1039 logoutput.append('OVMF: %s' % self.ovmf_bios)
978 if (self.ovmf_secboot_pkkek1): 1040 if (self.ovmf_secboot_pkkek1):
@@ -986,19 +1048,16 @@ class BaseConfig(object):
986 if self.slirp_enabled: 1048 if self.slirp_enabled:
987 self.nfs_server = '10.0.2.2' 1049 self.nfs_server = '10.0.2.2'
988 else: 1050 else:
989 self.nfs_server = '192.168.7.1' 1051 self.nfs_server = '192.168.7.@GATEWAY@'
990 1052
991 # Figure out a new nfs_instance to allow multiple qemus running. 1053 nfsd_port = 3048 + self.nfs_instance
992 ps = subprocess.check_output(("ps", "auxww")).decode('utf-8') 1054 lockdir = "/tmp/qemu-port-locks"
993 pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) ' 1055 self.make_lock_dir(lockdir)
994 all_instances = re.findall(pattern, ps, re.M) 1056 while not self.check_free_port('localhost', nfsd_port, lockdir):
995 if all_instances: 1057 self.nfs_instance += 1
996 all_instances.sort(key=int) 1058 nfsd_port += 1
997 self.nfs_instance = int(all_instances.pop()) + 1
998
999 nfsd_port = 3049 + 2 * self.nfs_instance
1000 mountd_port = 3048 + 2 * self.nfs_instance
1001 1059
1060 mountd_port = nfsd_port
1002 # Export vars for runqemu-export-rootfs 1061 # Export vars for runqemu-export-rootfs
1003 export_dict = { 1062 export_dict = {
1004 'NFS_INSTANCE': self.nfs_instance, 1063 'NFS_INSTANCE': self.nfs_instance,
@@ -1009,7 +1068,11 @@ class BaseConfig(object):
1009 # Use '%s' since they are integers 1068 # Use '%s' since they are integers
1010 os.putenv(k, '%s' % v) 1069 os.putenv(k, '%s' % v)
1011 1070
1012 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s" % (nfsd_port, mountd_port) 1071 qb_nfsrootfs_extra_opt = self.get("QB_NFSROOTFS_EXTRA_OPT")
1072 if qb_nfsrootfs_extra_opt and not qb_nfsrootfs_extra_opt.startswith(","):
1073 qb_nfsrootfs_extra_opt = "," + qb_nfsrootfs_extra_opt
1074
1075 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s%s" % (nfsd_port, mountd_port, qb_nfsrootfs_extra_opt)
1013 1076
1014 # Extract .tar.bz2 or .tar.bz if no nfs dir 1077 # Extract .tar.bz2 or .tar.bz if no nfs dir
1015 if not (self.rootfs and os.path.isdir(self.rootfs)): 1078 if not (self.rootfs and os.path.isdir(self.rootfs)):
@@ -1032,7 +1095,7 @@ class BaseConfig(object):
1032 cmd = ('runqemu-extract-sdk', src, dest) 1095 cmd = ('runqemu-extract-sdk', src, dest)
1033 logger.info('Running %s...' % str(cmd)) 1096 logger.info('Running %s...' % str(cmd))
1034 if subprocess.call(cmd) != 0: 1097 if subprocess.call(cmd) != 0:
1035 raise RunQemuError('Failed to run %s' % cmd) 1098 raise RunQemuError('Failed to run %s' % str(cmd))
1036 self.rootfs = dest 1099 self.rootfs = dest
1037 self.cleanup_files.append(self.rootfs) 1100 self.cleanup_files.append(self.rootfs)
1038 self.cleanup_files.append('%s.pseudo_state' % self.rootfs) 1101 self.cleanup_files.append('%s.pseudo_state' % self.rootfs)
@@ -1041,14 +1104,32 @@ class BaseConfig(object):
1041 cmd = ('runqemu-export-rootfs', 'start', self.rootfs) 1104 cmd = ('runqemu-export-rootfs', 'start', self.rootfs)
1042 logger.info('Running %s...' % str(cmd)) 1105 logger.info('Running %s...' % str(cmd))
1043 if subprocess.call(cmd) != 0: 1106 if subprocess.call(cmd) != 0:
1044 raise RunQemuError('Failed to run %s' % cmd) 1107 raise RunQemuError('Failed to run %s' % str(cmd))
1045 1108
1046 self.nfs_running = True 1109 self.nfs_running = True
1047 1110
1111 def setup_cmd(self):
1112 cmd = self.get('QB_SETUP_CMD')
1113 if cmd != '':
1114 logger.info('Running setup command %s' % str(cmd))
1115 if subprocess.call(cmd, shell=True) != 0:
1116 raise RunQemuError('Failed to run %s' % str(cmd))
1117
1048 def setup_net_bridge(self): 1118 def setup_net_bridge(self):
1049 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( 1119 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % (
1050 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) 1120 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper')))
1051 1121
1122 def make_lock_dir(self, lockdir):
1123 if not os.path.exists(lockdir):
1124 # There might be a race issue when multi runqemu processess are
1125 # running at the same time.
1126 try:
1127 os.mkdir(lockdir)
1128 os.chmod(lockdir, 0o777)
1129 except FileExistsError:
1130 pass
1131 return
1132
1052 def setup_slirp(self): 1133 def setup_slirp(self):
1053 """Setup user networking""" 1134 """Setup user networking"""
1054 1135
@@ -1058,7 +1139,7 @@ class BaseConfig(object):
1058 logger.info("Network configuration:%s", netconf) 1139 logger.info("Network configuration:%s", netconf)
1059 self.kernel_cmdline_script += netconf 1140 self.kernel_cmdline_script += netconf
1060 # Port mapping 1141 # Port mapping
1061 hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23" 1142 hostfwd = ",hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23"
1062 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE')) 1143 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE'))
1063 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default 1144 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default
1064 # Figure out the port 1145 # Figure out the port
@@ -1067,14 +1148,7 @@ class BaseConfig(object):
1067 mac = 2 1148 mac = 2
1068 1149
1069 lockdir = "/tmp/qemu-port-locks" 1150 lockdir = "/tmp/qemu-port-locks"
1070 if not os.path.exists(lockdir): 1151 self.make_lock_dir(lockdir)
1071 # There might be a race issue when multi runqemu processess are
1072 # running at the same time.
1073 try:
1074 os.mkdir(lockdir)
1075 os.chmod(lockdir, 0o777)
1076 except FileExistsError:
1077 pass
1078 1152
1079 # Find a free port to avoid conflicts 1153 # Find a free port to avoid conflicts
1080 for p in ports[:]: 1154 for p in ports[:]:
@@ -1114,20 +1188,17 @@ class BaseConfig(object):
1114 logger.error("ip: %s" % ip) 1188 logger.error("ip: %s" % ip)
1115 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") 1189 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found")
1116 1190
1117 if not os.path.exists(lockdir): 1191 self.make_lock_dir(lockdir)
1118 # There might be a race issue when multi runqemu processess are
1119 # running at the same time.
1120 try:
1121 os.mkdir(lockdir)
1122 os.chmod(lockdir, 0o777)
1123 except FileExistsError:
1124 pass
1125 1192
1126 cmd = (ip, 'link') 1193 cmd = (ip, 'link')
1127 logger.debug('Running %s...' % str(cmd)) 1194 logger.debug('Running %s...' % str(cmd))
1128 ip_link = subprocess.check_output(cmd).decode('utf-8') 1195 ip_link = subprocess.check_output(cmd).decode('utf-8')
1129 # Matches line like: 6: tap0: <foo> 1196 # Matches line like: 6: tap0: <foo>
1130 possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M) 1197 oe_tap_name = 'tap'
1198 if 'OE_TAP_NAME' in os.environ:
1199 oe_tap_name = os.environ['OE_TAP_NAME']
1200 tap_re = '^[0-9]+: +(' + oe_tap_name + '[0-9]+): <.*'
1201 possibles = re.findall(tap_re, ip_link, re.M)
1131 tap = "" 1202 tap = ""
1132 for p in possibles: 1203 for p in possibles:
1133 lockfile = os.path.join(lockdir, p) 1204 lockfile = os.path.join(lockdir, p)
@@ -1148,25 +1219,28 @@ class BaseConfig(object):
1148 raise RunQemuError("a new one with sudo.") 1219 raise RunQemuError("a new one with sudo.")
1149 1220
1150 gid = os.getgid() 1221 gid = os.getgid()
1151 uid = os.getuid()
1152 logger.info("Setting up tap interface under sudo") 1222 logger.info("Setting up tap interface under sudo")
1153 cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native) 1223 cmd = ('sudo', self.qemuifup, str(gid))
1154 try: 1224 for _ in range(5):
1155 tap = subprocess.check_output(cmd).decode('utf-8').strip() 1225 try:
1156 except subprocess.CalledProcessError as e: 1226 tap = subprocess.check_output(cmd).decode('utf-8').strip()
1157 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e)) 1227 except subprocess.CalledProcessError as e:
1158 sys.exit(1) 1228 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e))
1159 lockfile = os.path.join(lockdir, tap) 1229 sys.exit(1)
1160 self.taplock = lockfile + '.lock' 1230 lockfile = os.path.join(lockdir, tap)
1161 self.acquire_taplock() 1231 self.taplock = lockfile + '.lock'
1162 self.cleantap = True 1232 if self.acquire_taplock():
1163 logger.debug('Created tap: %s' % tap) 1233 self.cleantap = True
1234 logger.debug('Created tap: %s' % tap)
1235 break
1236 else:
1237 tap = None
1164 1238
1165 if not tap: 1239 if not tap:
1166 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") 1240 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
1167 sys.exit(1) 1241 sys.exit(1)
1168 self.tap = tap 1242 self.tap = tap
1169 tapnum = int(tap[3:]) 1243 tapnum = int(tap[len(oe_tap_name):])
1170 gateway = tapnum * 2 + 1 1244 gateway = tapnum * 2 + 1
1171 client = gateway + 1 1245 client = gateway + 1
1172 if self.fstype == 'nfs': 1246 if self.fstype == 'nfs':
@@ -1174,6 +1248,7 @@ class BaseConfig(object):
1174 netconf = " " + self.cmdline_ip_tap 1248 netconf = " " + self.cmdline_ip_tap
1175 netconf = netconf.replace('@CLIENT@', str(client)) 1249 netconf = netconf.replace('@CLIENT@', str(client))
1176 netconf = netconf.replace('@GATEWAY@', str(gateway)) 1250 netconf = netconf.replace('@GATEWAY@', str(gateway))
1251 self.nfs_server = self.nfs_server.replace('@GATEWAY@', str(gateway))
1177 logger.info("Network configuration:%s", netconf) 1252 logger.info("Network configuration:%s", netconf)
1178 self.kernel_cmdline_script += netconf 1253 self.kernel_cmdline_script += netconf
1179 mac = "%s%02x" % (self.mac_tap, client) 1254 mac = "%s%02x" % (self.mac_tap, client)
@@ -1189,7 +1264,8 @@ class BaseConfig(object):
1189 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) 1264 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt))
1190 1265
1191 def setup_network(self): 1266 def setup_network(self):
1192 if self.get('QB_NET') == 'none': 1267 if self.nonetwork or self.get('QB_NET') == 'none':
1268 self.set('NETWORK_CMD', '-nic none')
1193 return 1269 return
1194 if sys.stdin.isatty(): 1270 if sys.stdin.isatty():
1195 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() 1271 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip()
@@ -1249,6 +1325,10 @@ class BaseConfig(object):
1249 elif drive_type.startswith("/dev/hd"): 1325 elif drive_type.startswith("/dev/hd"):
1250 logger.info('Using ide drive') 1326 logger.info('Using ide drive')
1251 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format) 1327 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format)
1328 elif drive_type.startswith("/dev/mmcblk"):
1329 logger.info('Using sdcard drive')
1330 vm_drive = '-drive id=sdcard0,if=none,file=%s,format=%s -device sdhci-pci -device sd-card,drive=sdcard0' \
1331 % (self.rootfs, rootfs_format)
1252 elif drive_type.startswith("/dev/vdb"): 1332 elif drive_type.startswith("/dev/vdb"):
1253 logger.info('Using block virtio drive'); 1333 logger.info('Using block virtio drive');
1254 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \ 1334 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \
@@ -1288,7 +1368,7 @@ class BaseConfig(object):
1288 """attempt to determine the appropriate qemu-system binary""" 1368 """attempt to determine the appropriate qemu-system binary"""
1289 mach = self.get('MACHINE') 1369 mach = self.get('MACHINE')
1290 if not mach: 1370 if not mach:
1291 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' 1371 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemuloongarch64|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*'
1292 if self.rootfs: 1372 if self.rootfs:
1293 match = re.match(search, self.rootfs) 1373 match = re.match(search, self.rootfs)
1294 if match: 1374 if match:
@@ -1311,6 +1391,8 @@ class BaseConfig(object):
1311 qbsys = 'x86_64' 1391 qbsys = 'x86_64'
1312 elif mach == 'qemuppc': 1392 elif mach == 'qemuppc':
1313 qbsys = 'ppc' 1393 qbsys = 'ppc'
1394 elif mach == 'qemuloongarch64':
1395 qbsys = 'loongarch64'
1314 elif mach == 'qemumips': 1396 elif mach == 'qemumips':
1315 qbsys = 'mips' 1397 qbsys = 'mips'
1316 elif mach == 'qemumips64': 1398 elif mach == 'qemumips64':
@@ -1339,6 +1421,35 @@ class BaseConfig(object):
1339 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") 1421 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!")
1340 self.qemu_system = qemu_system 1422 self.qemu_system = qemu_system
1341 1423
1424 def check_render_nodes(self):
1425 render_hint = """If /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create one suitable for mesa llvmpipe software renderer."""
1426 try:
1427 content = os.listdir("/dev/dri")
1428 nodes = [i for i in content if i.startswith('renderD')]
1429 if len(nodes) == 0:
1430 raise RunQemuError("No render nodes found in /dev/dri/: %s. %s" %(content, render_hint))
1431 for n in nodes:
1432 try:
1433 with open(os.path.join("/dev/dri", n), "w") as f:
1434 f.close()
1435 break
1436 except IOError:
1437 pass
1438 else:
1439 raise RunQemuError("None of the render nodes in /dev/dri/ are accessible: %s; you may need to add yourself to 'render' group or otherwise ensure you have read-write permissions on one of them." %(nodes))
1440 except FileNotFoundError:
1441 raise RunQemuError("/dev/dri directory does not exist; no render nodes available on this machine. %s" %(render_hint))
1442
1443 def setup_guest_agent(self):
1444 if self.guest_agent == True:
1445 self.qemu_opt += ' -chardev socket,path=' + self.guest_agent_sockpath + ',server,nowait,id=qga0 '
1446 self.qemu_opt += ' -device virtio-serial '
1447 self.qemu_opt += ' -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 '
1448
1449 def setup_qmp(self):
1450 if self.qmp:
1451 self.qemu_opt += " -qmp %s,server,nowait" % self.qmp
1452
1342 def setup_vga(self): 1453 def setup_vga(self):
1343 if self.nographic == True: 1454 if self.nographic == True:
1344 if self.sdl == True: 1455 if self.sdl == True:
@@ -1354,27 +1465,43 @@ class BaseConfig(object):
1354 if (self.gl_es == True or self.gl == True) and (self.sdl == False and self.gtk == False): 1465 if (self.gl_es == True or self.gl == True) and (self.sdl == False and self.gtk == False):
1355 raise RunQemuError('Option gl/gl-es needs gtk or sdl option.') 1466 raise RunQemuError('Option gl/gl-es needs gtk or sdl option.')
1356 1467
1357 if self.sdl == True or self.gtk == True or self.egl_headless == True: 1468 # If we have no display option, we autodetect based upon what qemu supports. We
1358 if self.gl or self.gl_es or self.egl_headless: 1469 # need our font setup and show-cusor below so we need to see what qemu --help says
1359 self.qemu_opt += ' -device virtio-vga-gl ' 1470 # is supported so we can pass our correct config in.
1471 if not self.nographic and not self.sdl and not self.gtk and not self.publicvnc and not self.egl_headless == True:
1472 output = subprocess.check_output([self.qemu_bin, "--help"], universal_newlines=True, env=self.qemu_environ)
1473 if "-display gtk" in output:
1474 self.gtk = True
1475 elif "-display sdl" in output:
1476 self.sdl = True
1360 else: 1477 else:
1361 self.qemu_opt += ' -device virtio-vga ' 1478 self.qemu_opt += ' -display none'
1479
1480 if self.sdl == True or self.gtk == True or self.egl_headless == True:
1362 1481
1363 self.qemu_opt += '-display ' 1482 if self.qemu_system.endswith(('i386', 'x86_64')):
1483 if self.gl or self.gl_es or self.egl_headless:
1484 self.qemu_opt += ' -device virtio-vga-gl '
1485 else:
1486 self.qemu_opt += ' -device virtio-vga '
1487
1488 self.qemu_opt += ' -display '
1364 if self.egl_headless == True: 1489 if self.egl_headless == True:
1365 self.set_dri_path() 1490 self.check_render_nodes()
1491 self.set_mesa_paths()
1366 self.qemu_opt += 'egl-headless,' 1492 self.qemu_opt += 'egl-headless,'
1367 else: 1493 else:
1368 if self.sdl == True: 1494 if self.sdl == True:
1369 self.qemu_opt += 'sdl,' 1495 self.qemu_opt += 'sdl,'
1370 elif self.gtk == True: 1496 elif self.gtk == True:
1497 self.qemu_environ['FONTCONFIG_PATH'] = '/etc/fonts'
1371 self.qemu_opt += 'gtk,' 1498 self.qemu_opt += 'gtk,'
1372 1499
1373 if self.gl == True: 1500 if self.gl == True:
1374 self.set_dri_path() 1501 self.set_mesa_paths()
1375 self.qemu_opt += 'gl=on,' 1502 self.qemu_opt += 'gl=on,'
1376 elif self.gl_es == True: 1503 elif self.gl_es == True:
1377 self.set_dri_path() 1504 self.set_mesa_paths()
1378 self.qemu_opt += 'gl=es,' 1505 self.qemu_opt += 'gl=es,'
1379 self.qemu_opt += 'show-cursor=on' 1506 self.qemu_opt += 'show-cursor=on'
1380 1507
@@ -1386,6 +1513,19 @@ class BaseConfig(object):
1386 for entry in self.get('SERIAL_CONSOLES').split(' '): 1513 for entry in self.get('SERIAL_CONSOLES').split(' '):
1387 self.kernel_cmdline_script += ' console=%s' %entry.split(';')[1] 1514 self.kernel_cmdline_script += ' console=%s' %entry.split(';')[1]
1388 1515
1516 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES).
1517 # If no serial or serialtcp options were specified, only ttyS0 is created
1518 # and sysvinit shows an error trying to enable ttyS1:
1519 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1520 serial_num = len(re.findall("(^| )-serial ", self.qemu_opt))
1521
1522 # Assume if the user passed serial options, they know what they want
1523 # and pad to two devices
1524 if serial_num == 1:
1525 self.qemu_opt += " -serial null"
1526 elif serial_num >= 2:
1527 return
1528
1389 if self.serialstdio == True or self.nographic == True: 1529 if self.serialstdio == True or self.nographic == True:
1390 self.qemu_opt += " -serial mon:stdio" 1530 self.qemu_opt += " -serial mon:stdio"
1391 else: 1531 else:
@@ -1397,15 +1537,11 @@ class BaseConfig(object):
1397 1537
1398 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT") 1538 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1399 1539
1400 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES). 1540 serial_num = len(re.findall("(^| )-serial ", self.qemu_opt))
1401 # If no serial or serialtcp options were specified, only ttyS0 is created
1402 # and sysvinit shows an error trying to enable ttyS1:
1403 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1404 serial_num = len(re.findall("-serial", self.qemu_opt))
1405 if serial_num < 2: 1541 if serial_num < 2:
1406 self.qemu_opt += " -serial null" 1542 self.qemu_opt += " -serial null"
1407 1543
1408 def setup_final(self): 1544 def find_qemu(self):
1409 qemu_bin = os.path.join(self.bindir_native, self.qemu_system) 1545 qemu_bin = os.path.join(self.bindir_native, self.qemu_system)
1410 1546
1411 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't 1547 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't
@@ -1424,8 +1560,13 @@ class BaseConfig(object):
1424 1560
1425 if not os.access(qemu_bin, os.X_OK): 1561 if not os.access(qemu_bin, os.X_OK):
1426 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) 1562 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin)
1563 self.qemu_bin = qemu_bin
1427 1564
1428 self.qemu_opt = "%s %s %s %s %s" % (qemu_bin, self.get('NETWORK_CMD'), self.get('QB_RNG'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND').replace('@DEPLOY_DIR_IMAGE@', self.get('DEPLOY_DIR_IMAGE'))) 1565 def setup_final(self):
1566
1567 self.find_qemu()
1568
1569 self.qemu_opt = "%s %s %s %s %s" % (self.qemu_bin, self.get('NETWORK_CMD'), self.get('QB_RNG'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND').replace('@DEPLOY_DIR_IMAGE@', self.get('DEPLOY_DIR_IMAGE')))
1429 1570
1430 for ovmf in self.ovmf_bios: 1571 for ovmf in self.ovmf_bios:
1431 format = ovmf.rsplit('.', 1)[-1] 1572 format = ovmf.rsplit('.', 1)[-1]
@@ -1449,13 +1590,20 @@ class BaseConfig(object):
1449 if self.snapshot: 1590 if self.snapshot:
1450 self.qemu_opt += " -snapshot" 1591 self.qemu_opt += " -snapshot"
1451 1592
1593 self.setup_guest_agent()
1594 self.setup_qmp()
1452 self.setup_serial() 1595 self.setup_serial()
1453 self.setup_vga() 1596 self.setup_vga()
1454 1597
1455 def start_qemu(self): 1598 def start_qemu(self):
1456 import shlex 1599 import shlex
1457 if self.kernel: 1600 if self.kernel:
1458 kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline, 1601 kernel_opts = "-kernel %s" % (self.kernel)
1602 if self.get('QB_KERNEL_CMDLINE') == "none":
1603 if self.bootparams:
1604 kernel_opts += " -append '%s'" % (self.bootparams)
1605 else:
1606 kernel_opts += " -append '%s %s %s %s'" % (self.kernel_cmdline,
1459 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), 1607 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'),
1460 self.bootparams) 1608 self.bootparams)
1461 if self.dtb: 1609 if self.dtb:
@@ -1469,14 +1617,17 @@ class BaseConfig(object):
1469 cmd = "%s %s" % (self.qemu_opt, kernel_opts) 1617 cmd = "%s %s" % (self.qemu_opt, kernel_opts)
1470 cmds = shlex.split(cmd) 1618 cmds = shlex.split(cmd)
1471 logger.info('Running %s\n' % cmd) 1619 logger.info('Running %s\n' % cmd)
1620 with open('/proc/uptime', 'r') as f:
1621 uptime_seconds = f.readline().split()[0]
1622 logger.info('Host uptime: %s\n' % uptime_seconds)
1472 pass_fds = [] 1623 pass_fds = []
1473 if self.taplock_descriptor: 1624 if self.taplock_descriptor:
1474 pass_fds = [self.taplock_descriptor.fileno()] 1625 pass_fds = [self.taplock_descriptor.fileno()]
1475 if len(self.portlocks): 1626 if len(self.portlocks):
1476 for descriptor in self.portlocks.values(): 1627 for descriptor in self.portlocks.values():
1477 pass_fds.append(descriptor.fileno()) 1628 pass_fds.append(descriptor.fileno())
1478 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds) 1629 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ)
1479 self.qemupid = process.pid 1630 self.qemuprocess = process
1480 retcode = process.wait() 1631 retcode = process.wait()
1481 if retcode: 1632 if retcode:
1482 if retcode == -signal.SIGTERM: 1633 if retcode == -signal.SIGTERM:
@@ -1484,6 +1635,13 @@ class BaseConfig(object):
1484 else: 1635 else:
1485 logger.error("Failed to run qemu: %s", process.stderr.read().decode()) 1636 logger.error("Failed to run qemu: %s", process.stderr.read().decode())
1486 1637
1638 def cleanup_cmd(self):
1639 cmd = self.get('QB_CLEANUP_CMD')
1640 if cmd != '':
1641 logger.info('Running cleanup command %s' % str(cmd))
1642 if subprocess.call(cmd, shell=True) != 0:
1643 raise RunQemuError('Failed to run %s' % str(cmd))
1644
1487 def cleanup(self): 1645 def cleanup(self):
1488 if self.cleaned: 1646 if self.cleaned:
1489 return 1647 return
@@ -1492,18 +1650,30 @@ class BaseConfig(object):
1492 signal.signal(signal.SIGTERM, signal.SIG_IGN) 1650 signal.signal(signal.SIGTERM, signal.SIG_IGN)
1493 1651
1494 logger.info("Cleaning up") 1652 logger.info("Cleaning up")
1653
1654 if self.qemuprocess:
1655 try:
1656 # give it some time to shut down, ignore return values and output
1657 self.qemuprocess.send_signal(signal.SIGTERM)
1658 self.qemuprocess.communicate(timeout=5)
1659 except subprocess.TimeoutExpired:
1660 self.qemuprocess.kill()
1661
1662 with open('/proc/uptime', 'r') as f:
1663 uptime_seconds = f.readline().split()[0]
1664 logger.info('Host uptime: %s\n' % uptime_seconds)
1495 if self.cleantap: 1665 if self.cleantap:
1496 cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native) 1666 cmd = ('sudo', self.qemuifdown, self.tap)
1497 logger.debug('Running %s' % str(cmd)) 1667 logger.debug('Running %s' % str(cmd))
1498 subprocess.check_call(cmd) 1668 subprocess.check_call(cmd)
1499 self.release_taplock() 1669 self.release_taplock()
1500 self.release_portlock()
1501 1670
1502 if self.nfs_running: 1671 if self.nfs_running:
1503 logger.info("Shutting down the userspace NFS server...") 1672 logger.info("Shutting down the userspace NFS server...")
1504 cmd = ("runqemu-export-rootfs", "stop", self.rootfs) 1673 cmd = ("runqemu-export-rootfs", "stop", self.rootfs)
1505 logger.debug('Running %s' % str(cmd)) 1674 logger.debug('Running %s' % str(cmd))
1506 subprocess.check_call(cmd) 1675 subprocess.check_call(cmd)
1676 self.release_portlock()
1507 1677
1508 if self.saved_stty: 1678 if self.saved_stty:
1509 subprocess.check_call(("stty", self.saved_stty)) 1679 subprocess.check_call(("stty", self.saved_stty))
@@ -1516,12 +1686,12 @@ class BaseConfig(object):
1516 else: 1686 else:
1517 shutil.rmtree(ent) 1687 shutil.rmtree(ent)
1518 1688
1689 # Deliberately ignore the return code of 'tput smam'.
1690 subprocess.call(["tput", "smam"])
1691
1519 self.cleaned = True 1692 self.cleaned = True
1520 1693
1521 def run_bitbake_env(self, mach=None): 1694 def run_bitbake_env(self, mach=None, target=''):
1522 bitbake = shutil.which('bitbake')
1523 if not bitbake:
1524 return
1525 1695
1526 if not mach: 1696 if not mach:
1527 mach = self.get('MACHINE') 1697 mach = self.get('MACHINE')
@@ -1530,23 +1700,37 @@ class BaseConfig(object):
1530 if multiconfig: 1700 if multiconfig:
1531 multiconfig = "mc:%s" % multiconfig 1701 multiconfig = "mc:%s" % multiconfig
1532 1702
1703 if self.rootfs and not target:
1704 target = self.rootfs
1705
1533 if mach: 1706 if mach:
1534 cmd = 'MACHINE=%s bitbake -e %s' % (mach, multiconfig) 1707 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1535 else: 1708 else:
1536 cmd = 'bitbake -e %s' % multiconfig 1709 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1710
1711 bitbake = shutil.which('bitbake')
1712 if not bitbake:
1713 raise OEPathError("Bitbake is needed to run '%s', but it is not found in PATH. Please source the bitbake build environment." % cmd.strip())
1537 1714
1538 logger.info('Running %s...' % cmd) 1715 logger.info('Running %s...' % cmd)
1539 return subprocess.check_output(cmd, shell=True).decode('utf-8') 1716 try:
1717 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1718 except subprocess.CalledProcessError as err:
1719 logger.warning("Couldn't run '%s' to gather environment information, maybe the target wasn't an image name, will retry with virtual/kernel as a target:\n%s" % (cmd, err.output.decode('utf-8')))
1720 # need something with IMAGE_NAME_SUFFIX/IMAGE_LINK_NAME defined (kernel also inherits image-artifact-names.bbclass)
1721 target = 'virtual/kernel'
1722 if mach:
1723 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1724 else:
1725 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1726 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1727
1540 1728
1541 def load_bitbake_env(self, mach=None): 1729 def load_bitbake_env(self, mach=None, target=None):
1542 if self.bitbake_e: 1730 if self.bitbake_e:
1543 return 1731 return
1544 1732
1545 try: 1733 self.bitbake_e = self.run_bitbake_env(mach=mach, target=target)
1546 self.bitbake_e = self.run_bitbake_env(mach=mach)
1547 except subprocess.CalledProcessError as err:
1548 self.bitbake_e = ''
1549 logger.warning("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8'))
1550 1734
1551 def validate_combos(self): 1735 def validate_combos(self):
1552 if (self.fstype in self.vmtypes) and self.kernel: 1736 if (self.fstype in self.vmtypes) and self.kernel:
@@ -1576,7 +1760,7 @@ class BaseConfig(object):
1576 return result 1760 return result
1577 raise RunQemuError("Native sysroot directory %s doesn't exist" % result) 1761 raise RunQemuError("Native sysroot directory %s doesn't exist" % result)
1578 else: 1762 else:
1579 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd) 1763 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % str(cmd))
1580 1764
1581 1765
1582def main(): 1766def main():
@@ -1592,22 +1776,21 @@ def main():
1592 subprocess.check_call([renice, str(os.getpid())]) 1776 subprocess.check_call([renice, str(os.getpid())])
1593 1777
1594 def sigterm_handler(signum, frame): 1778 def sigterm_handler(signum, frame):
1595 logger.info("SIGTERM received") 1779 logger.info("Received signal: %s" % (signum))
1596 os.kill(config.qemupid, signal.SIGTERM)
1597 config.cleanup() 1780 config.cleanup()
1598 # Deliberately ignore the return code of 'tput smam'.
1599 subprocess.call(["tput", "smam"])
1600 signal.signal(signal.SIGTERM, sigterm_handler) 1781 signal.signal(signal.SIGTERM, sigterm_handler)
1601 1782
1602 config.check_args() 1783 config.check_args()
1603 config.read_qemuboot() 1784 config.read_qemuboot()
1604 config.check_and_set() 1785 config.check_and_set()
1786 config.uncompress_rootfs()
1605 # Check whether the combos is valid or not 1787 # Check whether the combos is valid or not
1606 config.validate_combos() 1788 config.validate_combos()
1607 config.print_config() 1789 config.print_config()
1608 config.setup_network() 1790 config.setup_network()
1609 config.setup_rootfs() 1791 config.setup_rootfs()
1610 config.setup_final() 1792 config.setup_final()
1793 config.setup_cmd()
1611 config.start_qemu() 1794 config.start_qemu()
1612 except RunQemuError as err: 1795 except RunQemuError as err:
1613 logger.error(err) 1796 logger.error(err)
@@ -1617,9 +1800,8 @@ def main():
1617 traceback.print_exc() 1800 traceback.print_exc()
1618 return 1 1801 return 1
1619 finally: 1802 finally:
1803 config.cleanup_cmd()
1620 config.cleanup() 1804 config.cleanup()
1621 # Deliberately ignore the return code of 'tput smam'.
1622 subprocess.call(["tput", "smam"])
1623 1805
1624if __name__ == "__main__": 1806if __name__ == "__main__":
1625 sys.exit(main()) 1807 sys.exit(main())