u_boot_console_base.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. # SPDX-License-Identifier: GPL-2.0
  2. # Copyright (c) 2015 Stephen Warren
  3. # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
  4. # Common logic to interact with U-Boot via the console. This class provides
  5. # the interface that tests use to execute U-Boot shell commands and wait for
  6. # their results. Sub-classes exist to perform board-type-specific setup
  7. # operations, such as spawning a sub-process for Sandbox, or attaching to the
  8. # serial console of real hardware.
  9. import multiplexed_log
  10. import os
  11. import pytest
  12. import re
  13. import sys
  14. import u_boot_spawn
  15. # Regexes for text we expect U-Boot to send to the console.
  16. pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
  17. pattern_u_boot_spl2_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
  18. pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}[^\r\n]*\\))')
  19. pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
  20. pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
  21. pattern_error_notification = re.compile('## Error: ')
  22. pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
  23. PAT_ID = 0
  24. PAT_RE = 1
  25. bad_pattern_defs = (
  26. ('spl_signon', pattern_u_boot_spl_signon),
  27. ('spl2_signon', pattern_u_boot_spl2_signon),
  28. ('main_signon', pattern_u_boot_main_signon),
  29. ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
  30. ('unknown_command', pattern_unknown_command),
  31. ('error_notification', pattern_error_notification),
  32. ('error_please_reset', pattern_error_please_reset),
  33. )
  34. class ConsoleDisableCheck(object):
  35. """Context manager (for Python's with statement) that temporarily disables
  36. the specified console output error check. This is useful when deliberately
  37. executing a command that is known to trigger one of the error checks, in
  38. order to test that the error condition is actually raised. This class is
  39. used internally by ConsoleBase::disable_check(); it is not intended for
  40. direct usage."""
  41. def __init__(self, console, check_type):
  42. self.console = console
  43. self.check_type = check_type
  44. def __enter__(self):
  45. self.console.disable_check_count[self.check_type] += 1
  46. self.console.eval_bad_patterns()
  47. def __exit__(self, extype, value, traceback):
  48. self.console.disable_check_count[self.check_type] -= 1
  49. self.console.eval_bad_patterns()
  50. class ConsoleSetupTimeout(object):
  51. """Context manager (for Python's with statement) that temporarily sets up
  52. timeout for specific command. This is useful when execution time is greater
  53. then default 30s."""
  54. def __init__(self, console, timeout):
  55. self.p = console.p
  56. self.orig_timeout = self.p.timeout
  57. self.p.timeout = timeout
  58. def __enter__(self):
  59. return self
  60. def __exit__(self, extype, value, traceback):
  61. self.p.timeout = self.orig_timeout
  62. class ConsoleBase(object):
  63. """The interface through which test functions interact with the U-Boot
  64. console. This primarily involves executing shell commands, capturing their
  65. results, and checking for common error conditions. Some common utilities
  66. are also provided too."""
  67. def __init__(self, log, config, max_fifo_fill):
  68. """Initialize a U-Boot console connection.
  69. Can only usefully be called by sub-classes.
  70. Args:
  71. log: A mulptiplex_log.Logfile object, to which the U-Boot output
  72. will be logged.
  73. config: A configuration data structure, as built by conftest.py.
  74. max_fifo_fill: The maximum number of characters to send to U-Boot
  75. command-line before waiting for U-Boot to echo the characters
  76. back. For UART-based HW without HW flow control, this value
  77. should be set less than the UART RX FIFO size to avoid
  78. overflow, assuming that U-Boot can't keep up with full-rate
  79. traffic at the baud rate.
  80. Returns:
  81. Nothing.
  82. """
  83. self.log = log
  84. self.config = config
  85. self.max_fifo_fill = max_fifo_fill
  86. self.logstream = self.log.get_stream('console', sys.stdout)
  87. # Array slice removes leading/trailing quotes
  88. self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
  89. self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
  90. self.p = None
  91. self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
  92. self.eval_bad_patterns()
  93. self.at_prompt = False
  94. self.at_prompt_logevt = None
  95. def get_spawn(self):
  96. # This is not called, ssubclass must define this.
  97. # Return a value to avoid:
  98. # u_boot_console_base.py:348:12: E1128: Assigning result of a function
  99. # call, where the function returns None (assignment-from-none)
  100. return u_boot_spawn.Spawn([])
  101. def eval_bad_patterns(self):
  102. self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
  103. if self.disable_check_count[pat[PAT_ID]] == 0]
  104. self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
  105. if self.disable_check_count[pat[PAT_ID]] == 0]
  106. def close(self):
  107. """Terminate the connection to the U-Boot console.
  108. This function is only useful once all interaction with U-Boot is
  109. complete. Once this function is called, data cannot be sent to or
  110. received from U-Boot.
  111. Args:
  112. None.
  113. Returns:
  114. Nothing.
  115. """
  116. if self.p:
  117. self.p.close()
  118. self.logstream.close()
  119. def wait_for_boot_prompt(self, loop_num = 1):
  120. """Wait for the boot up until command prompt. This is for internal use only.
  121. """
  122. try:
  123. bcfg = self.config.buildconfig
  124. config_spl = bcfg.get('config_spl', 'n') == 'y'
  125. config_spl_serial = bcfg.get('config_spl_serial', 'n') == 'y'
  126. env_spl_skipped = self.config.env.get('env__spl_skipped', False)
  127. env_spl2_skipped = self.config.env.get('env__spl2_skipped', True)
  128. while loop_num > 0:
  129. loop_num -= 1
  130. if config_spl and config_spl_serial and not env_spl_skipped:
  131. m = self.p.expect([pattern_u_boot_spl_signon] +
  132. self.bad_patterns)
  133. if m != 0:
  134. raise Exception('Bad pattern found on SPL console: ' +
  135. self.bad_pattern_ids[m - 1])
  136. if not env_spl2_skipped:
  137. m = self.p.expect([pattern_u_boot_spl2_signon] +
  138. self.bad_patterns)
  139. if m != 0:
  140. raise Exception('Bad pattern found on SPL2 console: ' +
  141. self.bad_pattern_ids[m - 1])
  142. m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
  143. if m != 0:
  144. raise Exception('Bad pattern found on console: ' +
  145. self.bad_pattern_ids[m - 1])
  146. self.u_boot_version_string = self.p.after
  147. while True:
  148. m = self.p.expect([self.prompt_compiled,
  149. pattern_stop_autoboot_prompt] + self.bad_patterns)
  150. if m == 0:
  151. break
  152. if m == 1:
  153. self.p.send(' ')
  154. continue
  155. raise Exception('Bad pattern found on console: ' +
  156. self.bad_pattern_ids[m - 2])
  157. except Exception as ex:
  158. self.log.error(str(ex))
  159. self.cleanup_spawn()
  160. raise
  161. finally:
  162. self.log.timestamp()
  163. def run_command(self, cmd, wait_for_echo=True, send_nl=True,
  164. wait_for_prompt=True, wait_for_reboot=False):
  165. """Execute a command via the U-Boot console.
  166. The command is always sent to U-Boot.
  167. U-Boot echoes any command back to its output, and this function
  168. typically waits for that to occur. The wait can be disabled by setting
  169. wait_for_echo=False, which is useful e.g. when sending CTRL-C to
  170. interrupt a long-running command such as "ums".
  171. Command execution is typically triggered by sending a newline
  172. character. This can be disabled by setting send_nl=False, which is
  173. also useful when sending CTRL-C.
  174. This function typically waits for the command to finish executing, and
  175. returns the console output that it generated. This can be disabled by
  176. setting wait_for_prompt=False, which is useful when invoking a long-
  177. running command such as "ums".
  178. Args:
  179. cmd: The command to send.
  180. wait_for_echo: Boolean indicating whether to wait for U-Boot to
  181. echo the command text back to its output.
  182. send_nl: Boolean indicating whether to send a newline character
  183. after the command string.
  184. wait_for_prompt: Boolean indicating whether to wait for the
  185. command prompt to be sent by U-Boot. This typically occurs
  186. immediately after the command has been executed.
  187. wait_for_reboot: Boolean indication whether to wait for the
  188. reboot U-Boot. If this sets True, wait_for_prompt must also
  189. be True.
  190. Returns:
  191. If wait_for_prompt == False:
  192. Nothing.
  193. Else:
  194. The output from U-Boot during command execution. In other
  195. words, the text U-Boot emitted between the point it echod the
  196. command string and emitted the subsequent command prompts.
  197. """
  198. if self.at_prompt and \
  199. self.at_prompt_logevt != self.logstream.logfile.cur_evt:
  200. self.logstream.write(self.prompt, implicit=True)
  201. try:
  202. self.at_prompt = False
  203. if send_nl:
  204. cmd += '\n'
  205. while cmd:
  206. # Limit max outstanding data, so UART FIFOs don't overflow
  207. chunk = cmd[:self.max_fifo_fill]
  208. cmd = cmd[self.max_fifo_fill:]
  209. self.p.send(chunk)
  210. if not wait_for_echo:
  211. continue
  212. chunk = re.escape(chunk)
  213. chunk = chunk.replace('\\\n', '[\r\n]')
  214. m = self.p.expect([chunk] + self.bad_patterns)
  215. if m != 0:
  216. self.at_prompt = False
  217. raise Exception('Bad pattern found on console: ' +
  218. self.bad_pattern_ids[m - 1])
  219. if not wait_for_prompt:
  220. return
  221. if wait_for_reboot:
  222. self.wait_for_boot_prompt()
  223. else:
  224. m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
  225. if m != 0:
  226. self.at_prompt = False
  227. raise Exception('Bad pattern found on console: ' +
  228. self.bad_pattern_ids[m - 1])
  229. self.at_prompt = True
  230. self.at_prompt_logevt = self.logstream.logfile.cur_evt
  231. # Only strip \r\n; space/TAB might be significant if testing
  232. # indentation.
  233. return self.p.before.strip('\r\n')
  234. except Exception as ex:
  235. self.log.error(str(ex))
  236. self.cleanup_spawn()
  237. raise
  238. finally:
  239. self.log.timestamp()
  240. def run_command_list(self, cmds):
  241. """Run a list of commands.
  242. This is a helper function to call run_command() with default arguments
  243. for each command in a list.
  244. Args:
  245. cmd: List of commands (each a string).
  246. Returns:
  247. A list of output strings from each command, one element for each
  248. command.
  249. """
  250. output = []
  251. for cmd in cmds:
  252. output.append(self.run_command(cmd))
  253. return output
  254. def ctrlc(self):
  255. """Send a CTRL-C character to U-Boot.
  256. This is useful in order to stop execution of long-running synchronous
  257. commands such as "ums".
  258. Args:
  259. None.
  260. Returns:
  261. Nothing.
  262. """
  263. self.log.action('Sending Ctrl-C')
  264. self.run_command(chr(3), wait_for_echo=False, send_nl=False)
  265. def wait_for(self, text):
  266. """Wait for a pattern to be emitted by U-Boot.
  267. This is useful when a long-running command such as "dfu" is executing,
  268. and it periodically emits some text that should show up at a specific
  269. location in the log file.
  270. Args:
  271. text: The text to wait for; either a string (containing raw text,
  272. not a regular expression) or an re object.
  273. Returns:
  274. Nothing.
  275. """
  276. if type(text) == type(''):
  277. text = re.escape(text)
  278. m = self.p.expect([text] + self.bad_patterns)
  279. if m != 0:
  280. raise Exception('Bad pattern found on console: ' +
  281. self.bad_pattern_ids[m - 1])
  282. def drain_console(self):
  283. """Read from and log the U-Boot console for a short time.
  284. U-Boot's console output is only logged when the test code actively
  285. waits for U-Boot to emit specific data. There are cases where tests
  286. can fail without doing this. For example, if a test asks U-Boot to
  287. enable USB device mode, then polls until a host-side device node
  288. exists. In such a case, it is useful to log U-Boot's console output
  289. in case U-Boot printed clues as to why the host-side even did not
  290. occur. This function will do that.
  291. Args:
  292. None.
  293. Returns:
  294. Nothing.
  295. """
  296. # If we are already not connected to U-Boot, there's nothing to drain.
  297. # This should only happen when a previous call to run_command() or
  298. # wait_for() failed (and hence the output has already been logged), or
  299. # the system is shutting down.
  300. if not self.p:
  301. return
  302. orig_timeout = self.p.timeout
  303. try:
  304. # Drain the log for a relatively short time.
  305. self.p.timeout = 1000
  306. # Wait for something U-Boot will likely never send. This will
  307. # cause the console output to be read and logged.
  308. self.p.expect(['This should never match U-Boot output'])
  309. except:
  310. # We expect a timeout, since U-Boot won't print what we waited
  311. # for. Squash it when it happens.
  312. #
  313. # Squash any other exception too. This function is only used to
  314. # drain (and log) the U-Boot console output after a failed test.
  315. # The U-Boot process will be restarted, or target board reset, once
  316. # this function returns. So, we don't care about detecting any
  317. # additional errors, so they're squashed so that the rest of the
  318. # post-test-failure cleanup code can continue operation, and
  319. # correctly terminate any log sections, etc.
  320. pass
  321. finally:
  322. self.p.timeout = orig_timeout
  323. def ensure_spawned(self, expect_reset=False):
  324. """Ensure a connection to a correctly running U-Boot instance.
  325. This may require spawning a new Sandbox process or resetting target
  326. hardware, as defined by the implementation sub-class.
  327. This is an internal function and should not be called directly.
  328. Args:
  329. expect_reset: Boolean indication whether this boot is expected
  330. to be reset while the 1st boot process after main boot before
  331. prompt. False by default.
  332. Returns:
  333. Nothing.
  334. """
  335. if self.p:
  336. # Reset the console timeout value as some tests may change
  337. # its default value during the execution
  338. if not self.config.gdbserver:
  339. self.p.timeout = 30000
  340. return
  341. try:
  342. self.log.start_section('Starting U-Boot')
  343. self.at_prompt = False
  344. self.p = self.get_spawn()
  345. # Real targets can take a long time to scroll large amounts of
  346. # text if LCD is enabled. This value may need tweaking in the
  347. # future, possibly per-test to be optimal. This works for 'help'
  348. # on board 'seaboard'.
  349. if not self.config.gdbserver:
  350. self.p.timeout = 30000
  351. self.p.logfile_read = self.logstream
  352. if expect_reset:
  353. loop_num = 2
  354. else:
  355. loop_num = 1
  356. self.wait_for_boot_prompt(loop_num = loop_num)
  357. self.at_prompt = True
  358. self.at_prompt_logevt = self.logstream.logfile.cur_evt
  359. except Exception as ex:
  360. self.log.error(str(ex))
  361. self.cleanup_spawn()
  362. raise
  363. finally:
  364. self.log.timestamp()
  365. self.log.end_section('Starting U-Boot')
  366. def cleanup_spawn(self):
  367. """Shut down all interaction with the U-Boot instance.
  368. This is used when an error is detected prior to re-establishing a
  369. connection with a fresh U-Boot instance.
  370. This is an internal function and should not be called directly.
  371. Args:
  372. None.
  373. Returns:
  374. Nothing.
  375. """
  376. try:
  377. if self.p:
  378. self.p.close()
  379. except:
  380. pass
  381. self.p = None
  382. def restart_uboot(self, expect_reset=False):
  383. """Shut down and restart U-Boot."""
  384. self.cleanup_spawn()
  385. self.ensure_spawned(expect_reset)
  386. def get_spawn_output(self):
  387. """Return the start-up output from U-Boot
  388. Returns:
  389. The output produced by ensure_spawed(), as a string.
  390. """
  391. if self.p:
  392. return self.p.get_expect_output()
  393. return None
  394. def validate_version_string_in_text(self, text):
  395. """Assert that a command's output includes the U-Boot signon message.
  396. This is primarily useful for validating the "version" command without
  397. duplicating the signon text regex in a test function.
  398. Args:
  399. text: The command output text to check.
  400. Returns:
  401. Nothing. An exception is raised if the validation fails.
  402. """
  403. assert(self.u_boot_version_string in text)
  404. def disable_check(self, check_type):
  405. """Temporarily disable an error check of U-Boot's output.
  406. Create a new context manager (for use with the "with" statement) which
  407. temporarily disables a particular console output error check.
  408. Args:
  409. check_type: The type of error-check to disable. Valid values may
  410. be found in self.disable_check_count above.
  411. Returns:
  412. A context manager object.
  413. """
  414. return ConsoleDisableCheck(self, check_type)
  415. def temporary_timeout(self, timeout):
  416. """Temporarily set up different timeout for commands.
  417. Create a new context manager (for use with the "with" statement) which
  418. temporarily change timeout.
  419. Args:
  420. timeout: Time in milliseconds.
  421. Returns:
  422. A context manager object.
  423. """
  424. return ConsoleSetupTimeout(self, timeout)