u_boot_console_base.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. # Copyright (c) 2015 Stephen Warren
  2. # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
  3. #
  4. # SPDX-License-Identifier: GPL-2.0
  5. # Common logic to interact with U-Boot via the console. This class provides
  6. # the interface that tests use to execute U-Boot shell commands and wait for
  7. # their results. Sub-classes exist to perform board-type-specific setup
  8. # operations, such as spawning a sub-process for Sandbox, or attaching to the
  9. # serial console of real hardware.
  10. import multiplexed_log
  11. import os
  12. import pytest
  13. import re
  14. import sys
  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_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
  18. pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
  19. pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
  20. pattern_error_notification = re.compile('## Error: ')
  21. class ConsoleDisableCheck(object):
  22. '''Context manager (for Python's with statement) that temporarily disables
  23. the specified console output error check. This is useful when deliberately
  24. executing a command that is known to trigger one of the error checks, in
  25. order to test that the error condition is actually raised. This class is
  26. used internally by ConsoleBase::disable_check(); it is not intended for
  27. direct usage.'''
  28. def __init__(self, console, check_type):
  29. self.console = console
  30. self.check_type = check_type
  31. def __enter__(self):
  32. self.console.disable_check_count[self.check_type] += 1
  33. def __exit__(self, extype, value, traceback):
  34. self.console.disable_check_count[self.check_type] -= 1
  35. class ConsoleBase(object):
  36. '''The interface through which test functions interact with the U-Boot
  37. console. This primarily involves executing shell commands, capturing their
  38. results, and checking for common error conditions. Some common utilities
  39. are also provided too.'''
  40. def __init__(self, log, config, max_fifo_fill):
  41. '''Initialize a U-Boot console connection.
  42. Can only usefully be called by sub-classes.
  43. Args:
  44. log: A mulptiplex_log.Logfile object, to which the U-Boot output
  45. will be logged.
  46. config: A configuration data structure, as built by conftest.py.
  47. max_fifo_fill: The maximum number of characters to send to U-Boot
  48. command-line before waiting for U-Boot to echo the characters
  49. back. For UART-based HW without HW flow control, this value
  50. should be set less than the UART RX FIFO size to avoid
  51. overflow, assuming that U-Boot can't keep up with full-rate
  52. traffic at the baud rate.
  53. Returns:
  54. Nothing.
  55. '''
  56. self.log = log
  57. self.config = config
  58. self.max_fifo_fill = max_fifo_fill
  59. self.logstream = self.log.get_stream('console', sys.stdout)
  60. # Array slice removes leading/trailing quotes
  61. self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
  62. self.prompt_escaped = re.escape(self.prompt)
  63. self.p = None
  64. self.disable_check_count = {
  65. 'spl_signon': 0,
  66. 'main_signon': 0,
  67. 'unknown_command': 0,
  68. 'error_notification': 0,
  69. }
  70. self.at_prompt = False
  71. self.at_prompt_logevt = None
  72. self.ram_base = None
  73. def close(self):
  74. '''Terminate the connection to the U-Boot console.
  75. This function is only useful once all interaction with U-Boot is
  76. complete. Once this function is called, data cannot be sent to or
  77. received from U-Boot.
  78. Args:
  79. None.
  80. Returns:
  81. Nothing.
  82. '''
  83. if self.p:
  84. self.p.close()
  85. self.logstream.close()
  86. def run_command(self, cmd, wait_for_echo=True, send_nl=True,
  87. wait_for_prompt=True):
  88. '''Execute a command via the U-Boot console.
  89. The command is always sent to U-Boot.
  90. U-Boot echoes any command back to its output, and this function
  91. typically waits for that to occur. The wait can be disabled by setting
  92. wait_for_echo=False, which is useful e.g. when sending CTRL-C to
  93. interrupt a long-running command such as "ums".
  94. Command execution is typically triggered by sending a newline
  95. character. This can be disabled by setting send_nl=False, which is
  96. also useful when sending CTRL-C.
  97. This function typically waits for the command to finish executing, and
  98. returns the console output that it generated. This can be disabled by
  99. setting wait_for_prompt=False, which is useful when invoking a long-
  100. running command such as "ums".
  101. Args:
  102. cmd: The command to send.
  103. wait_for_each: Boolean indicating whether to wait for U-Boot to
  104. echo the command text back to its output.
  105. send_nl: Boolean indicating whether to send a newline character
  106. after the command string.
  107. wait_for_prompt: Boolean indicating whether to wait for the
  108. command prompt to be sent by U-Boot. This typically occurs
  109. immediately after the command has been executed.
  110. Returns:
  111. If wait_for_prompt == False:
  112. Nothing.
  113. Else:
  114. The output from U-Boot during command execution. In other
  115. words, the text U-Boot emitted between the point it echod the
  116. command string and emitted the subsequent command prompts.
  117. '''
  118. self.ensure_spawned()
  119. if self.at_prompt and \
  120. self.at_prompt_logevt != self.logstream.logfile.cur_evt:
  121. self.logstream.write(self.prompt, implicit=True)
  122. bad_patterns = []
  123. bad_pattern_ids = []
  124. if (self.disable_check_count['spl_signon'] == 0 and
  125. self.u_boot_spl_signon):
  126. bad_patterns.append(self.u_boot_spl_signon_escaped)
  127. bad_pattern_ids.append('SPL signon')
  128. if self.disable_check_count['main_signon'] == 0:
  129. bad_patterns.append(self.u_boot_main_signon_escaped)
  130. bad_pattern_ids.append('U-Boot main signon')
  131. if self.disable_check_count['unknown_command'] == 0:
  132. bad_patterns.append(pattern_unknown_command)
  133. bad_pattern_ids.append('Unknown command')
  134. if self.disable_check_count['error_notification'] == 0:
  135. bad_patterns.append(pattern_error_notification)
  136. bad_pattern_ids.append('Error notification')
  137. try:
  138. self.at_prompt = False
  139. if send_nl:
  140. cmd += '\n'
  141. while cmd:
  142. # Limit max outstanding data, so UART FIFOs don't overflow
  143. chunk = cmd[:self.max_fifo_fill]
  144. cmd = cmd[self.max_fifo_fill:]
  145. self.p.send(chunk)
  146. if not wait_for_echo:
  147. continue
  148. chunk = re.escape(chunk)
  149. chunk = chunk.replace('\\\n', '[\r\n]')
  150. m = self.p.expect([chunk] + bad_patterns)
  151. if m != 0:
  152. self.at_prompt = False
  153. raise Exception('Bad pattern found on console: ' +
  154. bad_pattern_ids[m - 1])
  155. if not wait_for_prompt:
  156. return
  157. m = self.p.expect([self.prompt_escaped] + bad_patterns)
  158. if m != 0:
  159. self.at_prompt = False
  160. raise Exception('Bad pattern found on console: ' +
  161. bad_pattern_ids[m - 1])
  162. self.at_prompt = True
  163. self.at_prompt_logevt = self.logstream.logfile.cur_evt
  164. # Only strip \r\n; space/TAB might be significant if testing
  165. # indentation.
  166. return self.p.before.strip('\r\n')
  167. except Exception as ex:
  168. self.log.error(str(ex))
  169. self.cleanup_spawn()
  170. raise
  171. def ctrlc(self):
  172. '''Send a CTRL-C character to U-Boot.
  173. This is useful in order to stop execution of long-running synchronous
  174. commands such as "ums".
  175. Args:
  176. None.
  177. Returns:
  178. Nothing.
  179. '''
  180. self.run_command(chr(3), wait_for_echo=False, send_nl=False)
  181. def ensure_spawned(self):
  182. '''Ensure a connection to a correctly running U-Boot instance.
  183. This may require spawning a new Sandbox process or resetting target
  184. hardware, as defined by the implementation sub-class.
  185. This is an internal function and should not be called directly.
  186. Args:
  187. None.
  188. Returns:
  189. Nothing.
  190. '''
  191. if self.p:
  192. return
  193. try:
  194. self.at_prompt = False
  195. self.log.action('Starting U-Boot')
  196. self.p = self.get_spawn()
  197. # Real targets can take a long time to scroll large amounts of
  198. # text if LCD is enabled. This value may need tweaking in the
  199. # future, possibly per-test to be optimal. This works for 'help'
  200. # on board 'seaboard'.
  201. self.p.timeout = 30000
  202. self.p.logfile_read = self.logstream
  203. if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
  204. self.p.expect([pattern_u_boot_spl_signon])
  205. self.u_boot_spl_signon = self.p.after
  206. self.u_boot_spl_signon_escaped = re.escape(self.p.after)
  207. else:
  208. self.u_boot_spl_signon = None
  209. self.p.expect([pattern_u_boot_main_signon])
  210. self.u_boot_main_signon = self.p.after
  211. self.u_boot_main_signon_escaped = re.escape(self.p.after)
  212. build_idx = self.u_boot_main_signon.find(', Build:')
  213. if build_idx == -1:
  214. self.u_boot_version_string = self.u_boot_main_signon
  215. else:
  216. self.u_boot_version_string = self.u_boot_main_signon[:build_idx]
  217. while True:
  218. match = self.p.expect([self.prompt_escaped,
  219. pattern_stop_autoboot_prompt])
  220. if match == 1:
  221. self.p.send(chr(3)) # CTRL-C
  222. continue
  223. break
  224. self.at_prompt = True
  225. self.at_prompt_logevt = self.logstream.logfile.cur_evt
  226. except Exception as ex:
  227. self.log.error(str(ex))
  228. self.cleanup_spawn()
  229. raise
  230. def cleanup_spawn(self):
  231. '''Shut down all interaction with the U-Boot instance.
  232. This is used when an error is detected prior to re-establishing a
  233. connection with a fresh U-Boot instance.
  234. This is an internal function and should not be called directly.
  235. Args:
  236. None.
  237. Returns:
  238. Nothing.
  239. '''
  240. try:
  241. if self.p:
  242. self.p.close()
  243. except:
  244. pass
  245. self.p = None
  246. def validate_version_string_in_text(self, text):
  247. '''Assert that a command's output includes the U-Boot signon message.
  248. This is primarily useful for validating the "version" command without
  249. duplicating the signon text regex in a test function.
  250. Args:
  251. text: The command output text to check.
  252. Returns:
  253. Nothing. An exception is raised if the validation fails.
  254. '''
  255. assert(self.u_boot_version_string in text)
  256. def disable_check(self, check_type):
  257. '''Temporarily disable an error check of U-Boot's output.
  258. Create a new context manager (for use with the "with" statement) which
  259. temporarily disables a particular console output error check.
  260. Args:
  261. check_type: The type of error-check to disable. Valid values may
  262. be found in self.disable_check_count above.
  263. Returns:
  264. A context manager object.
  265. '''
  266. return ConsoleDisableCheck(self, check_type)
  267. def find_ram_base(self):
  268. '''Find the running U-Boot's RAM location.
  269. Probe the running U-Boot to determine the address of the first bank
  270. of RAM. This is useful for tests that test reading/writing RAM, or
  271. load/save files that aren't associated with some standard address
  272. typically represented in an environment variable such as
  273. ${kernel_addr_r}. The value is cached so that it only needs to be
  274. actively read once.
  275. Args:
  276. None.
  277. Returns:
  278. The address of U-Boot's first RAM bank, as an integer.
  279. '''
  280. if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
  281. pytest.skip('bdinfo command not supported')
  282. if self.ram_base == -1:
  283. pytest.skip('Previously failed to find RAM bank start')
  284. if self.ram_base is not None:
  285. return self.ram_base
  286. with self.log.section('find_ram_base'):
  287. response = self.run_command('bdinfo')
  288. for l in response.split('\n'):
  289. if '-> start' in l:
  290. self.ram_base = int(l.split('=')[1].strip(), 16)
  291. break
  292. if self.ram_base is None:
  293. self.ram_base = -1
  294. raise Exception('Failed to find RAM bank start in `bdinfo`')
  295. return self.ram_base