diff options
Diffstat (limited to 'scripts/runqemu')
-rwxr-xr-x | scripts/runqemu | 664 |
1 files changed, 443 insertions, 221 deletions
diff --git a/scripts/runqemu b/scripts/runqemu index 532f2e338d..3d77046972 100755 --- a/scripts/runqemu +++ b/scripts/runqemu | |||
@@ -18,6 +18,7 @@ import shutil | |||
18 | import glob | 18 | import glob |
19 | import configparser | 19 | import configparser |
20 | import signal | 20 | import signal |
21 | import time | ||
21 | 22 | ||
22 | class RunQemuError(Exception): | 23 | class RunQemuError(Exception): |
23 | """Custom exception to raise on known errors.""" | 24 | """Custom exception to raise on known errors.""" |
@@ -65,20 +66,25 @@ of the following environment variables (in any order): | |||
65 | MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) | 66 | MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) |
66 | Simplified QEMU command-line options can be passed with: | 67 | Simplified QEMU command-line options can be passed with: |
67 | nographic - disable video console | 68 | nographic - disable video console |
69 | nonetwork - disable network connectivity | ||
68 | novga - Disable VGA emulation completely | 70 | novga - Disable VGA emulation completely |
69 | sdl - choose the SDL UI frontend | 71 | sdl - choose the SDL UI frontend |
70 | gtk - choose the Gtk UI frontend | 72 | gtk - choose the Gtk UI frontend |
71 | gl - enable virgl-based GL acceleration (also needs gtk or sdl options) | 73 | gl - enable virgl-based GL acceleration (also needs gtk or sdl options) |
72 | 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) |
73 | 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 |
76 | (hint: if /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create | ||
77 | one suitable for mesa llvmpipe software renderer) | ||
74 | serial - enable a serial console on /dev/ttyS0 | 78 | serial - enable a serial console on /dev/ttyS0 |
75 | 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) |
76 | slirp - enable user networking, no root privileges is required | 80 | slirp - enable user networking, no root privilege is required |
77 | snapshot - don't write changes to back to images | 81 | snapshot - don't write changes back to images |
78 | 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) |
79 | 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) |
80 | publicvnc - enable a VNC server open to all hosts | 84 | publicvnc - enable a VNC server open to all hosts |
81 | 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) | ||
82 | [*/]ovmf* - OVMF firmware file or base name for booting with UEFI | 88 | [*/]ovmf* - OVMF firmware file or base name for booting with UEFI |
83 | tcpserial=<port> - specify tcp serial port number | 89 | tcpserial=<port> - specify tcp serial port number |
84 | qemuparams=<xyz> - specify custom parameters to QEMU | 90 | qemuparams=<xyz> - specify custom parameters to QEMU |
@@ -113,10 +119,10 @@ def check_tun(): | |||
113 | if not os.access(dev_tun, os.W_OK): | 119 | if not os.access(dev_tun, os.W_OK): |
114 | 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)) |
115 | 121 | ||
116 | def get_first_file(cmds): | 122 | def get_first_file(globs): |
117 | """Return first file found in wildcard cmds""" | 123 | """Return first file found in wildcard globs""" |
118 | for cmd in cmds: | 124 | for g in globs: |
119 | all_files = glob.glob(cmd) | 125 | all_files = glob.glob(g) |
120 | if all_files: | 126 | if all_files: |
121 | for f in all_files: | 127 | for f in all_files: |
122 | if not os.path.isdir(f): | 128 | if not os.path.isdir(f): |
@@ -139,12 +145,12 @@ class BaseConfig(object): | |||
139 | 'OE_TMPDIR', | 145 | 'OE_TMPDIR', |
140 | 'OECORE_NATIVE_SYSROOT', | 146 | 'OECORE_NATIVE_SYSROOT', |
141 | 'MULTICONFIG', | 147 | 'MULTICONFIG', |
148 | 'SERIAL_CONSOLES', | ||
142 | ) | 149 | ) |
143 | 150 | ||
144 | self.qemu_opt = '' | 151 | self.qemu_opt = '' |
145 | self.qemu_opt_script = '' | 152 | self.qemu_opt_script = '' |
146 | self.qemuparams = '' | 153 | self.qemuparams = '' |
147 | self.clean_nfs_dir = False | ||
148 | self.nfs_server = '' | 154 | self.nfs_server = '' |
149 | self.rootfs = '' | 155 | self.rootfs = '' |
150 | # File name(s) of a OVMF firmware file or variable store, | 156 | # File name(s) of a OVMF firmware file or variable store, |
@@ -173,6 +179,15 @@ class BaseConfig(object): | |||
173 | self.nfs_running = False | 179 | self.nfs_running = False |
174 | self.serialconsole = False | 180 | self.serialconsole = False |
175 | self.serialstdio = False | 181 | self.serialstdio = False |
182 | self.nographic = False | ||
183 | self.nonetwork = False | ||
184 | self.sdl = False | ||
185 | self.gtk = False | ||
186 | self.gl = False | ||
187 | self.gl_es = False | ||
188 | self.egl_headless = False | ||
189 | self.publicvnc = False | ||
190 | self.novga = False | ||
176 | self.cleantap = False | 191 | self.cleantap = False |
177 | self.saved_stty = '' | 192 | self.saved_stty = '' |
178 | self.audio_enabled = False | 193 | self.audio_enabled = False |
@@ -184,12 +199,14 @@ class BaseConfig(object): | |||
184 | self.snapshot = False | 199 | self.snapshot = False |
185 | self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") | 200 | self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") |
186 | self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', | 201 | self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', |
187 | 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz') | 202 | 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz', |
203 | 'squashfs', 'squashfs-xz', 'squashfs-lzo', | ||
204 | 'squashfs-lz4', 'squashfs-zst') | ||
188 | self.vmtypes = ('hddimg', 'iso') | 205 | self.vmtypes = ('hddimg', 'iso') |
189 | self.fsinfo = {} | 206 | self.fsinfo = {} |
190 | self.network_device = "-device e1000,netdev=net0,mac=@MAC@" | 207 | self.network_device = "-device e1000,netdev=net0,mac=@MAC@" |
191 | self.cmdline_ip_slirp = "ip=dhcp" | 208 | self.cmdline_ip_slirp = "ip=dhcp" |
192 | self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0" | 209 | 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" |
193 | # Use different mac section for tap and slirp to avoid | 210 | # Use different mac section for tap and slirp to avoid |
194 | # conflicts, e.g., when one is running with tap, the other is | 211 | # conflicts, e.g., when one is running with tap, the other is |
195 | # running with slirp. | 212 | # running with slirp. |
@@ -199,9 +216,15 @@ class BaseConfig(object): | |||
199 | self.mac_tap = "52:54:00:12:34:" | 216 | self.mac_tap = "52:54:00:12:34:" |
200 | self.mac_slirp = "52:54:00:12:35:" | 217 | self.mac_slirp = "52:54:00:12:35:" |
201 | # pid of the actual qemu process | 218 | # pid of the actual qemu process |
202 | self.qemupid = None | 219 | self.qemu_environ = os.environ.copy() |
220 | self.qemuprocess = None | ||
203 | # avoid cleanup twice | 221 | # avoid cleanup twice |
204 | self.cleaned = False | 222 | self.cleaned = False |
223 | # Files to cleanup after run | ||
224 | self.cleanup_files = [] | ||
225 | self.qmp = None | ||
226 | self.guest_agent = False | ||
227 | self.guest_agent_sockpath = '/tmp/qga.sock' | ||
205 | 228 | ||
206 | def acquire_taplock(self, error=True): | 229 | def acquire_taplock(self, error=True): |
207 | logger.debug("Acquiring lockfile %s..." % self.taplock) | 230 | logger.debug("Acquiring lockfile %s..." % self.taplock) |
@@ -223,9 +246,12 @@ class BaseConfig(object): | |||
223 | def release_taplock(self): | 246 | def release_taplock(self): |
224 | if self.taplock_descriptor: | 247 | if self.taplock_descriptor: |
225 | logger.debug("Releasing lockfile for tap device '%s'" % self.tap) | 248 | logger.debug("Releasing lockfile for tap device '%s'" % self.tap) |
226 | fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN) | 249 | # We pass the fd to the qemu process and if we unlock here, it would unlock for |
250 | # that too. Therefore don't unlock, just close | ||
251 | # fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN) | ||
227 | self.taplock_descriptor.close() | 252 | self.taplock_descriptor.close() |
228 | os.remove(self.taplock) | 253 | # Removing the file is a potential race, don't do that either |
254 | # os.remove(self.taplock) | ||
229 | self.taplock_descriptor = None | 255 | self.taplock_descriptor = None |
230 | 256 | ||
231 | def check_free_port(self, host, port, lockdir): | 257 | def check_free_port(self, host, port, lockdir): |
@@ -263,17 +289,23 @@ class BaseConfig(object): | |||
263 | 289 | ||
264 | def release_portlock(self, lockfile=None): | 290 | def release_portlock(self, lockfile=None): |
265 | if lockfile != None: | 291 | if lockfile != None: |
266 | logger.debug("Releasing lockfile '%s'" % lockfile) | 292 | logger.debug("Releasing lockfile '%s'" % lockfile) |
267 | fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) | 293 | # We pass the fd to the qemu process and if we unlock here, it would unlock for |
268 | self.portlocks[lockfile].close() | 294 | # that too. Therefore don't unlock, just close |
269 | os.remove(lockfile) | 295 | # fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) |
270 | del self.portlocks[lockfile] | 296 | self.portlocks[lockfile].close() |
297 | # Removing the file is a potential race, don't do that either | ||
298 | # os.remove(lockfile) | ||
299 | del self.portlocks[lockfile] | ||
271 | elif len(self.portlocks): | 300 | elif len(self.portlocks): |
272 | for lockfile, descriptor in self.portlocks.items(): | 301 | for lockfile, descriptor in self.portlocks.items(): |
273 | logger.debug("Releasing lockfile '%s'" % lockfile) | 302 | logger.debug("Releasing lockfile '%s'" % lockfile) |
274 | fcntl.flock(descriptor, fcntl.LOCK_UN) | 303 | # We pass the fd to the qemu process and if we unlock here, it would unlock for |
304 | # that too. Therefore don't unlock, just close | ||
305 | # fcntl.flock(descriptor, fcntl.LOCK_UN) | ||
275 | descriptor.close() | 306 | descriptor.close() |
276 | os.remove(lockfile) | 307 | # Removing the file is a potential race, don't do that either |
308 | # os.remove(lockfile) | ||
277 | self.portlocks = {} | 309 | self.portlocks = {} |
278 | 310 | ||
279 | def get(self, key): | 311 | def get(self, key): |
@@ -331,21 +363,21 @@ class BaseConfig(object): | |||
331 | def check_arg_path(self, p): | 363 | def check_arg_path(self, p): |
332 | """ | 364 | """ |
333 | - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf | 365 | - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf |
334 | - Check whether is a kernel file | 366 | - Check whether it is a kernel file |
335 | - Check whether is a image file | 367 | - Check whether it is an image file |
336 | - Check whether it is a nfs dir | 368 | - Check whether it is an NFS dir |
337 | - Check whether it is a OVMF flash file | 369 | - Check whether it is an OVMF flash file |
338 | """ | 370 | """ |
339 | if p.endswith('.qemuboot.conf'): | 371 | if p.endswith('.qemuboot.conf'): |
340 | self.qemuboot = p | 372 | self.qemuboot = p |
341 | self.qbconfload = True | 373 | self.qbconfload = True |
342 | elif re.search('\.bin$', p) or re.search('bzImage', p) or \ | 374 | elif re.search('\\.bin$', p) or re.search('bzImage', p) or \ |
343 | re.search('zImage', p) or re.search('vmlinux', p) or \ | 375 | re.search('zImage', p) or re.search('vmlinux', p) or \ |
344 | re.search('fitImage', p) or re.search('uImage', p): | 376 | re.search('fitImage', p) or re.search('uImage', p): |
345 | self.kernel = p | 377 | self.kernel = p |
346 | elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p): | 378 | elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)): |
347 | self.rootfs = p | 379 | self.rootfs = p |
348 | # Check filename against self.fstypes can hanlde <file>.cpio.gz, | 380 | # Check filename against self.fstypes can handle <file>.cpio.gz, |
349 | # otherwise, its type would be "gz", which is incorrect. | 381 | # otherwise, its type would be "gz", which is incorrect. |
350 | fst = "" | 382 | fst = "" |
351 | for t in self.fstypes: | 383 | for t in self.fstypes: |
@@ -353,18 +385,24 @@ class BaseConfig(object): | |||
353 | fst = t | 385 | fst = t |
354 | break | 386 | break |
355 | if not fst: | 387 | if not fst: |
356 | m = re.search('.*\.(.*)$', self.rootfs) | 388 | m = re.search('.*\\.(.*)$', self.rootfs) |
357 | if m: | 389 | if m: |
358 | fst = m.group(1) | 390 | fst = m.group(1) |
359 | if fst: | 391 | if fst: |
360 | self.check_arg_fstype(fst) | 392 | self.check_arg_fstype(fst) |
361 | qb = re.sub('\.' + fst + "$", '', self.rootfs) | 393 | qb = re.sub('\\.' + fst + "$", '.qemuboot.conf', self.rootfs) |
362 | qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf') | ||
363 | if os.path.exists(qb): | 394 | if os.path.exists(qb): |
364 | self.qemuboot = qb | 395 | self.qemuboot = qb |
365 | self.qbconfload = True | 396 | self.qbconfload = True |
366 | else: | 397 | else: |
367 | logger.warning("%s doesn't exist" % qb) | 398 | logger.warning("%s doesn't exist, will try to remove '.rootfs' from filename" % qb) |
399 | # They to remove .rootfs (IMAGE_NAME_SUFFIX) as well | ||
400 | qb = re.sub('\\.rootfs.qemuboot.conf$', '.qemuboot.conf', qb) | ||
401 | if os.path.exists(qb): | ||
402 | self.qemuboot = qb | ||
403 | self.qbconfload = True | ||
404 | else: | ||
405 | logger.warning("%s doesn't exist" % qb) | ||
368 | else: | 406 | else: |
369 | raise RunQemuError("Can't find FSTYPE from: %s" % p) | 407 | raise RunQemuError("Can't find FSTYPE from: %s" % p) |
370 | 408 | ||
@@ -398,6 +436,7 @@ class BaseConfig(object): | |||
398 | # are there other scenarios in which we need to support being | 436 | # are there other scenarios in which we need to support being |
399 | # invoked by bitbake? | 437 | # invoked by bitbake? |
400 | deploy = self.get('DEPLOY_DIR_IMAGE') | 438 | deploy = self.get('DEPLOY_DIR_IMAGE') |
439 | image_link_name = self.get('IMAGE_LINK_NAME') | ||
401 | bbchild = deploy and self.get('OE_TMPDIR') | 440 | bbchild = deploy and self.get('OE_TMPDIR') |
402 | if bbchild: | 441 | if bbchild: |
403 | self.set_machine_deploy_dir(arg, deploy) | 442 | self.set_machine_deploy_dir(arg, deploy) |
@@ -422,23 +461,27 @@ class BaseConfig(object): | |||
422 | else: | 461 | else: |
423 | logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) | 462 | logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) |
424 | self.set("MACHINE", arg) | 463 | self.set("MACHINE", arg) |
425 | 464 | if not image_link_name: | |
426 | def set_dri_path(self): | 465 | s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M) |
427 | # As runqemu can be run within bitbake (when using testimage, for example), | 466 | if s: |
428 | # we need to ensure that we run host pkg-config, and that it does not | 467 | image_link_name = s.group(1) |
429 | # get mis-directed to native build paths set by bitbake. | 468 | self.set("IMAGE_LINK_NAME", image_link_name) |
430 | try: | 469 | logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name) |
431 | del os.environ['PKG_CONFIG_PATH'] | 470 | |
432 | del os.environ['PKG_CONFIG_DIR'] | 471 | def set_mesa_paths(self): |
433 | del os.environ['PKG_CONFIG_LIBDIR'] | 472 | drivers_path = os.path.join(self.bindir_native, '../lib/dri') |
434 | del os.environ['PKG_CONFIG_SYSROOT_DIR'] | 473 | gbm_path = os.path.join(self.bindir_native, '../lib/gbm') |
435 | except KeyError: | 474 | if not os.path.exists(drivers_path) or not os.listdir(drivers_path) \ |
436 | pass | 475 | or not os.path.exists(gbm_path) or not os.listdir(gbm_path): |
437 | try: | 476 | raise RunQemuError(""" |
438 | dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True) | 477 | qemu has been built without opengl support and accelerated graphics support is not available. |
439 | except subprocess.CalledProcessError as e: | 478 | To enable it, add: |
440 | 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 | DISTRO_FEATURES_NATIVE:append = " opengl" |
441 | os.environ['LIBGL_DRIVERS_PATH'] = dripath.decode('utf-8').strip() | 480 | DISTRO_FEATURES_NATIVESDK:append = " opengl" |
481 | to your build configuration. | ||
482 | """) | ||
483 | self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path | ||
484 | self.qemu_environ['GBM_BACKENDS_PATH'] = gbm_path | ||
442 | 485 | ||
443 | def check_args(self): | 486 | def check_args(self): |
444 | for debug in ("-d", "--debug"): | 487 | for debug in ("-d", "--debug"): |
@@ -452,51 +495,32 @@ class BaseConfig(object): | |||
452 | sys.argv.remove(quiet) | 495 | sys.argv.remove(quiet) |
453 | 496 | ||
454 | if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: | 497 | if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: |
455 | os.environ['SDL_RENDER_DRIVER'] = 'software' | 498 | self.qemu_environ['SDL_RENDER_DRIVER'] = 'software' |
499 | self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false' | ||
456 | 500 | ||
457 | unknown_arg = "" | 501 | unknown_arg = "" |
458 | for arg in sys.argv[1:]: | 502 | for arg in sys.argv[1:]: |
459 | if arg in self.fstypes + self.vmtypes + self.wictypes: | 503 | if arg in self.fstypes + self.vmtypes + self.wictypes: |
460 | self.check_arg_fstype(arg) | 504 | self.check_arg_fstype(arg) |
461 | elif arg == 'nographic': | 505 | elif arg == 'nographic': |
462 | if ('sdl' in sys.argv): | 506 | self.nographic = True |
463 | raise RunQemuError('Option nographic makes no sense alongside the sdl option.' % (arg)) | 507 | elif arg == "nonetwork": |
464 | if ('gtk' in sys.argv): | 508 | self.nonetwork = True |
465 | raise RunQemuError('Option nographic makes no sense alongside the gtk option.' % (arg)) | ||
466 | self.qemu_opt_script += ' -nographic' | ||
467 | self.kernel_cmdline_script += ' console=ttyS0' | ||
468 | elif arg == 'sdl': | 509 | elif arg == 'sdl': |
469 | if 'gl' in sys.argv[1:]: | 510 | self.sdl = True |
470 | self.set_dri_path() | ||
471 | self.qemu_opt_script += ' -vga virtio -display sdl,gl=on,show-cursor=on' | ||
472 | elif 'gl-es' in sys.argv[1:]: | ||
473 | self.set_dri_path() | ||
474 | self.qemu_opt_script += ' -vga virtio -display sdl,gl=es,show-cursor=on' | ||
475 | else: | ||
476 | self.qemu_opt_script += ' -display sdl,show-cursor=on' | ||
477 | elif arg == 'gtk': | 511 | elif arg == 'gtk': |
478 | if 'gl' in sys.argv[1:]: | 512 | self.gtk = True |
479 | self.set_dri_path() | 513 | elif arg == 'gl': |
480 | self.qemu_opt_script += ' -vga virtio -display gtk,gl=on,show-cursor=on' | 514 | self.gl = True |
481 | elif 'gl-es' in sys.argv[1:]: | 515 | elif arg == 'gl-es': |
482 | self.set_dri_path() | 516 | self.gl_es = True |
483 | self.qemu_opt_script += ' -vga virtio -display gtk,gl=es,show-cursor=on' | ||
484 | else: | ||
485 | self.qemu_opt_script += ' -display gtk,show-cursor=on' | ||
486 | elif arg == 'gl' or arg == 'gl-es': | ||
487 | # These args are handled inside sdl or gtk blocks above | ||
488 | if ('gtk' not in sys.argv) and ('sdl' not in sys.argv): | ||
489 | raise RunQemuError('Option %s also needs gtk or sdl option.' % (arg)) | ||
490 | elif arg == 'egl-headless': | 517 | elif arg == 'egl-headless': |
491 | self.set_dri_path() | 518 | self.egl_headless = True |
492 | self.qemu_opt_script += ' -vga virtio -display egl-headless,show-cursor=on' | ||
493 | elif arg == 'novga': | 519 | elif arg == 'novga': |
494 | self.qemu_opt_script += ' -vga none' | 520 | self.novga = True |
495 | elif arg == 'serial': | 521 | elif arg == 'serial': |
496 | self.kernel_cmdline_script += ' console=ttyS0' | ||
497 | self.serialconsole = True | 522 | self.serialconsole = True |
498 | elif arg == "serialstdio": | 523 | elif arg == "serialstdio": |
499 | self.kernel_cmdline_script += ' console=ttyS0' | ||
500 | self.serialstdio = True | 524 | self.serialstdio = True |
501 | elif arg == 'audio': | 525 | elif arg == 'audio': |
502 | logger.info("Enabling audio in qemu") | 526 | logger.info("Enabling audio in qemu") |
@@ -513,7 +537,16 @@ class BaseConfig(object): | |||
513 | elif arg == 'snapshot': | 537 | elif arg == 'snapshot': |
514 | self.snapshot = True | 538 | self.snapshot = True |
515 | elif arg == 'publicvnc': | 539 | elif arg == 'publicvnc': |
540 | self.publicvnc = True | ||
516 | self.qemu_opt_script += ' -vnc :0' | 541 | self.qemu_opt_script += ' -vnc :0' |
542 | elif arg == 'guestagent': | ||
543 | self.guest_agent = True | ||
544 | elif arg == "qmp": | ||
545 | self.qmp = "unix:qmp.sock" | ||
546 | elif arg.startswith("qmp="): | ||
547 | self.qmp = arg[len('qmp='):] | ||
548 | elif arg.startswith('guestagent-sockpath='): | ||
549 | self.guest_agent_sockpath = '%s' % arg[len('guestagent-sockpath='):] | ||
517 | elif arg.startswith('tcpserial='): | 550 | elif arg.startswith('tcpserial='): |
518 | self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] | 551 | self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] |
519 | elif arg.startswith('qemuparams='): | 552 | elif arg.startswith('qemuparams='): |
@@ -545,21 +578,28 @@ class BaseConfig(object): | |||
545 | self.check_arg_machine(unknown_arg) | 578 | self.check_arg_machine(unknown_arg) |
546 | 579 | ||
547 | if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): | 580 | if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): |
548 | self.load_bitbake_env() | 581 | self.load_bitbake_env(target=self.rootfs) |
549 | s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) | 582 | s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) |
550 | if s: | 583 | if s: |
551 | self.set("DEPLOY_DIR_IMAGE", s.group(1)) | 584 | self.set("DEPLOY_DIR_IMAGE", s.group(1)) |
552 | 585 | ||
586 | if not self.get('IMAGE_LINK_NAME') and self.rootfs: | ||
587 | s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M) | ||
588 | if s: | ||
589 | image_link_name = s.group(1) | ||
590 | self.set("IMAGE_LINK_NAME", image_link_name) | ||
591 | logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name) | ||
592 | |||
553 | def check_kvm(self): | 593 | def check_kvm(self): |
554 | """Check kvm and kvm-host""" | 594 | """Check kvm and kvm-host""" |
555 | if not (self.kvm_enabled or self.vhost_enabled): | 595 | if not (self.kvm_enabled or self.vhost_enabled): |
556 | self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU')) | 596 | self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'), self.get('QB_SMP')) |
557 | return | 597 | return |
558 | 598 | ||
559 | if not self.get('QB_CPU_KVM'): | 599 | if not self.get('QB_CPU_KVM'): |
560 | raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") | 600 | raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") |
561 | 601 | ||
562 | self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM')) | 602 | self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'), self.get('QB_SMP')) |
563 | yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" | 603 | yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" |
564 | yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" | 604 | yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" |
565 | dev_kvm = '/dev/kvm' | 605 | dev_kvm = '/dev/kvm' |
@@ -579,11 +619,6 @@ class BaseConfig(object): | |||
579 | 619 | ||
580 | if os.access(dev_kvm, os.W_OK|os.R_OK): | 620 | if os.access(dev_kvm, os.W_OK|os.R_OK): |
581 | self.qemu_opt_script += ' -enable-kvm' | 621 | self.qemu_opt_script += ' -enable-kvm' |
582 | if self.get('MACHINE') == "qemux86": | ||
583 | # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs | ||
584 | # See YOCTO #12301 | ||
585 | # On 64 bit we use x2apic | ||
586 | self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic" | ||
587 | else: | 622 | else: |
588 | logger.error("You have no read or write permission on /dev/kvm.") | 623 | logger.error("You have no read or write permission on /dev/kvm.") |
589 | logger.error("Please change the ownership of this file as described at:") | 624 | logger.error("Please change the ownership of this file as described at:") |
@@ -624,10 +659,10 @@ class BaseConfig(object): | |||
624 | elif fsflag == 'kernel-in-fs': | 659 | elif fsflag == 'kernel-in-fs': |
625 | wic_fs = False | 660 | wic_fs = False |
626 | else: | 661 | else: |
627 | logger.warn('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) | 662 | logger.warning('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) |
628 | continue | 663 | continue |
629 | else: | 664 | else: |
630 | logger.warn('QB_FSINFO is not supported for image type "%s"', fstype) | 665 | logger.warning('QB_FSINFO is not supported for image type "%s"', fstype) |
631 | continue | 666 | continue |
632 | 667 | ||
633 | if fstype in self.fsinfo: | 668 | if fstype in self.fsinfo: |
@@ -660,16 +695,16 @@ class BaseConfig(object): | |||
660 | 695 | ||
661 | if self.rootfs and not os.path.exists(self.rootfs): | 696 | if self.rootfs and not os.path.exists(self.rootfs): |
662 | # Lazy rootfs | 697 | # Lazy rootfs |
663 | self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), | 698 | self.rootfs = "%s/%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), |
664 | self.rootfs, self.get('MACHINE'), | 699 | self.get('IMAGE_LINK_NAME'), |
665 | self.fstype) | 700 | self.fstype) |
666 | elif not self.rootfs: | 701 | elif not self.rootfs: |
667 | cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) | 702 | glob_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) |
668 | cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) | 703 | glob_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) |
669 | cmds = (cmd_name, cmd_link) | 704 | globs = (glob_name, glob_link) |
670 | self.rootfs = get_first_file(cmds) | 705 | self.rootfs = get_first_file(globs) |
671 | if not self.rootfs: | 706 | if not self.rootfs: |
672 | raise RunQemuError("Failed to find rootfs: %s or %s" % cmds) | 707 | raise RunQemuError("Failed to find rootfs: %s or %s" % globs) |
673 | 708 | ||
674 | if not os.path.exists(self.rootfs): | 709 | if not os.path.exists(self.rootfs): |
675 | raise RunQemuError("Can't find rootfs: %s" % self.rootfs) | 710 | raise RunQemuError("Can't find rootfs: %s" % self.rootfs) |
@@ -729,10 +764,10 @@ class BaseConfig(object): | |||
729 | kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) | 764 | kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) |
730 | kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) | 765 | kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) |
731 | kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) | 766 | kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) |
732 | cmds = (kernel_match_name, kernel_match_link, kernel_startswith) | 767 | globs = (kernel_match_name, kernel_match_link, kernel_startswith) |
733 | self.kernel = get_first_file(cmds) | 768 | self.kernel = get_first_file(globs) |
734 | if not self.kernel: | 769 | if not self.kernel: |
735 | raise RunQemuError('KERNEL not found: %s, %s or %s' % cmds) | 770 | raise RunQemuError('KERNEL not found: %s, %s or %s' % globs) |
736 | 771 | ||
737 | if not os.path.exists(self.kernel): | 772 | if not os.path.exists(self.kernel): |
738 | raise RunQemuError("KERNEL %s not found" % self.kernel) | 773 | raise RunQemuError("KERNEL %s not found" % self.kernel) |
@@ -749,13 +784,13 @@ class BaseConfig(object): | |||
749 | dtb = self.get('QB_DTB') | 784 | dtb = self.get('QB_DTB') |
750 | if dtb: | 785 | if dtb: |
751 | deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') | 786 | deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') |
752 | cmd_match = "%s/%s" % (deploy_dir_image, dtb) | 787 | glob_match = "%s/%s" % (deploy_dir_image, dtb) |
753 | cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb) | 788 | glob_startswith = "%s/%s*" % (deploy_dir_image, dtb) |
754 | cmd_wild = "%s/*.dtb" % deploy_dir_image | 789 | glob_wild = "%s/*.dtb" % deploy_dir_image |
755 | cmds = (cmd_match, cmd_startswith, cmd_wild) | 790 | globs = (glob_match, glob_startswith, glob_wild) |
756 | self.dtb = get_first_file(cmds) | 791 | self.dtb = get_first_file(globs) |
757 | if not os.path.exists(self.dtb): | 792 | if not os.path.exists(self.dtb): |
758 | raise RunQemuError('DTB not found: %s, %s or %s' % cmds) | 793 | raise RunQemuError('DTB not found: %s, %s or %s' % globs) |
759 | 794 | ||
760 | def check_bios(self): | 795 | def check_bios(self): |
761 | """Check and set bios""" | 796 | """Check and set bios""" |
@@ -779,7 +814,7 @@ class BaseConfig(object): | |||
779 | raise RunQemuError('BIOS not found: %s' % bios_match_name) | 814 | raise RunQemuError('BIOS not found: %s' % bios_match_name) |
780 | 815 | ||
781 | if not os.path.exists(self.bios): | 816 | if not os.path.exists(self.bios): |
782 | raise RunQemuError("KERNEL %s not found" % self.bios) | 817 | raise RunQemuError("BIOS %s not found" % self.bios) |
783 | 818 | ||
784 | 819 | ||
785 | def check_mem(self): | 820 | def check_mem(self): |
@@ -806,7 +841,7 @@ class BaseConfig(object): | |||
806 | self.set('QB_MEM', qb_mem) | 841 | self.set('QB_MEM', qb_mem) |
807 | 842 | ||
808 | mach = self.get('MACHINE') | 843 | mach = self.get('MACHINE') |
809 | if not mach.startswith('qemumips'): | 844 | if not mach.startswith(('qemumips', 'qemux86', 'qemuloongarch64')): |
810 | self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' | 845 | self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' |
811 | 846 | ||
812 | self.qemu_opt_script += ' %s' % self.get('QB_MEM') | 847 | self.qemu_opt_script += ' %s' % self.get('QB_MEM') |
@@ -818,11 +853,11 @@ class BaseConfig(object): | |||
818 | if self.get('QB_TCPSERIAL_OPT'): | 853 | if self.get('QB_TCPSERIAL_OPT'): |
819 | self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) | 854 | self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) |
820 | else: | 855 | else: |
821 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port | 856 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port |
822 | 857 | ||
823 | if len(ports) > 1: | 858 | if len(ports) > 1: |
824 | for port in ports[1:]: | 859 | for port in ports[1:]: |
825 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port | 860 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port |
826 | 861 | ||
827 | def check_and_set(self): | 862 | def check_and_set(self): |
828 | """Check configs sanity and set when needed""" | 863 | """Check configs sanity and set when needed""" |
@@ -865,8 +900,10 @@ class BaseConfig(object): | |||
865 | machine = self.get('MACHINE') | 900 | machine = self.get('MACHINE') |
866 | if not machine: | 901 | if not machine: |
867 | machine = os.path.basename(deploy_dir_image) | 902 | machine = os.path.basename(deploy_dir_image) |
868 | self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image, | 903 | if not self.get('IMAGE_LINK_NAME'): |
869 | self.rootfs, machine) | 904 | raise RunQemuError("IMAGE_LINK_NAME wasn't set to find corresponding .qemuboot.conf file") |
905 | self.qemuboot = "%s/%s.qemuboot.conf" % (deploy_dir_image, | ||
906 | self.get('IMAGE_LINK_NAME')) | ||
870 | else: | 907 | else: |
871 | cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image | 908 | cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image |
872 | logger.debug('Running %s...' % cmd) | 909 | logger.debug('Running %s...' % cmd) |
@@ -987,19 +1024,16 @@ class BaseConfig(object): | |||
987 | if self.slirp_enabled: | 1024 | if self.slirp_enabled: |
988 | self.nfs_server = '10.0.2.2' | 1025 | self.nfs_server = '10.0.2.2' |
989 | else: | 1026 | else: |
990 | self.nfs_server = '192.168.7.1' | 1027 | self.nfs_server = '192.168.7.@GATEWAY@' |
991 | |||
992 | # Figure out a new nfs_instance to allow multiple qemus running. | ||
993 | ps = subprocess.check_output(("ps", "auxww")).decode('utf-8') | ||
994 | pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) ' | ||
995 | all_instances = re.findall(pattern, ps, re.M) | ||
996 | if all_instances: | ||
997 | all_instances.sort(key=int) | ||
998 | self.nfs_instance = int(all_instances.pop()) + 1 | ||
999 | 1028 | ||
1000 | nfsd_port = 3049 + 2 * self.nfs_instance | 1029 | nfsd_port = 3048 + self.nfs_instance |
1001 | mountd_port = 3048 + 2 * self.nfs_instance | 1030 | lockdir = "/tmp/qemu-port-locks" |
1031 | self.make_lock_dir(lockdir) | ||
1032 | while not self.check_free_port('localhost', nfsd_port, lockdir): | ||
1033 | self.nfs_instance += 1 | ||
1034 | nfsd_port += 1 | ||
1002 | 1035 | ||
1036 | mountd_port = nfsd_port | ||
1003 | # Export vars for runqemu-export-rootfs | 1037 | # Export vars for runqemu-export-rootfs |
1004 | export_dict = { | 1038 | export_dict = { |
1005 | 'NFS_INSTANCE': self.nfs_instance, | 1039 | 'NFS_INSTANCE': self.nfs_instance, |
@@ -1010,7 +1044,11 @@ class BaseConfig(object): | |||
1010 | # Use '%s' since they are integers | 1044 | # Use '%s' since they are integers |
1011 | os.putenv(k, '%s' % v) | 1045 | os.putenv(k, '%s' % v) |
1012 | 1046 | ||
1013 | self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s" % (nfsd_port, mountd_port) | 1047 | qb_nfsrootfs_extra_opt = self.get("QB_NFSROOTFS_EXTRA_OPT") |
1048 | if qb_nfsrootfs_extra_opt and not qb_nfsrootfs_extra_opt.startswith(","): | ||
1049 | qb_nfsrootfs_extra_opt = "," + qb_nfsrootfs_extra_opt | ||
1050 | |||
1051 | self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s%s" % (nfsd_port, mountd_port, qb_nfsrootfs_extra_opt) | ||
1014 | 1052 | ||
1015 | # Extract .tar.bz2 or .tar.bz if no nfs dir | 1053 | # Extract .tar.bz2 or .tar.bz if no nfs dir |
1016 | if not (self.rootfs and os.path.isdir(self.rootfs)): | 1054 | if not (self.rootfs and os.path.isdir(self.rootfs)): |
@@ -1033,22 +1071,41 @@ class BaseConfig(object): | |||
1033 | cmd = ('runqemu-extract-sdk', src, dest) | 1071 | cmd = ('runqemu-extract-sdk', src, dest) |
1034 | logger.info('Running %s...' % str(cmd)) | 1072 | logger.info('Running %s...' % str(cmd)) |
1035 | if subprocess.call(cmd) != 0: | 1073 | if subprocess.call(cmd) != 0: |
1036 | raise RunQemuError('Failed to run %s' % cmd) | 1074 | raise RunQemuError('Failed to run %s' % str(cmd)) |
1037 | self.clean_nfs_dir = True | ||
1038 | self.rootfs = dest | 1075 | self.rootfs = dest |
1076 | self.cleanup_files.append(self.rootfs) | ||
1077 | self.cleanup_files.append('%s.pseudo_state' % self.rootfs) | ||
1039 | 1078 | ||
1040 | # Start the userspace NFS server | 1079 | # Start the userspace NFS server |
1041 | cmd = ('runqemu-export-rootfs', 'start', self.rootfs) | 1080 | cmd = ('runqemu-export-rootfs', 'start', self.rootfs) |
1042 | logger.info('Running %s...' % str(cmd)) | 1081 | logger.info('Running %s...' % str(cmd)) |
1043 | if subprocess.call(cmd) != 0: | 1082 | if subprocess.call(cmd) != 0: |
1044 | raise RunQemuError('Failed to run %s' % cmd) | 1083 | raise RunQemuError('Failed to run %s' % str(cmd)) |
1045 | 1084 | ||
1046 | self.nfs_running = True | 1085 | self.nfs_running = True |
1047 | 1086 | ||
1087 | def setup_cmd(self): | ||
1088 | cmd = self.get('QB_SETUP_CMD') | ||
1089 | if cmd != '': | ||
1090 | logger.info('Running setup command %s' % str(cmd)) | ||
1091 | if subprocess.call(cmd, shell=True) != 0: | ||
1092 | raise RunQemuError('Failed to run %s' % str(cmd)) | ||
1093 | |||
1048 | def setup_net_bridge(self): | 1094 | def setup_net_bridge(self): |
1049 | self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( | 1095 | 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'))) | 1096 | self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) |
1051 | 1097 | ||
1098 | def make_lock_dir(self, lockdir): | ||
1099 | if not os.path.exists(lockdir): | ||
1100 | # There might be a race issue when multi runqemu processess are | ||
1101 | # running at the same time. | ||
1102 | try: | ||
1103 | os.mkdir(lockdir) | ||
1104 | os.chmod(lockdir, 0o777) | ||
1105 | except FileExistsError: | ||
1106 | pass | ||
1107 | return | ||
1108 | |||
1052 | def setup_slirp(self): | 1109 | def setup_slirp(self): |
1053 | """Setup user networking""" | 1110 | """Setup user networking""" |
1054 | 1111 | ||
@@ -1058,7 +1115,7 @@ class BaseConfig(object): | |||
1058 | logger.info("Network configuration:%s", netconf) | 1115 | logger.info("Network configuration:%s", netconf) |
1059 | self.kernel_cmdline_script += netconf | 1116 | self.kernel_cmdline_script += netconf |
1060 | # Port mapping | 1117 | # Port mapping |
1061 | hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23" | 1118 | 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')) | 1119 | 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 | 1120 | qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default |
1064 | # Figure out the port | 1121 | # Figure out the port |
@@ -1067,14 +1124,7 @@ class BaseConfig(object): | |||
1067 | mac = 2 | 1124 | mac = 2 |
1068 | 1125 | ||
1069 | lockdir = "/tmp/qemu-port-locks" | 1126 | lockdir = "/tmp/qemu-port-locks" |
1070 | if not os.path.exists(lockdir): | 1127 | 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 | 1128 | ||
1079 | # Find a free port to avoid conflicts | 1129 | # Find a free port to avoid conflicts |
1080 | for p in ports[:]: | 1130 | for p in ports[:]: |
@@ -1114,20 +1164,17 @@ class BaseConfig(object): | |||
1114 | logger.error("ip: %s" % ip) | 1164 | logger.error("ip: %s" % ip) |
1115 | raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") | 1165 | raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") |
1116 | 1166 | ||
1117 | if not os.path.exists(lockdir): | 1167 | 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 | 1168 | ||
1126 | cmd = (ip, 'link') | 1169 | cmd = (ip, 'link') |
1127 | logger.debug('Running %s...' % str(cmd)) | 1170 | logger.debug('Running %s...' % str(cmd)) |
1128 | ip_link = subprocess.check_output(cmd).decode('utf-8') | 1171 | ip_link = subprocess.check_output(cmd).decode('utf-8') |
1129 | # Matches line like: 6: tap0: <foo> | 1172 | # Matches line like: 6: tap0: <foo> |
1130 | possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M) | 1173 | oe_tap_name = 'tap' |
1174 | if 'OE_TAP_NAME' in os.environ: | ||
1175 | oe_tap_name = os.environ['OE_TAP_NAME'] | ||
1176 | tap_re = '^[0-9]+: +(' + oe_tap_name + '[0-9]+): <.*' | ||
1177 | possibles = re.findall(tap_re, ip_link, re.M) | ||
1131 | tap = "" | 1178 | tap = "" |
1132 | for p in possibles: | 1179 | for p in possibles: |
1133 | lockfile = os.path.join(lockdir, p) | 1180 | lockfile = os.path.join(lockdir, p) |
@@ -1148,25 +1195,28 @@ class BaseConfig(object): | |||
1148 | raise RunQemuError("a new one with sudo.") | 1195 | raise RunQemuError("a new one with sudo.") |
1149 | 1196 | ||
1150 | gid = os.getgid() | 1197 | gid = os.getgid() |
1151 | uid = os.getuid() | ||
1152 | logger.info("Setting up tap interface under sudo") | 1198 | logger.info("Setting up tap interface under sudo") |
1153 | cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native) | 1199 | cmd = ('sudo', self.qemuifup, str(gid)) |
1154 | try: | 1200 | for _ in range(5): |
1155 | tap = subprocess.check_output(cmd).decode('utf-8').strip() | 1201 | try: |
1156 | except subprocess.CalledProcessError as e: | 1202 | 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)) | 1203 | except subprocess.CalledProcessError as e: |
1158 | sys.exit(1) | 1204 | 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) | 1205 | sys.exit(1) |
1160 | self.taplock = lockfile + '.lock' | 1206 | lockfile = os.path.join(lockdir, tap) |
1161 | self.acquire_taplock() | 1207 | self.taplock = lockfile + '.lock' |
1162 | self.cleantap = True | 1208 | if self.acquire_taplock(): |
1163 | logger.debug('Created tap: %s' % tap) | 1209 | self.cleantap = True |
1210 | logger.debug('Created tap: %s' % tap) | ||
1211 | break | ||
1212 | else: | ||
1213 | tap = None | ||
1164 | 1214 | ||
1165 | if not tap: | 1215 | if not tap: |
1166 | logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") | 1216 | logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") |
1167 | sys.exit(1) | 1217 | sys.exit(1) |
1168 | self.tap = tap | 1218 | self.tap = tap |
1169 | tapnum = int(tap[3:]) | 1219 | tapnum = int(tap[len(oe_tap_name):]) |
1170 | gateway = tapnum * 2 + 1 | 1220 | gateway = tapnum * 2 + 1 |
1171 | client = gateway + 1 | 1221 | client = gateway + 1 |
1172 | if self.fstype == 'nfs': | 1222 | if self.fstype == 'nfs': |
@@ -1174,6 +1224,7 @@ class BaseConfig(object): | |||
1174 | netconf = " " + self.cmdline_ip_tap | 1224 | netconf = " " + self.cmdline_ip_tap |
1175 | netconf = netconf.replace('@CLIENT@', str(client)) | 1225 | netconf = netconf.replace('@CLIENT@', str(client)) |
1176 | netconf = netconf.replace('@GATEWAY@', str(gateway)) | 1226 | netconf = netconf.replace('@GATEWAY@', str(gateway)) |
1227 | self.nfs_server = self.nfs_server.replace('@GATEWAY@', str(gateway)) | ||
1177 | logger.info("Network configuration:%s", netconf) | 1228 | logger.info("Network configuration:%s", netconf) |
1178 | self.kernel_cmdline_script += netconf | 1229 | self.kernel_cmdline_script += netconf |
1179 | mac = "%s%02x" % (self.mac_tap, client) | 1230 | mac = "%s%02x" % (self.mac_tap, client) |
@@ -1189,7 +1240,8 @@ class BaseConfig(object): | |||
1189 | self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) | 1240 | self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) |
1190 | 1241 | ||
1191 | def setup_network(self): | 1242 | def setup_network(self): |
1192 | if self.get('QB_NET') == 'none': | 1243 | if self.nonetwork or self.get('QB_NET') == 'none': |
1244 | self.set('NETWORK_CMD', '-nic none') | ||
1193 | return | 1245 | return |
1194 | if sys.stdin.isatty(): | 1246 | if sys.stdin.isatty(): |
1195 | self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() | 1247 | self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() |
@@ -1210,6 +1262,18 @@ class BaseConfig(object): | |||
1210 | self.fstype = self.fstype[4:] | 1262 | self.fstype = self.fstype[4:] |
1211 | rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' | 1263 | rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' |
1212 | 1264 | ||
1265 | tmpfsdir = os.environ.get("RUNQEMU_TMPFS_DIR", None) | ||
1266 | if self.snapshot and tmpfsdir: | ||
1267 | newrootfs = os.path.join(tmpfsdir, os.path.basename(self.rootfs)) + "." + str(os.getpid()) | ||
1268 | logger.info("Copying rootfs to %s" % newrootfs) | ||
1269 | copy_start = time.time() | ||
1270 | shutil.copyfile(self.rootfs, newrootfs) | ||
1271 | logger.info("Copy done in %s seconds" % (time.time() - copy_start)) | ||
1272 | self.rootfs = newrootfs | ||
1273 | # Don't need a second copy now! | ||
1274 | self.snapshot = False | ||
1275 | self.cleanup_files.append(newrootfs) | ||
1276 | |||
1213 | qb_rootfs_opt = self.get('QB_ROOTFS_OPT') | 1277 | qb_rootfs_opt = self.get('QB_ROOTFS_OPT') |
1214 | if qb_rootfs_opt: | 1278 | if qb_rootfs_opt: |
1215 | self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) | 1279 | self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) |
@@ -1237,6 +1301,10 @@ class BaseConfig(object): | |||
1237 | elif drive_type.startswith("/dev/hd"): | 1301 | elif drive_type.startswith("/dev/hd"): |
1238 | logger.info('Using ide drive') | 1302 | logger.info('Using ide drive') |
1239 | vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format) | 1303 | vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format) |
1304 | elif drive_type.startswith("/dev/mmcblk"): | ||
1305 | logger.info('Using sdcard drive') | ||
1306 | vm_drive = '-drive id=sdcard0,if=none,file=%s,format=%s -device sdhci-pci -device sd-card,drive=sdcard0' \ | ||
1307 | % (self.rootfs, rootfs_format) | ||
1240 | elif drive_type.startswith("/dev/vdb"): | 1308 | elif drive_type.startswith("/dev/vdb"): |
1241 | logger.info('Using block virtio drive'); | 1309 | logger.info('Using block virtio drive'); |
1242 | vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \ | 1310 | vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \ |
@@ -1254,7 +1322,13 @@ class BaseConfig(object): | |||
1254 | self.rootfs_options = vm_drive | 1322 | self.rootfs_options = vm_drive |
1255 | if not self.fstype in self.vmtypes: | 1323 | if not self.fstype in self.vmtypes: |
1256 | self.rootfs_options += ' -no-reboot' | 1324 | self.rootfs_options += ' -no-reboot' |
1257 | self.kernel_cmdline = 'root=%s rw' % (self.get('QB_KERNEL_ROOT')) | 1325 | |
1326 | # By default, ' rw' is appended to QB_KERNEL_ROOT unless either ro or rw is explicitly passed. | ||
1327 | qb_kernel_root = self.get('QB_KERNEL_ROOT') | ||
1328 | qb_kernel_root_l = qb_kernel_root.split() | ||
1329 | if not ('ro' in qb_kernel_root_l or 'rw' in qb_kernel_root_l): | ||
1330 | qb_kernel_root += ' rw' | ||
1331 | self.kernel_cmdline = 'root=%s' % qb_kernel_root | ||
1258 | 1332 | ||
1259 | if self.fstype == 'nfs': | 1333 | if self.fstype == 'nfs': |
1260 | self.rootfs_options = '' | 1334 | self.rootfs_options = '' |
@@ -1270,7 +1344,7 @@ class BaseConfig(object): | |||
1270 | """attempt to determine the appropriate qemu-system binary""" | 1344 | """attempt to determine the appropriate qemu-system binary""" |
1271 | mach = self.get('MACHINE') | 1345 | mach = self.get('MACHINE') |
1272 | if not mach: | 1346 | if not mach: |
1273 | search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' | 1347 | search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemuloongarch64|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' |
1274 | if self.rootfs: | 1348 | if self.rootfs: |
1275 | match = re.match(search, self.rootfs) | 1349 | match = re.match(search, self.rootfs) |
1276 | if match: | 1350 | if match: |
@@ -1293,6 +1367,8 @@ class BaseConfig(object): | |||
1293 | qbsys = 'x86_64' | 1367 | qbsys = 'x86_64' |
1294 | elif mach == 'qemuppc': | 1368 | elif mach == 'qemuppc': |
1295 | qbsys = 'ppc' | 1369 | qbsys = 'ppc' |
1370 | elif mach == 'qemuloongarch64': | ||
1371 | qbsys = 'loongarch64' | ||
1296 | elif mach == 'qemumips': | 1372 | elif mach == 'qemumips': |
1297 | qbsys = 'mips' | 1373 | qbsys = 'mips' |
1298 | elif mach == 'qemumips64': | 1374 | elif mach == 'qemumips64': |
@@ -1321,7 +1397,127 @@ class BaseConfig(object): | |||
1321 | raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") | 1397 | raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") |
1322 | self.qemu_system = qemu_system | 1398 | self.qemu_system = qemu_system |
1323 | 1399 | ||
1324 | def setup_final(self): | 1400 | def check_render_nodes(self): |
1401 | 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.""" | ||
1402 | try: | ||
1403 | content = os.listdir("/dev/dri") | ||
1404 | nodes = [i for i in content if i.startswith('renderD')] | ||
1405 | if len(nodes) == 0: | ||
1406 | raise RunQemuError("No render nodes found in /dev/dri/: %s. %s" %(content, render_hint)) | ||
1407 | for n in nodes: | ||
1408 | try: | ||
1409 | with open(os.path.join("/dev/dri", n), "w") as f: | ||
1410 | f.close() | ||
1411 | break | ||
1412 | except IOError: | ||
1413 | pass | ||
1414 | else: | ||
1415 | 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)) | ||
1416 | except FileNotFoundError: | ||
1417 | raise RunQemuError("/dev/dri directory does not exist; no render nodes available on this machine. %s" %(render_hint)) | ||
1418 | |||
1419 | def setup_guest_agent(self): | ||
1420 | if self.guest_agent == True: | ||
1421 | self.qemu_opt += ' -chardev socket,path=' + self.guest_agent_sockpath + ',server,nowait,id=qga0 ' | ||
1422 | self.qemu_opt += ' -device virtio-serial ' | ||
1423 | self.qemu_opt += ' -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 ' | ||
1424 | |||
1425 | def setup_qmp(self): | ||
1426 | if self.qmp: | ||
1427 | self.qemu_opt += " -qmp %s,server,nowait" % self.qmp | ||
1428 | |||
1429 | def setup_vga(self): | ||
1430 | if self.nographic == True: | ||
1431 | if self.sdl == True: | ||
1432 | raise RunQemuError('Option nographic makes no sense alongside the sdl option.') | ||
1433 | if self.gtk == True: | ||
1434 | raise RunQemuError('Option nographic makes no sense alongside the gtk option.') | ||
1435 | self.qemu_opt += ' -nographic' | ||
1436 | |||
1437 | if self.novga == True: | ||
1438 | self.qemu_opt += ' -vga none' | ||
1439 | return | ||
1440 | |||
1441 | if (self.gl_es == True or self.gl == True) and (self.sdl == False and self.gtk == False): | ||
1442 | raise RunQemuError('Option gl/gl-es needs gtk or sdl option.') | ||
1443 | |||
1444 | # If we have no display option, we autodetect based upon what qemu supports. We | ||
1445 | # need our font setup and show-cusor below so we need to see what qemu --help says | ||
1446 | # is supported so we can pass our correct config in. | ||
1447 | if not self.nographic and not self.sdl and not self.gtk and not self.publicvnc and not self.egl_headless == True: | ||
1448 | output = subprocess.check_output([self.qemu_bin, "--help"], universal_newlines=True, env=self.qemu_environ) | ||
1449 | if "-display gtk" in output: | ||
1450 | self.gtk = True | ||
1451 | elif "-display sdl" in output: | ||
1452 | self.sdl = True | ||
1453 | else: | ||
1454 | self.qemu_opt += ' -display none' | ||
1455 | |||
1456 | if self.sdl == True or self.gtk == True or self.egl_headless == True: | ||
1457 | |||
1458 | if self.qemu_system.endswith(('i386', 'x86_64')): | ||
1459 | if self.gl or self.gl_es or self.egl_headless: | ||
1460 | self.qemu_opt += ' -device virtio-vga-gl ' | ||
1461 | else: | ||
1462 | self.qemu_opt += ' -device virtio-vga ' | ||
1463 | |||
1464 | self.qemu_opt += ' -display ' | ||
1465 | if self.egl_headless == True: | ||
1466 | self.check_render_nodes() | ||
1467 | self.set_mesa_paths() | ||
1468 | self.qemu_opt += 'egl-headless,' | ||
1469 | else: | ||
1470 | if self.sdl == True: | ||
1471 | self.qemu_opt += 'sdl,' | ||
1472 | elif self.gtk == True: | ||
1473 | self.qemu_environ['FONTCONFIG_PATH'] = '/etc/fonts' | ||
1474 | self.qemu_opt += 'gtk,' | ||
1475 | |||
1476 | if self.gl == True: | ||
1477 | self.set_mesa_paths() | ||
1478 | self.qemu_opt += 'gl=on,' | ||
1479 | elif self.gl_es == True: | ||
1480 | self.set_mesa_paths() | ||
1481 | self.qemu_opt += 'gl=es,' | ||
1482 | self.qemu_opt += 'show-cursor=on' | ||
1483 | |||
1484 | self.qemu_opt += ' %s' %self.get('QB_GRAPHICS') | ||
1485 | |||
1486 | def setup_serial(self): | ||
1487 | # Setup correct kernel command line for serial | ||
1488 | if self.get('SERIAL_CONSOLES') and (self.serialstdio == True or self.serialconsole == True or self.nographic == True or self.tcpserial_portnum): | ||
1489 | for entry in self.get('SERIAL_CONSOLES').split(' '): | ||
1490 | self.kernel_cmdline_script += ' console=%s' %entry.split(';')[1] | ||
1491 | |||
1492 | # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES). | ||
1493 | # If no serial or serialtcp options were specified, only ttyS0 is created | ||
1494 | # and sysvinit shows an error trying to enable ttyS1: | ||
1495 | # INIT: Id "S1" respawning too fast: disabled for 5 minutes | ||
1496 | serial_num = len(re.findall("(^| )-serial ", self.qemu_opt)) | ||
1497 | |||
1498 | # Assume if the user passed serial options, they know what they want | ||
1499 | # and pad to two devices | ||
1500 | if serial_num == 1: | ||
1501 | self.qemu_opt += " -serial null" | ||
1502 | elif serial_num >= 2: | ||
1503 | return | ||
1504 | |||
1505 | if self.serialstdio == True or self.nographic == True: | ||
1506 | self.qemu_opt += " -serial mon:stdio" | ||
1507 | else: | ||
1508 | self.qemu_opt += " -serial mon:vc" | ||
1509 | if self.serialconsole: | ||
1510 | if sys.stdin.isatty(): | ||
1511 | subprocess.check_call(("stty", "intr", "^]")) | ||
1512 | logger.info("Interrupt character is '^]'") | ||
1513 | |||
1514 | self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT") | ||
1515 | |||
1516 | serial_num = len(re.findall("(^| )-serial ", self.qemu_opt)) | ||
1517 | if serial_num < 2: | ||
1518 | self.qemu_opt += " -serial null" | ||
1519 | |||
1520 | def find_qemu(self): | ||
1325 | qemu_bin = os.path.join(self.bindir_native, self.qemu_system) | 1521 | qemu_bin = os.path.join(self.bindir_native, self.qemu_system) |
1326 | 1522 | ||
1327 | # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't | 1523 | # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't |
@@ -1340,11 +1536,18 @@ class BaseConfig(object): | |||
1340 | 1536 | ||
1341 | if not os.access(qemu_bin, os.X_OK): | 1537 | if not os.access(qemu_bin, os.X_OK): |
1342 | raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) | 1538 | raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) |
1539 | self.qemu_bin = qemu_bin | ||
1343 | 1540 | ||
1344 | 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')) | 1541 | def setup_final(self): |
1542 | |||
1543 | self.find_qemu() | ||
1544 | |||
1545 | 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'))) | ||
1345 | 1546 | ||
1346 | for ovmf in self.ovmf_bios: | 1547 | for ovmf in self.ovmf_bios: |
1347 | format = ovmf.rsplit('.', 1)[-1] | 1548 | format = ovmf.rsplit('.', 1)[-1] |
1549 | if format == "bin": | ||
1550 | format = "raw" | ||
1348 | self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) | 1551 | self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) |
1349 | 1552 | ||
1350 | self.qemu_opt += ' ' + self.qemu_opt_script | 1553 | self.qemu_opt += ' ' + self.qemu_opt_script |
@@ -1363,61 +1566,44 @@ class BaseConfig(object): | |||
1363 | if self.snapshot: | 1566 | if self.snapshot: |
1364 | self.qemu_opt += " -snapshot" | 1567 | self.qemu_opt += " -snapshot" |
1365 | 1568 | ||
1366 | if self.serialconsole: | 1569 | self.setup_guest_agent() |
1367 | if sys.stdin.isatty(): | 1570 | self.setup_qmp() |
1368 | subprocess.check_call(("stty", "intr", "^]")) | 1571 | self.setup_serial() |
1369 | logger.info("Interrupt character is '^]'") | 1572 | self.setup_vga() |
1370 | |||
1371 | first_serial = "" | ||
1372 | if not re.search("-nographic", self.qemu_opt): | ||
1373 | first_serial = "-serial mon:vc" | ||
1374 | # We always want a ttyS1. Since qemu by default adds a serial | ||
1375 | # port when nodefaults is not specified, it seems that all that | ||
1376 | # would be needed is to make sure a "-serial" is there. However, | ||
1377 | # it appears that when "-serial" is specified, it ignores the | ||
1378 | # default serial port that is normally added. So here we make | ||
1379 | # sure to add two -serial if there are none. And only one if | ||
1380 | # there is one -serial already. | ||
1381 | serial_num = len(re.findall("-serial", self.qemu_opt)) | ||
1382 | if serial_num == 0: | ||
1383 | self.qemu_opt += " %s %s" % (first_serial, self.get("QB_SERIAL_OPT")) | ||
1384 | elif serial_num == 1: | ||
1385 | self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT") | ||
1386 | |||
1387 | # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES), | ||
1388 | # if not serial or serialtcp options was specified only ttyS0 is created | ||
1389 | # and sysvinit shows an error trying to enable ttyS1: | ||
1390 | # INIT: Id "S1" respawning too fast: disabled for 5 minutes | ||
1391 | serial_num = len(re.findall("-serial", self.qemu_opt)) | ||
1392 | if serial_num == 0: | ||
1393 | if re.search("-nographic", self.qemu_opt) or self.serialstdio: | ||
1394 | self.qemu_opt += " -serial mon:stdio -serial null" | ||
1395 | else: | ||
1396 | self.qemu_opt += " -serial mon:vc -serial null" | ||
1397 | 1573 | ||
1398 | def start_qemu(self): | 1574 | def start_qemu(self): |
1399 | import shlex | 1575 | import shlex |
1400 | if self.kernel: | 1576 | if self.kernel: |
1401 | kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline, | 1577 | kernel_opts = "-kernel %s" % (self.kernel) |
1578 | if self.get('QB_KERNEL_CMDLINE') == "none": | ||
1579 | if self.bootparams: | ||
1580 | kernel_opts += " -append '%s'" % (self.bootparams) | ||
1581 | else: | ||
1582 | kernel_opts += " -append '%s %s %s %s'" % (self.kernel_cmdline, | ||
1402 | self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), | 1583 | self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), |
1403 | self.bootparams) | 1584 | self.bootparams) |
1404 | if self.bios: | ||
1405 | kernel_opts += " -bios %s" % self.bios | ||
1406 | if self.dtb: | 1585 | if self.dtb: |
1407 | kernel_opts += " -dtb %s" % self.dtb | 1586 | kernel_opts += " -dtb %s" % self.dtb |
1408 | else: | 1587 | else: |
1409 | kernel_opts = "" | 1588 | kernel_opts = "" |
1589 | |||
1590 | if self.bios: | ||
1591 | self.qemu_opt += " -bios %s" % self.bios | ||
1592 | |||
1410 | cmd = "%s %s" % (self.qemu_opt, kernel_opts) | 1593 | cmd = "%s %s" % (self.qemu_opt, kernel_opts) |
1411 | cmds = shlex.split(cmd) | 1594 | cmds = shlex.split(cmd) |
1412 | logger.info('Running %s\n' % cmd) | 1595 | logger.info('Running %s\n' % cmd) |
1596 | with open('/proc/uptime', 'r') as f: | ||
1597 | uptime_seconds = f.readline().split()[0] | ||
1598 | logger.info('Host uptime: %s\n' % uptime_seconds) | ||
1413 | pass_fds = [] | 1599 | pass_fds = [] |
1414 | if self.taplock_descriptor: | 1600 | if self.taplock_descriptor: |
1415 | pass_fds = [self.taplock_descriptor.fileno()] | 1601 | pass_fds = [self.taplock_descriptor.fileno()] |
1416 | if len(self.portlocks): | 1602 | if len(self.portlocks): |
1417 | for descriptor in self.portlocks.values(): | 1603 | for descriptor in self.portlocks.values(): |
1418 | pass_fds.append(descriptor.fileno()) | 1604 | pass_fds.append(descriptor.fileno()) |
1419 | process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds) | 1605 | process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ) |
1420 | self.qemupid = process.pid | 1606 | self.qemuprocess = process |
1421 | retcode = process.wait() | 1607 | retcode = process.wait() |
1422 | if retcode: | 1608 | if retcode: |
1423 | if retcode == -signal.SIGTERM: | 1609 | if retcode == -signal.SIGTERM: |
@@ -1425,6 +1611,13 @@ class BaseConfig(object): | |||
1425 | else: | 1611 | else: |
1426 | logger.error("Failed to run qemu: %s", process.stderr.read().decode()) | 1612 | logger.error("Failed to run qemu: %s", process.stderr.read().decode()) |
1427 | 1613 | ||
1614 | def cleanup_cmd(self): | ||
1615 | cmd = self.get('QB_CLEANUP_CMD') | ||
1616 | if cmd != '': | ||
1617 | logger.info('Running cleanup command %s' % str(cmd)) | ||
1618 | if subprocess.call(cmd, shell=True) != 0: | ||
1619 | raise RunQemuError('Failed to run %s' % str(cmd)) | ||
1620 | |||
1428 | def cleanup(self): | 1621 | def cleanup(self): |
1429 | if self.cleaned: | 1622 | if self.cleaned: |
1430 | return | 1623 | return |
@@ -1433,30 +1626,48 @@ class BaseConfig(object): | |||
1433 | signal.signal(signal.SIGTERM, signal.SIG_IGN) | 1626 | signal.signal(signal.SIGTERM, signal.SIG_IGN) |
1434 | 1627 | ||
1435 | logger.info("Cleaning up") | 1628 | logger.info("Cleaning up") |
1629 | |||
1630 | if self.qemuprocess: | ||
1631 | try: | ||
1632 | # give it some time to shut down, ignore return values and output | ||
1633 | self.qemuprocess.send_signal(signal.SIGTERM) | ||
1634 | self.qemuprocess.communicate(timeout=5) | ||
1635 | except subprocess.TimeoutExpired: | ||
1636 | self.qemuprocess.kill() | ||
1637 | |||
1638 | with open('/proc/uptime', 'r') as f: | ||
1639 | uptime_seconds = f.readline().split()[0] | ||
1640 | logger.info('Host uptime: %s\n' % uptime_seconds) | ||
1436 | if self.cleantap: | 1641 | if self.cleantap: |
1437 | cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native) | 1642 | cmd = ('sudo', self.qemuifdown, self.tap) |
1438 | logger.debug('Running %s' % str(cmd)) | 1643 | logger.debug('Running %s' % str(cmd)) |
1439 | subprocess.check_call(cmd) | 1644 | subprocess.check_call(cmd) |
1440 | self.release_taplock() | 1645 | self.release_taplock() |
1441 | self.release_portlock() | ||
1442 | 1646 | ||
1443 | if self.nfs_running: | 1647 | if self.nfs_running: |
1444 | logger.info("Shutting down the userspace NFS server...") | 1648 | logger.info("Shutting down the userspace NFS server...") |
1445 | cmd = ("runqemu-export-rootfs", "stop", self.rootfs) | 1649 | cmd = ("runqemu-export-rootfs", "stop", self.rootfs) |
1446 | logger.debug('Running %s' % str(cmd)) | 1650 | logger.debug('Running %s' % str(cmd)) |
1447 | subprocess.check_call(cmd) | 1651 | subprocess.check_call(cmd) |
1652 | self.release_portlock() | ||
1448 | 1653 | ||
1449 | if self.saved_stty: | 1654 | if self.saved_stty: |
1450 | subprocess.check_call(("stty", self.saved_stty)) | 1655 | subprocess.check_call(("stty", self.saved_stty)) |
1451 | 1656 | ||
1452 | if self.clean_nfs_dir: | 1657 | if self.cleanup_files: |
1453 | logger.info('Removing %s' % self.rootfs) | 1658 | for ent in self.cleanup_files: |
1454 | shutil.rmtree(self.rootfs) | 1659 | logger.info('Removing %s' % ent) |
1455 | shutil.rmtree('%s.pseudo_state' % self.rootfs) | 1660 | if os.path.isfile(ent): |
1661 | os.remove(ent) | ||
1662 | else: | ||
1663 | shutil.rmtree(ent) | ||
1664 | |||
1665 | # Deliberately ignore the return code of 'tput smam'. | ||
1666 | subprocess.call(["tput", "smam"]) | ||
1456 | 1667 | ||
1457 | self.cleaned = True | 1668 | self.cleaned = True |
1458 | 1669 | ||
1459 | def run_bitbake_env(self, mach=None): | 1670 | def run_bitbake_env(self, mach=None, target=''): |
1460 | bitbake = shutil.which('bitbake') | 1671 | bitbake = shutil.which('bitbake') |
1461 | if not bitbake: | 1672 | if not bitbake: |
1462 | return | 1673 | return |
@@ -1468,23 +1679,37 @@ class BaseConfig(object): | |||
1468 | if multiconfig: | 1679 | if multiconfig: |
1469 | multiconfig = "mc:%s" % multiconfig | 1680 | multiconfig = "mc:%s" % multiconfig |
1470 | 1681 | ||
1682 | if self.rootfs and not target: | ||
1683 | target = self.rootfs | ||
1684 | |||
1471 | if mach: | 1685 | if mach: |
1472 | cmd = 'MACHINE=%s bitbake -e %s' % (mach, multiconfig) | 1686 | cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target) |
1473 | else: | 1687 | else: |
1474 | cmd = 'bitbake -e %s' % multiconfig | 1688 | cmd = 'bitbake -e %s %s' % (multiconfig, target) |
1475 | 1689 | ||
1476 | logger.info('Running %s...' % cmd) | 1690 | logger.info('Running %s...' % cmd) |
1477 | return subprocess.check_output(cmd, shell=True).decode('utf-8') | 1691 | try: |
1692 | return subprocess.check_output(cmd, shell=True).decode('utf-8') | ||
1693 | except subprocess.CalledProcessError as err: | ||
1694 | 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'))) | ||
1695 | # need something with IMAGE_NAME_SUFFIX/IMAGE_LINK_NAME defined (kernel also inherits image-artifact-names.bbclass) | ||
1696 | target = 'virtual/kernel' | ||
1697 | if mach: | ||
1698 | cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target) | ||
1699 | else: | ||
1700 | cmd = 'bitbake -e %s %s' % (multiconfig, target) | ||
1701 | try: | ||
1702 | return subprocess.check_output(cmd, shell=True).decode('utf-8') | ||
1703 | except subprocess.CalledProcessError as err: | ||
1704 | logger.warning("Couldn't run '%s' to gather environment information, giving up with 'bitbake -e':\n%s" % (cmd, err.output.decode('utf-8'))) | ||
1705 | return '' | ||
1706 | |||
1478 | 1707 | ||
1479 | def load_bitbake_env(self, mach=None): | 1708 | def load_bitbake_env(self, mach=None, target=None): |
1480 | if self.bitbake_e: | 1709 | if self.bitbake_e: |
1481 | return | 1710 | return |
1482 | 1711 | ||
1483 | try: | 1712 | self.bitbake_e = self.run_bitbake_env(mach=mach, target=target) |
1484 | self.bitbake_e = self.run_bitbake_env(mach=mach) | ||
1485 | except subprocess.CalledProcessError as err: | ||
1486 | self.bitbake_e = '' | ||
1487 | logger.warning("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8')) | ||
1488 | 1713 | ||
1489 | def validate_combos(self): | 1714 | def validate_combos(self): |
1490 | if (self.fstype in self.vmtypes) and self.kernel: | 1715 | if (self.fstype in self.vmtypes) and self.kernel: |
@@ -1514,7 +1739,7 @@ class BaseConfig(object): | |||
1514 | return result | 1739 | return result |
1515 | raise RunQemuError("Native sysroot directory %s doesn't exist" % result) | 1740 | raise RunQemuError("Native sysroot directory %s doesn't exist" % result) |
1516 | else: | 1741 | else: |
1517 | raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd) | 1742 | raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % str(cmd)) |
1518 | 1743 | ||
1519 | 1744 | ||
1520 | def main(): | 1745 | def main(): |
@@ -1530,11 +1755,8 @@ def main(): | |||
1530 | subprocess.check_call([renice, str(os.getpid())]) | 1755 | subprocess.check_call([renice, str(os.getpid())]) |
1531 | 1756 | ||
1532 | def sigterm_handler(signum, frame): | 1757 | def sigterm_handler(signum, frame): |
1533 | logger.info("SIGTERM received") | 1758 | logger.info("Received signal: %s" % (signum)) |
1534 | os.kill(config.qemupid, signal.SIGTERM) | ||
1535 | config.cleanup() | 1759 | config.cleanup() |
1536 | # Deliberately ignore the return code of 'tput smam'. | ||
1537 | subprocess.call(["tput", "smam"]) | ||
1538 | signal.signal(signal.SIGTERM, sigterm_handler) | 1760 | signal.signal(signal.SIGTERM, sigterm_handler) |
1539 | 1761 | ||
1540 | config.check_args() | 1762 | config.check_args() |
@@ -1546,6 +1768,7 @@ def main(): | |||
1546 | config.setup_network() | 1768 | config.setup_network() |
1547 | config.setup_rootfs() | 1769 | config.setup_rootfs() |
1548 | config.setup_final() | 1770 | config.setup_final() |
1771 | config.setup_cmd() | ||
1549 | config.start_qemu() | 1772 | config.start_qemu() |
1550 | except RunQemuError as err: | 1773 | except RunQemuError as err: |
1551 | logger.error(err) | 1774 | logger.error(err) |
@@ -1555,9 +1778,8 @@ def main(): | |||
1555 | traceback.print_exc() | 1778 | traceback.print_exc() |
1556 | return 1 | 1779 | return 1 |
1557 | finally: | 1780 | finally: |
1781 | config.cleanup_cmd() | ||
1558 | config.cleanup() | 1782 | config.cleanup() |
1559 | # Deliberately ignore the return code of 'tput smam'. | ||
1560 | subprocess.call(["tput", "smam"]) | ||
1561 | 1783 | ||
1562 | if __name__ == "__main__": | 1784 | if __name__ == "__main__": |
1563 | sys.exit(main()) | 1785 | sys.exit(main()) |