test_fdt.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. #!/usr/bin/python
  2. # SPDX-License-Identifier: GPL-2.0+
  3. # Copyright (c) 2018 Google, Inc
  4. # Written by Simon Glass <sjg@chromium.org>
  5. #
  6. from optparse import OptionParser
  7. import glob
  8. import os
  9. import sys
  10. import unittest
  11. # Bring in the patman libraries
  12. our_path = os.path.dirname(os.path.realpath(__file__))
  13. for dirname in ['../patman', '..']:
  14. sys.path.insert(0, os.path.join(our_path, dirname))
  15. import command
  16. import fdt
  17. from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL
  18. import fdt_util
  19. from fdt_util import fdt32_to_cpu
  20. import libfdt
  21. import test_util
  22. import tools
  23. def _GetPropertyValue(dtb, node, prop_name):
  24. """Low-level function to get the property value based on its offset
  25. This looks directly in the device tree at the property's offset to find
  26. its value. It is useful as a check that the property is in the correct
  27. place.
  28. Args:
  29. node: Node to look in
  30. prop_name: Property name to find
  31. Returns:
  32. Tuple:
  33. Prop object found
  34. Value of property as a string (found using property offset)
  35. """
  36. prop = node.props[prop_name]
  37. # Add 12, which is sizeof(struct fdt_property), to get to start of data
  38. offset = prop.GetOffset() + 12
  39. data = dtb.GetContents()[offset:offset + len(prop.value)]
  40. return prop, [chr(x) for x in data]
  41. class TestFdt(unittest.TestCase):
  42. """Tests for the Fdt module
  43. This includes unit tests for some functions and functional tests for the fdt
  44. module.
  45. """
  46. @classmethod
  47. def setUpClass(cls):
  48. tools.PrepareOutputDir(None)
  49. @classmethod
  50. def tearDownClass(cls):
  51. tools.FinaliseOutputDir()
  52. def setUp(self):
  53. self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
  54. def testFdt(self):
  55. """Test that we can open an Fdt"""
  56. self.dtb.Scan()
  57. root = self.dtb.GetRoot()
  58. self.assertTrue(isinstance(root, fdt.Node))
  59. def testGetNode(self):
  60. """Test the GetNode() method"""
  61. node = self.dtb.GetNode('/spl-test')
  62. self.assertTrue(isinstance(node, fdt.Node))
  63. node = self.dtb.GetNode('/i2c@0/pmic@9')
  64. self.assertTrue(isinstance(node, fdt.Node))
  65. self.assertEqual('pmic@9', node.name)
  66. self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing'))
  67. def testFlush(self):
  68. """Check that we can flush the device tree out to its file"""
  69. fname = self.dtb._fname
  70. with open(fname) as fd:
  71. data = fd.read()
  72. os.remove(fname)
  73. with self.assertRaises(IOError):
  74. open(fname)
  75. self.dtb.Flush()
  76. with open(fname) as fd:
  77. data = fd.read()
  78. def testPack(self):
  79. """Test that packing a device tree works"""
  80. self.dtb.Pack()
  81. def testGetFdt(self):
  82. """Tetst that we can access the raw device-tree data"""
  83. self.assertTrue(isinstance(self.dtb.GetContents(), bytearray))
  84. def testGetProps(self):
  85. """Tests obtaining a list of properties"""
  86. node = self.dtb.GetNode('/spl-test')
  87. props = self.dtb.GetProps(node)
  88. self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible',
  89. 'intarray', 'intval', 'longbytearray', 'notstring',
  90. 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'],
  91. sorted(props.keys()))
  92. def testCheckError(self):
  93. """Tests the ChecKError() function"""
  94. with self.assertRaises(ValueError) as e:
  95. fdt.CheckErr(-libfdt.NOTFOUND, 'hello')
  96. self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception))
  97. def testGetFdt(self):
  98. node = self.dtb.GetNode('/spl-test')
  99. self.assertEqual(self.dtb, node.GetFdt())
  100. class TestNode(unittest.TestCase):
  101. """Test operation of the Node class"""
  102. @classmethod
  103. def setUpClass(cls):
  104. tools.PrepareOutputDir(None)
  105. @classmethod
  106. def tearDownClass(cls):
  107. tools.FinaliseOutputDir()
  108. def setUp(self):
  109. self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
  110. self.node = self.dtb.GetNode('/spl-test')
  111. def testOffset(self):
  112. """Tests that we can obtain the offset of a node"""
  113. self.assertTrue(self.node.Offset() > 0)
  114. def testDelete(self):
  115. """Tests that we can delete a property"""
  116. node2 = self.dtb.GetNode('/spl-test2')
  117. offset1 = node2.Offset()
  118. self.node.DeleteProp('intval')
  119. offset2 = node2.Offset()
  120. self.assertTrue(offset2 < offset1)
  121. self.node.DeleteProp('intarray')
  122. offset3 = node2.Offset()
  123. self.assertTrue(offset3 < offset2)
  124. with self.assertRaises(libfdt.FdtException):
  125. self.node.DeleteProp('missing')
  126. def testDeleteGetOffset(self):
  127. """Test that property offset update when properties are deleted"""
  128. self.node.DeleteProp('intval')
  129. prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
  130. self.assertEqual(prop.value, value)
  131. def testFindNode(self):
  132. """Tests that we can find a node using the FindNode() functoin"""
  133. node = self.dtb.GetRoot().FindNode('i2c@0')
  134. self.assertEqual('i2c@0', node.name)
  135. subnode = node.FindNode('pmic@9')
  136. self.assertEqual('pmic@9', subnode.name)
  137. self.assertEqual(None, node.FindNode('missing'))
  138. def testRefreshMissingNode(self):
  139. """Test refreshing offsets when an extra node is present in dtb"""
  140. # Delete it from our tables, not the device tree
  141. del self.dtb._root.subnodes[-1]
  142. with self.assertRaises(ValueError) as e:
  143. self.dtb.Refresh()
  144. self.assertIn('Internal error, offset', str(e.exception))
  145. def testRefreshExtraNode(self):
  146. """Test refreshing offsets when an expected node is missing"""
  147. # Delete it from the device tre, not our tables
  148. self.dtb.GetFdtObj().del_node(self.node.Offset())
  149. with self.assertRaises(ValueError) as e:
  150. self.dtb.Refresh()
  151. self.assertIn('Internal error, node name mismatch '
  152. 'spl-test != spl-test2', str(e.exception))
  153. def testRefreshMissingProp(self):
  154. """Test refreshing offsets when an extra property is present in dtb"""
  155. # Delete it from our tables, not the device tree
  156. del self.node.props['notstring']
  157. with self.assertRaises(ValueError) as e:
  158. self.dtb.Refresh()
  159. self.assertIn("Internal error, property 'notstring' missing, offset ",
  160. str(e.exception))
  161. def testLookupPhandle(self):
  162. """Test looking up a single phandle"""
  163. dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
  164. node = dtb.GetNode('/phandle-source2')
  165. prop = node.props['clocks']
  166. target = dtb.GetNode('/phandle-target')
  167. self.assertEqual(target, dtb.LookupPhandle(fdt32_to_cpu(prop.value)))
  168. class TestProp(unittest.TestCase):
  169. """Test operation of the Prop class"""
  170. @classmethod
  171. def setUpClass(cls):
  172. tools.PrepareOutputDir(None)
  173. @classmethod
  174. def tearDownClass(cls):
  175. tools.FinaliseOutputDir()
  176. def setUp(self):
  177. self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
  178. self.node = self.dtb.GetNode('/spl-test')
  179. self.fdt = self.dtb.GetFdtObj()
  180. def testMissingNode(self):
  181. self.assertEqual(None, self.dtb.GetNode('missing'))
  182. def testPhandle(self):
  183. dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
  184. node = dtb.GetNode('/phandle-source2')
  185. prop = node.props['clocks']
  186. self.assertTrue(fdt32_to_cpu(prop.value) > 0)
  187. def _ConvertProp(self, prop_name):
  188. """Helper function to look up a property in self.node and return it
  189. Args:
  190. Property name to find
  191. Return fdt.Prop object for this property
  192. """
  193. p = self.fdt.getprop(self.node.Offset(), prop_name)
  194. return fdt.Prop(self.node, -1, prop_name, p)
  195. def testMakeProp(self):
  196. """Test we can convert all the the types that are supported"""
  197. prop = self._ConvertProp('boolval')
  198. self.assertEqual(fdt.TYPE_BOOL, prop.type)
  199. self.assertEqual(True, prop.value)
  200. prop = self._ConvertProp('intval')
  201. self.assertEqual(fdt.TYPE_INT, prop.type)
  202. self.assertEqual(1, fdt32_to_cpu(prop.value))
  203. prop = self._ConvertProp('intarray')
  204. self.assertEqual(fdt.TYPE_INT, prop.type)
  205. val = [fdt32_to_cpu(val) for val in prop.value]
  206. self.assertEqual([2, 3, 4], val)
  207. prop = self._ConvertProp('byteval')
  208. self.assertEqual(fdt.TYPE_BYTE, prop.type)
  209. self.assertEqual(5, ord(prop.value))
  210. prop = self._ConvertProp('longbytearray')
  211. self.assertEqual(fdt.TYPE_BYTE, prop.type)
  212. val = [ord(val) for val in prop.value]
  213. self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val)
  214. prop = self._ConvertProp('stringval')
  215. self.assertEqual(fdt.TYPE_STRING, prop.type)
  216. self.assertEqual('message', prop.value)
  217. prop = self._ConvertProp('stringarray')
  218. self.assertEqual(fdt.TYPE_STRING, prop.type)
  219. self.assertEqual(['multi-word', 'message'], prop.value)
  220. prop = self._ConvertProp('notstring')
  221. self.assertEqual(fdt.TYPE_BYTE, prop.type)
  222. val = [ord(val) for val in prop.value]
  223. self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val)
  224. def testGetEmpty(self):
  225. """Tests the GetEmpty() function for the various supported types"""
  226. self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL))
  227. self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE))
  228. self.assertEqual(chr(0) * 4, fdt.Prop.GetEmpty(fdt.TYPE_INT))
  229. self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING))
  230. def testGetOffset(self):
  231. """Test we can get the offset of a property"""
  232. prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
  233. self.assertEqual(prop.value, value)
  234. def testWiden(self):
  235. """Test widening of values"""
  236. node2 = self.dtb.GetNode('/spl-test2')
  237. prop = self.node.props['intval']
  238. # No action
  239. prop2 = node2.props['intval']
  240. prop.Widen(prop2)
  241. self.assertEqual(fdt.TYPE_INT, prop.type)
  242. self.assertEqual(1, fdt32_to_cpu(prop.value))
  243. # Convert singla value to array
  244. prop2 = self.node.props['intarray']
  245. prop.Widen(prop2)
  246. self.assertEqual(fdt.TYPE_INT, prop.type)
  247. self.assertTrue(isinstance(prop.value, list))
  248. # A 4-byte array looks like a single integer. When widened by a longer
  249. # byte array, it should turn into an array.
  250. prop = self.node.props['longbytearray']
  251. prop2 = node2.props['longbytearray']
  252. self.assertFalse(isinstance(prop2.value, list))
  253. self.assertEqual(4, len(prop2.value))
  254. prop2.Widen(prop)
  255. self.assertTrue(isinstance(prop2.value, list))
  256. self.assertEqual(9, len(prop2.value))
  257. # Similarly for a string array
  258. prop = self.node.props['stringval']
  259. prop2 = node2.props['stringarray']
  260. self.assertFalse(isinstance(prop.value, list))
  261. self.assertEqual(7, len(prop.value))
  262. prop.Widen(prop2)
  263. self.assertTrue(isinstance(prop.value, list))
  264. self.assertEqual(3, len(prop.value))
  265. # Enlarging an existing array
  266. prop = self.node.props['stringarray']
  267. prop2 = node2.props['stringarray']
  268. self.assertTrue(isinstance(prop.value, list))
  269. self.assertEqual(2, len(prop.value))
  270. prop.Widen(prop2)
  271. self.assertTrue(isinstance(prop.value, list))
  272. self.assertEqual(3, len(prop.value))
  273. def testAdd(self):
  274. """Test adding properties"""
  275. self.fdt.pack()
  276. # This function should automatically expand the device tree
  277. self.node.AddZeroProp('one')
  278. self.node.AddZeroProp('two')
  279. self.node.AddZeroProp('three')
  280. self.dtb.Sync(auto_resize=True)
  281. # Updating existing properties should be OK, since the device-tree size
  282. # does not change
  283. self.fdt.pack()
  284. self.node.SetInt('one', 1)
  285. self.node.SetInt('two', 2)
  286. self.node.SetInt('three', 3)
  287. self.dtb.Sync(auto_resize=False)
  288. # This should fail since it would need to increase the device-tree size
  289. self.node.AddZeroProp('four')
  290. with self.assertRaises(libfdt.FdtException) as e:
  291. self.dtb.Sync(auto_resize=False)
  292. self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
  293. self.dtb.Sync(auto_resize=True)
  294. def testAddNode(self):
  295. self.fdt.pack()
  296. self.node.AddSubnode('subnode')
  297. with self.assertRaises(libfdt.FdtException) as e:
  298. self.dtb.Sync(auto_resize=False)
  299. self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
  300. self.dtb.Sync(auto_resize=True)
  301. offset = self.fdt.path_offset('/spl-test/subnode')
  302. self.assertTrue(offset > 0)
  303. def testAddMore(self):
  304. """Test various other methods for adding and setting properties"""
  305. self.node.AddZeroProp('one')
  306. self.dtb.Sync(auto_resize=True)
  307. data = self.fdt.getprop(self.node.Offset(), 'one')
  308. self.assertEqual(0, fdt32_to_cpu(data))
  309. self.node.SetInt('one', 1)
  310. self.dtb.Sync(auto_resize=False)
  311. data = self.fdt.getprop(self.node.Offset(), 'one')
  312. self.assertEqual(1, fdt32_to_cpu(data))
  313. val = '123' + chr(0) + '456'
  314. self.node.AddString('string', val)
  315. self.dtb.Sync(auto_resize=True)
  316. data = self.fdt.getprop(self.node.Offset(), 'string')
  317. self.assertEqual(val + '\0', data)
  318. self.fdt.pack()
  319. self.node.SetString('string', val + 'x')
  320. with self.assertRaises(libfdt.FdtException) as e:
  321. self.dtb.Sync(auto_resize=False)
  322. self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
  323. self.node.SetString('string', val[:-1])
  324. prop = self.node.props['string']
  325. prop.SetData(val)
  326. self.dtb.Sync(auto_resize=False)
  327. data = self.fdt.getprop(self.node.Offset(), 'string')
  328. self.assertEqual(val, data)
  329. self.node.AddEmptyProp('empty', 5)
  330. self.dtb.Sync(auto_resize=True)
  331. prop = self.node.props['empty']
  332. prop.SetData(val)
  333. self.dtb.Sync(auto_resize=False)
  334. data = self.fdt.getprop(self.node.Offset(), 'empty')
  335. self.assertEqual(val, data)
  336. self.node.SetData('empty', '123')
  337. self.assertEqual('123', prop.bytes)
  338. def testFromData(self):
  339. dtb2 = fdt.Fdt.FromData(self.dtb.GetContents())
  340. self.assertEqual(dtb2.GetContents(), self.dtb.GetContents())
  341. self.node.AddEmptyProp('empty', 5)
  342. self.dtb.Sync(auto_resize=True)
  343. self.assertTrue(dtb2.GetContents() != self.dtb.GetContents())
  344. class TestFdtUtil(unittest.TestCase):
  345. """Tests for the fdt_util module
  346. This module will likely be mostly replaced at some point, once upstream
  347. libfdt has better Python support. For now, this provides tests for current
  348. functionality.
  349. """
  350. @classmethod
  351. def setUpClass(cls):
  352. tools.PrepareOutputDir(None)
  353. @classmethod
  354. def tearDownClass(cls):
  355. tools.FinaliseOutputDir()
  356. def setUp(self):
  357. self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
  358. self.node = self.dtb.GetNode('/spl-test')
  359. def testGetInt(self):
  360. self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
  361. self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
  362. with self.assertRaises(ValueError) as e:
  363. self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
  364. self.assertIn("property 'intarray' has list value: expecting a single "
  365. 'integer', str(e.exception))
  366. def testGetString(self):
  367. self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
  368. self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
  369. 'test'))
  370. with self.assertRaises(ValueError) as e:
  371. self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
  372. self.assertIn("property 'stringarray' has list value: expecting a "
  373. 'single string', str(e.exception))
  374. def testGetBool(self):
  375. self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
  376. self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
  377. self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
  378. self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
  379. def testGetByte(self):
  380. self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
  381. self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
  382. with self.assertRaises(ValueError) as e:
  383. fdt_util.GetByte(self.node, 'longbytearray')
  384. self.assertIn("property 'longbytearray' has list value: expecting a "
  385. 'single byte', str(e.exception))
  386. with self.assertRaises(ValueError) as e:
  387. fdt_util.GetByte(self.node, 'intval')
  388. self.assertIn("property 'intval' has length 4, expecting 1",
  389. str(e.exception))
  390. def testGetPhandleList(self):
  391. dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
  392. node = dtb.GetNode('/phandle-source2')
  393. self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
  394. node = dtb.GetNode('/phandle-source')
  395. self.assertEqual([1, 2, 11, 3, 12, 13, 1],
  396. fdt_util.GetPhandleList(node, 'clocks'))
  397. self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
  398. def testGetDataType(self):
  399. self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
  400. self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
  401. str))
  402. with self.assertRaises(ValueError) as e:
  403. self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
  404. bool))
  405. def testFdtCellsToCpu(self):
  406. val = self.node.props['intarray'].value
  407. self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
  408. self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
  409. dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
  410. node2 = dtb2.GetNode('/test1')
  411. val = node2.props['reg'].value
  412. self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
  413. def testEnsureCompiled(self):
  414. """Test a degenerate case of this function"""
  415. dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
  416. self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
  417. def testGetPlainBytes(self):
  418. self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
  419. def RunTestCoverage():
  420. """Run the tests and check that we get 100% coverage"""
  421. test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
  422. ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
  423. def RunTests(args):
  424. """Run all the test we have for the fdt model
  425. Args:
  426. args: List of positional args provided to fdt. This can hold a test
  427. name to execute (as in 'fdt -t testFdt', for example)
  428. """
  429. result = unittest.TestResult()
  430. sys.argv = [sys.argv[0]]
  431. test_name = args and args[0] or None
  432. for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
  433. if test_name:
  434. try:
  435. suite = unittest.TestLoader().loadTestsFromName(test_name, module)
  436. except AttributeError:
  437. continue
  438. else:
  439. suite = unittest.TestLoader().loadTestsFromTestCase(module)
  440. suite.run(result)
  441. print result
  442. for _, err in result.errors:
  443. print err
  444. for _, err in result.failures:
  445. print err
  446. if __name__ != '__main__':
  447. sys.exit(1)
  448. parser = OptionParser()
  449. parser.add_option('-B', '--build-dir', type='string', default='b',
  450. help='Directory containing the build output')
  451. parser.add_option('-t', '--test', action='store_true', dest='test',
  452. default=False, help='run tests')
  453. parser.add_option('-T', '--test-coverage', action='store_true',
  454. default=False, help='run tests and check for 100% coverage')
  455. (options, args) = parser.parse_args()
  456. # Run our meagre tests
  457. if options.test:
  458. RunTests(args)
  459. elif options.test_coverage:
  460. RunTestCoverage()