test_fit.py 15 KB

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