diff options
author | Randy Witt <randy.e.witt@linux.intel.com> | 2015-08-20 13:01:25 -0700 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2015-08-24 23:47:07 +0100 |
commit | fd164dcc31aa6c60e9834036a7cfcea5411daa3d (patch) | |
tree | 5f1b1395e7edbd9ff689fcf48f3baea5a2599c6c | |
parent | 8d05d8a0c46564784463d2a39c68c467714f306d (diff) | |
download | poky-fd164dcc31aa6c60e9834036a7cfcea5411daa3d.tar.gz |
qemurunner: Use two serial ports and log console with a thread
qemu can freeze and stop responding if the socket buffer connected to a tcp
serial connection fills up. This happens of course when the reader of
the serial data doesn't actually read it.
This happened in the qemurunner code, because after checking for the
"login:" sentinel, data was never again read from the serial connection.
This patch solves the potential freeze by adding a thread to continuously
read the data from the console and log it. So it also will give a full log
of the console, rather than just up to the login prompt.
To simplify this patch, another serial port was also added to use for the
sole purpose of watching for the sentinel as well as being the interactive
serial port. This will also prevent the possibility of lots of debug
data on the console preventing the sentinel value from being seen due to
interleaved text.
(From OE-Core rev: 2da3fee6b6d9f4dd4c4cb529f4ba393c20aa0f13)
Signed-off-by: Randy Witt <randy.e.witt@linux.intel.com>
Signed-off-by: Ross Burton <ross.burton@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | meta/conf/machine/qemuarm.conf | 2 | ||||
-rw-r--r-- | meta/conf/machine/qemuarm64.conf | 2 | ||||
-rw-r--r-- | meta/conf/machine/qemumips.conf | 2 | ||||
-rw-r--r-- | meta/conf/machine/qemumips64.conf | 2 | ||||
-rw-r--r-- | meta/conf/machine/qemuppc.conf | 2 | ||||
-rw-r--r-- | meta/conf/machine/qemux86-64.conf | 2 | ||||
-rw-r--r-- | meta/conf/machine/qemux86.conf | 2 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 128 |
8 files changed, 133 insertions, 9 deletions
diff --git a/meta/conf/machine/qemuarm.conf b/meta/conf/machine/qemuarm.conf index d07084bcf3..cdad03fc4d 100644 --- a/meta/conf/machine/qemuarm.conf +++ b/meta/conf/machine/qemuarm.conf | |||
@@ -8,5 +8,5 @@ require conf/machine/include/tune-arm926ejs.inc | |||
8 | 8 | ||
9 | KERNEL_IMAGETYPE = "zImage" | 9 | KERNEL_IMAGETYPE = "zImage" |
10 | 10 | ||
11 | SERIAL_CONSOLE = "115200 ttyAMA0" | 11 | SERIAL_CONSOLES = "115200;ttyAMA0 115200;ttyAMA1" |
12 | 12 | ||
diff --git a/meta/conf/machine/qemuarm64.conf b/meta/conf/machine/qemuarm64.conf index 20bcfbac99..7bbdad74a3 100644 --- a/meta/conf/machine/qemuarm64.conf +++ b/meta/conf/machine/qemuarm64.conf | |||
@@ -9,4 +9,4 @@ MACHINE_FEATURES = "" | |||
9 | 9 | ||
10 | KERNEL_IMAGETYPE = "Image" | 10 | KERNEL_IMAGETYPE = "Image" |
11 | 11 | ||
12 | SERIAL_CONSOLE = "38400 ttyAMA0" | 12 | SERIAL_CONSOLES = "38400;ttyAMA0 38400;ttyAMA1" |
diff --git a/meta/conf/machine/qemumips.conf b/meta/conf/machine/qemumips.conf index d9d2421616..fbf813740b 100644 --- a/meta/conf/machine/qemumips.conf +++ b/meta/conf/machine/qemumips.conf | |||
@@ -8,6 +8,6 @@ require conf/machine/include/tune-mips32r2.inc | |||
8 | KERNEL_IMAGETYPE = "vmlinux" | 8 | KERNEL_IMAGETYPE = "vmlinux" |
9 | KERNEL_ALT_IMAGETYPE = "vmlinux.bin" | 9 | KERNEL_ALT_IMAGETYPE = "vmlinux.bin" |
10 | 10 | ||
11 | SERIAL_CONSOLE = "115200 ttyS0" | 11 | SERIAL_CONSOLES = "115200;ttyS0 115200;ttyS1" |
12 | 12 | ||
13 | MACHINE_EXTRA_RRECOMMENDS = " kernel-modules" | 13 | MACHINE_EXTRA_RRECOMMENDS = " kernel-modules" |
diff --git a/meta/conf/machine/qemumips64.conf b/meta/conf/machine/qemumips64.conf index b2c7998a66..8c3f1fe283 100644 --- a/meta/conf/machine/qemumips64.conf +++ b/meta/conf/machine/qemumips64.conf | |||
@@ -8,6 +8,6 @@ require conf/machine/include/tune-mips64.inc | |||
8 | KERNEL_IMAGETYPE = "vmlinux" | 8 | KERNEL_IMAGETYPE = "vmlinux" |
9 | KERNEL_ALT_IMAGETYPE = "vmlinux.bin" | 9 | KERNEL_ALT_IMAGETYPE = "vmlinux.bin" |
10 | 10 | ||
11 | SERIAL_CONSOLE = "115200 ttyS0" | 11 | SERIAL_CONSOLES = "115200;ttyS0 115200;ttyS1" |
12 | 12 | ||
13 | MACHINE_EXTRA_RRECOMMENDS = " kernel-modules" | 13 | MACHINE_EXTRA_RRECOMMENDS = " kernel-modules" |
diff --git a/meta/conf/machine/qemuppc.conf b/meta/conf/machine/qemuppc.conf index 93145a1e2f..85cbbf798d 100644 --- a/meta/conf/machine/qemuppc.conf +++ b/meta/conf/machine/qemuppc.conf | |||
@@ -7,5 +7,5 @@ require conf/machine/include/tune-ppc7400.inc | |||
7 | 7 | ||
8 | KERNEL_IMAGETYPE = "vmlinux" | 8 | KERNEL_IMAGETYPE = "vmlinux" |
9 | 9 | ||
10 | SERIAL_CONSOLE = "115200 ttyS0" | 10 | SERIAL_CONSOLES = "115200;ttyS0 115200;ttyS1" |
11 | 11 | ||
diff --git a/meta/conf/machine/qemux86-64.conf b/meta/conf/machine/qemux86-64.conf index 837f9f4ab0..a4fd43ce1a 100644 --- a/meta/conf/machine/qemux86-64.conf +++ b/meta/conf/machine/qemux86-64.conf | |||
@@ -13,7 +13,7 @@ require conf/machine/include/tune-core2.inc | |||
13 | 13 | ||
14 | KERNEL_IMAGETYPE = "bzImage" | 14 | KERNEL_IMAGETYPE = "bzImage" |
15 | 15 | ||
16 | SERIAL_CONSOLE = "115200 ttyS0" | 16 | SERIAL_CONSOLES = "115200;ttyS0 115200;ttyS1" |
17 | 17 | ||
18 | XSERVER = "xserver-xorg \ | 18 | XSERVER = "xserver-xorg \ |
19 | ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'mesa-driver-swrast', '', d)} \ | 19 | ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'mesa-driver-swrast', '', d)} \ |
diff --git a/meta/conf/machine/qemux86.conf b/meta/conf/machine/qemux86.conf index 3562276932..96cea66b49 100644 --- a/meta/conf/machine/qemux86.conf +++ b/meta/conf/machine/qemux86.conf | |||
@@ -12,7 +12,7 @@ require conf/machine/include/tune-i586.inc | |||
12 | 12 | ||
13 | KERNEL_IMAGETYPE = "bzImage" | 13 | KERNEL_IMAGETYPE = "bzImage" |
14 | 14 | ||
15 | SERIAL_CONSOLE = "115200 ttyS0" | 15 | SERIAL_CONSOLES = "115200;ttyS0 115200;ttyS1" |
16 | 16 | ||
17 | XSERVER = "xserver-xorg \ | 17 | XSERVER = "xserver-xorg \ |
18 | ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'mesa-driver-swrast', '', d)} \ | 18 | ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'mesa-driver-swrast', '', d)} \ |
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py index c8d689900d..e976fd0819 100644 --- a/meta/lib/oeqa/utils/qemurunner.py +++ b/meta/lib/oeqa/utils/qemurunner.py | |||
@@ -13,6 +13,7 @@ import re | |||
13 | import socket | 13 | import socket |
14 | import select | 14 | import select |
15 | import errno | 15 | import errno |
16 | import threading | ||
16 | 17 | ||
17 | import logging | 18 | import logging |
18 | logger = logging.getLogger("BitBake.QemuRunner") | 19 | logger = logging.getLogger("BitBake.QemuRunner") |
@@ -38,6 +39,7 @@ class QemuRunner: | |||
38 | self.logfile = logfile | 39 | self.logfile = logfile |
39 | self.boottime = boottime | 40 | self.boottime = boottime |
40 | self.logged = False | 41 | self.logged = False |
42 | self.thread = None | ||
41 | 43 | ||
42 | self.runqemutime = 60 | 44 | self.runqemutime = 60 |
43 | 45 | ||
@@ -81,6 +83,7 @@ class QemuRunner: | |||
81 | os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image | 83 | os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image |
82 | 84 | ||
83 | try: | 85 | try: |
86 | threadsock, threadport = self.create_socket() | ||
84 | self.server_socket, self.serverport = self.create_socket() | 87 | self.server_socket, self.serverport = self.create_socket() |
85 | except socket.error, msg: | 88 | except socket.error, msg: |
86 | logger.error("Failed to create listening socket: %s" % msg[1]) | 89 | logger.error("Failed to create listening socket: %s" % msg[1]) |
@@ -89,7 +92,7 @@ class QemuRunner: | |||
89 | # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact | 92 | # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact |
90 | # badly with screensavers. | 93 | # badly with screensavers. |
91 | os.environ["QEMU_DONT_GRAB"] = "1" | 94 | os.environ["QEMU_DONT_GRAB"] = "1" |
92 | self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial tcp:127.0.0.1:%s"' % self.serverport | 95 | self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial tcp:127.0.0.1:{} -serial tcp:127.0.0.1:{}"'.format(threadport, self.serverport) |
93 | if qemuparams: | 96 | if qemuparams: |
94 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' | 97 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' |
95 | 98 | ||
@@ -138,6 +141,18 @@ class QemuRunner: | |||
138 | return False | 141 | return False |
139 | logger.info("Target IP: %s" % self.ip) | 142 | logger.info("Target IP: %s" % self.ip) |
140 | logger.info("Server IP: %s" % self.server_ip) | 143 | logger.info("Server IP: %s" % self.server_ip) |
144 | |||
145 | logger.info("Starting logging thread") | ||
146 | self.thread = LoggingThread(self.log, threadsock, logger) | ||
147 | self.thread.start() | ||
148 | if not self.thread.connection_established.wait(self.boottime): | ||
149 | logger.error("Didn't receive a console connection from qemu. " | ||
150 | "Here is the qemu command line used:\n%s\nand " | ||
151 | "output from runqemu:\n%s" % (cmdline, | ||
152 | getOutput(output))) | ||
153 | self.stop_thread() | ||
154 | return False | ||
155 | |||
141 | logger.info("Waiting at most %d seconds for login banner" % self.boottime) | 156 | logger.info("Waiting at most %d seconds for login banner" % self.boottime) |
142 | endtime = time.time() + self.boottime | 157 | endtime = time.time() + self.boottime |
143 | socklist = [self.server_socket] | 158 | socklist = [self.server_socket] |
@@ -157,7 +172,6 @@ class QemuRunner: | |||
157 | else: | 172 | else: |
158 | data = sock.recv(1024) | 173 | data = sock.recv(1024) |
159 | if data: | 174 | if data: |
160 | self.log(data) | ||
161 | bootlog += data | 175 | bootlog += data |
162 | if re.search(".* login:", bootlog): | 176 | if re.search(".* login:", bootlog): |
163 | self.server_socket = qemusock | 177 | self.server_socket = qemusock |
@@ -214,6 +228,12 @@ class QemuRunner: | |||
214 | self.server_socket = None | 228 | self.server_socket = None |
215 | self.qemupid = None | 229 | self.qemupid = None |
216 | self.ip = None | 230 | self.ip = None |
231 | self.stop_thread() | ||
232 | |||
233 | def stop_thread(self): | ||
234 | if self.thread and self.thread.is_alive(): | ||
235 | self.thread.stop() | ||
236 | self.thread.join() | ||
217 | 237 | ||
218 | def restart(self, qemuparams = None): | 238 | def restart(self, qemuparams = None): |
219 | logger.info("Restarting qemu process") | 239 | logger.info("Restarting qemu process") |
@@ -312,3 +332,107 @@ class QemuRunner: | |||
312 | if (status_cmd == "0"): | 332 | if (status_cmd == "0"): |
313 | status = 1 | 333 | status = 1 |
314 | return (status, str(data)) | 334 | return (status, str(data)) |
335 | |||
336 | # This class is for reading data from a socket and passing it to logfunc | ||
337 | # to be processed. It's completely event driven and has a straightforward | ||
338 | # event loop. The mechanism for stopping the thread is a simple pipe which | ||
339 | # will wake up the poll and allow for tearing everything down. | ||
340 | class LoggingThread(threading.Thread): | ||
341 | def __init__(self, logfunc, sock, logger): | ||
342 | self.connection_established = threading.Event() | ||
343 | self.serversock = sock | ||
344 | self.logfunc = logfunc | ||
345 | self.logger = logger | ||
346 | self.readsock = None | ||
347 | self.running = False | ||
348 | |||
349 | self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL | ||
350 | self.readevents = select.POLLIN | select.POLLPRI | ||
351 | |||
352 | threading.Thread.__init__(self, target=self.threadtarget) | ||
353 | |||
354 | def threadtarget(self): | ||
355 | try: | ||
356 | self.eventloop() | ||
357 | finally: | ||
358 | self.teardown() | ||
359 | |||
360 | def run(self): | ||
361 | self.logger.info("Starting logging thread") | ||
362 | self.readpipe, self.writepipe = os.pipe() | ||
363 | threading.Thread.run(self) | ||
364 | |||
365 | def stop(self): | ||
366 | self.logger.info("Stopping logging thread") | ||
367 | if self.running: | ||
368 | os.write(self.writepipe, "stop") | ||
369 | |||
370 | def teardown(self): | ||
371 | self.close_socket(self.serversock) | ||
372 | |||
373 | if self.readsock is not None: | ||
374 | self.close_socket(self.readsock) | ||
375 | |||
376 | self.close_ignore_error(self.readpipe) | ||
377 | self.close_ignore_error(self.writepipe) | ||
378 | self.running = False | ||
379 | |||
380 | def eventloop(self): | ||
381 | poll = select.poll() | ||
382 | eventmask = self.errorevents | self.readevents | ||
383 | poll.register(self.serversock.fileno()) | ||
384 | poll.register(self.readpipe, eventmask) | ||
385 | |||
386 | breakout = False | ||
387 | self.running = True | ||
388 | self.logger.info("Starting thread event loop") | ||
389 | while not breakout: | ||
390 | events = poll.poll() | ||
391 | for event in events: | ||
392 | # An error occurred, bail out | ||
393 | if event[1] & self.errorevents: | ||
394 | raise Exception(self.stringify_event(event[1])) | ||
395 | |||
396 | # Event to stop the thread | ||
397 | if self.readpipe == event[0]: | ||
398 | self.logger.info("Stop event received") | ||
399 | breakout = True | ||
400 | break | ||
401 | |||
402 | # A connection request was received | ||
403 | elif self.serversock.fileno() == event[0]: | ||
404 | self.logger.info("Connection request received") | ||
405 | self.readsock, _ = self.serversock.accept() | ||
406 | poll.unregister(self.serversock.fileno()) | ||
407 | poll.register(self.readsock.fileno()) | ||
408 | |||
409 | self.logger.info("Setting connection established event") | ||
410 | self.connection_established.set() | ||
411 | |||
412 | # Actual data to be logged | ||
413 | elif self.readsock.fileno() == event[0]: | ||
414 | data = self.readsock.recv(1024) | ||
415 | if not data: | ||
416 | raise Exception("No data on read ready socket") | ||
417 | |||
418 | self.logfunc(data) | ||
419 | |||
420 | def stringify_event(self, event): | ||
421 | val = '' | ||
422 | if select.POLLERR == event: | ||
423 | val = 'POLLER' | ||
424 | elif select.POLLHUP == event: | ||
425 | val = 'POLLHUP' | ||
426 | elif select.POLLNVAL == event: | ||
427 | val = 'POLLNVAL' | ||
428 | return val | ||
429 | |||
430 | def close_socket(self, sock): | ||
431 | sock.shutdown(socket.SHUT_RDWR) | ||
432 | sock.close() | ||
433 | |||
434 | def close_ignore_error(self, fd): | ||
435 | try: | ||
436 | os.close(fd) | ||
437 | except OSError: | ||
438 | pass | ||