test_fit.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. # SPDX-License-Identifier: GPL-2.0+
  2. # Copyright (c) 2013, Google Inc.
  3. #
  4. # Sanity check of the FIT handling in U-Boot
  5. import os
  6. import pytest
  7. import struct
  8. import u_boot_utils as util
  9. # Define a base ITS which we can adjust using % and a dictionary
  10. base_its = '''
  11. /dts-v1/;
  12. / {
  13. description = "Chrome OS kernel image with one or more FDT blobs";
  14. #address-cells = <1>;
  15. images {
  16. kernel-1 {
  17. data = /incbin/("%(kernel)s");
  18. type = "kernel";
  19. arch = "sandbox";
  20. os = "linux";
  21. compression = "%(compression)s";
  22. load = <0x40000>;
  23. entry = <0x8>;
  24. };
  25. kernel-2 {
  26. data = /incbin/("%(loadables1)s");
  27. type = "kernel";
  28. arch = "sandbox";
  29. os = "linux";
  30. compression = "none";
  31. %(loadables1_load)s
  32. entry = <0x0>;
  33. };
  34. fdt-1 {
  35. description = "snow";
  36. data = /incbin/("%(fdt)s");
  37. type = "flat_dt";
  38. arch = "sandbox";
  39. %(fdt_load)s
  40. compression = "%(compression)s";
  41. signature-1 {
  42. algo = "sha1,rsa2048";
  43. key-name-hint = "dev";
  44. };
  45. };
  46. ramdisk-1 {
  47. description = "snow";
  48. data = /incbin/("%(ramdisk)s");
  49. type = "ramdisk";
  50. arch = "sandbox";
  51. os = "linux";
  52. %(ramdisk_load)s
  53. compression = "%(compression)s";
  54. };
  55. ramdisk-2 {
  56. description = "snow";
  57. data = /incbin/("%(loadables2)s");
  58. type = "ramdisk";
  59. arch = "sandbox";
  60. os = "linux";
  61. %(loadables2_load)s
  62. compression = "none";
  63. };
  64. };
  65. configurations {
  66. default = "conf-1";
  67. conf-1 {
  68. kernel = "kernel-1";
  69. fdt = "fdt-1";
  70. %(ramdisk_config)s
  71. %(loadables_config)s
  72. };
  73. };
  74. };
  75. '''
  76. # Define a base FDT - currently we don't use anything in this
  77. base_fdt = '''
  78. /dts-v1/;
  79. / {
  80. #address-cells = <1>;
  81. #size-cells = <0>;
  82. model = "Sandbox Verified Boot Test";
  83. compatible = "sandbox";
  84. reset@0 {
  85. compatible = "sandbox,reset";
  86. reg = <0>;
  87. };
  88. };
  89. '''
  90. # This is the U-Boot script that is run for each test. First load the FIT,
  91. # then run the 'bootm' command, then save out memory from the places where
  92. # we expect 'bootm' to write things. Then quit.
  93. base_script = '''
  94. host load hostfs 0 %(fit_addr)x %(fit)s
  95. fdt addr %(fit_addr)x
  96. bootm start %(fit_addr)x
  97. bootm loados
  98. host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
  99. host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
  100. host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
  101. host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
  102. host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
  103. '''
  104. @pytest.mark.boardspec('sandbox')
  105. @pytest.mark.buildconfigspec('fit_signature')
  106. @pytest.mark.requiredtool('dtc')
  107. def test_fit(u_boot_console):
  108. def make_fname(leaf):
  109. """Make a temporary filename
  110. Args:
  111. leaf: Leaf name of file to create (within temporary directory)
  112. Return:
  113. Temporary filename
  114. """
  115. return os.path.join(cons.config.build_dir, leaf)
  116. def filesize(fname):
  117. """Get the size of a file
  118. Args:
  119. fname: Filename to check
  120. Return:
  121. Size of file in bytes
  122. """
  123. return os.stat(fname).st_size
  124. def read_file(fname):
  125. """Read the contents of a file
  126. Args:
  127. fname: Filename to read
  128. Returns:
  129. Contents of file as a string
  130. """
  131. with open(fname, 'rb') as fd:
  132. return fd.read()
  133. def make_dtb():
  134. """Make a sample .dts file and compile it to a .dtb
  135. Returns:
  136. Filename of .dtb file created
  137. """
  138. src = make_fname('u-boot.dts')
  139. dtb = make_fname('u-boot.dtb')
  140. with open(src, 'w') as fd:
  141. fd.write(base_fdt)
  142. util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
  143. return dtb
  144. def make_its(params):
  145. """Make a sample .its file with parameters embedded
  146. Args:
  147. params: Dictionary containing parameters to embed in the %() strings
  148. Returns:
  149. Filename of .its file created
  150. """
  151. its = make_fname('test.its')
  152. with open(its, 'w') as fd:
  153. print(base_its % params, file=fd)
  154. return its
  155. def make_fit(mkimage, params):
  156. """Make a sample .fit file ready for loading
  157. This creates a .its script with the selected parameters and uses mkimage to
  158. turn this into a .fit image.
  159. Args:
  160. mkimage: Filename of 'mkimage' utility
  161. params: Dictionary containing parameters to embed in the %() strings
  162. Return:
  163. Filename of .fit file created
  164. """
  165. fit = make_fname('test.fit')
  166. its = make_its(params)
  167. util.run_and_log(cons, [mkimage, '-f', its, fit])
  168. with open(make_fname('u-boot.dts'), 'w') as fd:
  169. fd.write(base_fdt)
  170. return fit
  171. def make_kernel(filename, text):
  172. """Make a sample kernel with test data
  173. Args:
  174. filename: the name of the file you want to create
  175. Returns:
  176. Full path and filename of the kernel it created
  177. """
  178. fname = make_fname(filename)
  179. data = ''
  180. for i in range(100):
  181. data += 'this %s %d is unlikely to boot\n' % (text, i)
  182. with open(fname, 'w') as fd:
  183. print(data, file=fd)
  184. return fname
  185. def make_ramdisk(filename, text):
  186. """Make a sample ramdisk with test data
  187. Returns:
  188. Filename of ramdisk created
  189. """
  190. fname = make_fname(filename)
  191. data = ''
  192. for i in range(100):
  193. data += '%s %d was seldom used in the middle ages\n' % (text, i)
  194. with open(fname, 'w') as fd:
  195. print(data, file=fd)
  196. return fname
  197. def make_compressed(filename):
  198. util.run_and_log(cons, ['gzip', '-f', '-k', filename])
  199. return filename + '.gz'
  200. def find_matching(text, match):
  201. """Find a match in a line of text, and return the unmatched line portion
  202. This is used to extract a part of a line from some text. The match string
  203. is used to locate the line - we use the first line that contains that
  204. match text.
  205. Once we find a match, we discard the match string itself from the line,
  206. and return what remains.
  207. TODO: If this function becomes more generally useful, we could change it
  208. to use regex and return groups.
  209. Args:
  210. text: Text to check (list of strings, one for each command issued)
  211. match: String to search for
  212. Return:
  213. String containing unmatched portion of line
  214. Exceptions:
  215. ValueError: If match is not found
  216. >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
  217. '10'
  218. >>> find_matching(['first line:10', 'second_line:20'], 'second line')
  219. Traceback (most recent call last):
  220. ...
  221. ValueError: Test aborted
  222. >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
  223. '20'
  224. >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
  225. 'third_line:')
  226. '30'
  227. """
  228. __tracebackhide__ = True
  229. for line in '\n'.join(text).splitlines():
  230. pos = line.find(match)
  231. if pos != -1:
  232. return line[:pos] + line[pos + len(match):]
  233. pytest.fail("Expected '%s' but not found in output")
  234. def check_equal(expected_fname, actual_fname, failure_msg):
  235. """Check that a file matches its expected contents
  236. This is always used on out-buffers whose size is decided by the test
  237. script anyway, which in some cases may be larger than what we're
  238. actually looking for. So it's safe to truncate it to the size of the
  239. expected data.
  240. Args:
  241. expected_fname: Filename containing expected contents
  242. actual_fname: Filename containing actual contents
  243. failure_msg: Message to print on failure
  244. """
  245. expected_data = read_file(expected_fname)
  246. actual_data = read_file(actual_fname)
  247. if len(expected_data) < len(actual_data):
  248. actual_data = actual_data[:len(expected_data)]
  249. assert expected_data == actual_data, failure_msg
  250. def check_not_equal(expected_fname, actual_fname, failure_msg):
  251. """Check that a file does not match its expected contents
  252. Args:
  253. expected_fname: Filename containing expected contents
  254. actual_fname: Filename containing actual contents
  255. failure_msg: Message to print on failure
  256. """
  257. expected_data = read_file(expected_fname)
  258. actual_data = read_file(actual_fname)
  259. assert expected_data != actual_data, failure_msg
  260. def run_fit_test(mkimage):
  261. """Basic sanity check of FIT loading in U-Boot
  262. TODO: Almost everything:
  263. - hash algorithms - invalid hash/contents should be detected
  264. - signature algorithms - invalid sig/contents should be detected
  265. - compression
  266. - checking that errors are detected like:
  267. - image overwriting
  268. - missing images
  269. - invalid configurations
  270. - incorrect os/arch/type fields
  271. - empty data
  272. - images too large/small
  273. - invalid FDT (e.g. putting a random binary in instead)
  274. - default configuration selection
  275. - bootm command line parameters should have desired effect
  276. - run code coverage to make sure we are testing all the code
  277. """
  278. # Set up invariant files
  279. control_dtb = make_dtb()
  280. kernel = make_kernel('test-kernel.bin', 'kernel')
  281. ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
  282. loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
  283. loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
  284. kernel_out = make_fname('kernel-out.bin')
  285. fdt = make_fname('u-boot.dtb')
  286. fdt_out = make_fname('fdt-out.dtb')
  287. ramdisk_out = make_fname('ramdisk-out.bin')
  288. loadables1_out = make_fname('loadables1-out.bin')
  289. loadables2_out = make_fname('loadables2-out.bin')
  290. # Set up basic parameters with default values
  291. params = {
  292. 'fit_addr' : 0x1000,
  293. 'kernel' : kernel,
  294. 'kernel_out' : kernel_out,
  295. 'kernel_addr' : 0x40000,
  296. 'kernel_size' : filesize(kernel),
  297. 'fdt' : fdt,
  298. 'fdt_out' : fdt_out,
  299. 'fdt_addr' : 0x80000,
  300. 'fdt_size' : filesize(control_dtb),
  301. 'fdt_load' : '',
  302. 'ramdisk' : ramdisk,
  303. 'ramdisk_out' : ramdisk_out,
  304. 'ramdisk_addr' : 0xc0000,
  305. 'ramdisk_size' : filesize(ramdisk),
  306. 'ramdisk_load' : '',
  307. 'ramdisk_config' : '',
  308. 'loadables1' : loadables1,
  309. 'loadables1_out' : loadables1_out,
  310. 'loadables1_addr' : 0x100000,
  311. 'loadables1_size' : filesize(loadables1),
  312. 'loadables1_load' : '',
  313. 'loadables2' : loadables2,
  314. 'loadables2_out' : loadables2_out,
  315. 'loadables2_addr' : 0x140000,
  316. 'loadables2_size' : filesize(loadables2),
  317. 'loadables2_load' : '',
  318. 'loadables_config' : '',
  319. 'compression' : 'none',
  320. }
  321. # Make a basic FIT and a script to load it
  322. fit = make_fit(mkimage, params)
  323. params['fit'] = fit
  324. cmd = base_script % params
  325. # First check that we can load a kernel
  326. # We could perhaps reduce duplication with some loss of readability
  327. cons.config.dtb = control_dtb
  328. cons.restart_uboot()
  329. with cons.log.section('Kernel load'):
  330. output = cons.run_command_list(cmd.splitlines())
  331. check_equal(kernel, kernel_out, 'Kernel not loaded')
  332. check_not_equal(control_dtb, fdt_out,
  333. 'FDT loaded but should be ignored')
  334. check_not_equal(ramdisk, ramdisk_out,
  335. 'Ramdisk loaded but should not be')
  336. # Find out the offset in the FIT where U-Boot has found the FDT
  337. line = find_matching(output, 'Booting using the fdt blob at ')
  338. fit_offset = int(line, 16) - params['fit_addr']
  339. fdt_magic = struct.pack('>L', 0xd00dfeed)
  340. data = read_file(fit)
  341. # Now find where it actually is in the FIT (skip the first word)
  342. real_fit_offset = data.find(fdt_magic, 4)
  343. assert fit_offset == real_fit_offset, (
  344. 'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
  345. (fit_offset, real_fit_offset))
  346. # Now a kernel and an FDT
  347. with cons.log.section('Kernel + FDT load'):
  348. params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
  349. fit = make_fit(mkimage, params)
  350. cons.restart_uboot()
  351. output = cons.run_command_list(cmd.splitlines())
  352. check_equal(kernel, kernel_out, 'Kernel not loaded')
  353. check_equal(control_dtb, fdt_out, 'FDT not loaded')
  354. check_not_equal(ramdisk, ramdisk_out,
  355. 'Ramdisk loaded but should not be')
  356. # Try a ramdisk
  357. with cons.log.section('Kernel + FDT + Ramdisk load'):
  358. params['ramdisk_config'] = 'ramdisk = "ramdisk-1";'
  359. params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
  360. fit = make_fit(mkimage, params)
  361. cons.restart_uboot()
  362. output = cons.run_command_list(cmd.splitlines())
  363. check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
  364. # Configuration with some Loadables
  365. with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
  366. params['loadables_config'] = 'loadables = "kernel-2", "ramdisk-2";'
  367. params['loadables1_load'] = ('load = <%#x>;' %
  368. params['loadables1_addr'])
  369. params['loadables2_load'] = ('load = <%#x>;' %
  370. params['loadables2_addr'])
  371. fit = make_fit(mkimage, params)
  372. cons.restart_uboot()
  373. output = cons.run_command_list(cmd.splitlines())
  374. check_equal(loadables1, loadables1_out,
  375. 'Loadables1 (kernel) not loaded')
  376. check_equal(loadables2, loadables2_out,
  377. 'Loadables2 (ramdisk) not loaded')
  378. # Kernel, FDT and Ramdisk all compressed
  379. with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
  380. params['compression'] = 'gzip'
  381. params['kernel'] = make_compressed(kernel)
  382. params['fdt'] = make_compressed(fdt)
  383. params['ramdisk'] = make_compressed(ramdisk)
  384. fit = make_fit(mkimage, params)
  385. cons.restart_uboot()
  386. output = cons.run_command_list(cmd.splitlines())
  387. check_equal(kernel, kernel_out, 'Kernel not loaded')
  388. check_equal(control_dtb, fdt_out, 'FDT not loaded')
  389. check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
  390. check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
  391. cons = u_boot_console
  392. try:
  393. # We need to use our own device tree file. Remember to restore it
  394. # afterwards.
  395. old_dtb = cons.config.dtb
  396. mkimage = cons.config.build_dir + '/tools/mkimage'
  397. run_fit_test(mkimage)
  398. finally:
  399. # Go back to the original U-Boot with the correct dtb.
  400. cons.config.dtb = old_dtb
  401. cons.restart_uboot()