diff options
| -rw-r--r-- | scripts/dtc/pylibfdt/libfdt.i_shipped | 230 | ||||
| -rwxr-xr-x | tools/dtoc/test_fdt.py | 175 |
2 files changed, 380 insertions, 25 deletions
diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 326a15c7941..3d527dfe212 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -118,11 +118,14 @@ def check_err_null(val, quiet=()): FdtException if val indicates an error was reported and the error is not in @quiet. """ - # Normally a list is returned which contains the data and its length. - # If we get just an integer error code, it means the function failed. + # Compatibility for SWIG v4.2 and earlier. SWIG 4.2 would drop the first + # item from the list if it was None, returning only the second item. if not isinstance(val, list): - if -val not in quiet: - raise FdtException(val) + val = [None, val] + + if val[0] is None: + if -val[1] not in quiet: + raise FdtException(val[1]) return val class FdtRo(object): @@ -163,7 +166,7 @@ class FdtRo(object): quiet: Errors to ignore (empty to raise on all errors) Returns: - Typle: + Tuple: Offset of the next node, if any, else a -ve error Depth of the returned node, if any, else undefined @@ -299,7 +302,41 @@ class FdtRo(object): Returns: Number of memory reserve-map records """ - return check_err(fdt_get_mem_rsv(self._fdt, index), quiet) + val = fdt_get_mem_rsv(self._fdt, index) + check_err(val[0], quiet) + return val[1:] + + def address_cells(self, nodeoffset, quiet=()): + """Return the number of address cells used by a node's children + + Args: + nodeoffset: Offset of the node to check + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Number of address cells used by the children of @nodeoffset + + Raises: + FdtException if the node has an invalid #address-cells property, + or another error occurs + """ + return check_err(fdt_address_cells(self._fdt, nodeoffset), quiet) + + def size_cells(self, nodeoffset, quiet=()): + """Return the number of size cells used by a node's children + + Args: + nodeoffset: Offset of the node to check + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Number of size cells used by the children of @nodeoffset + + Raises: + FdtException if the node has an invalid #size-cells property, or + another error occurs + """ + return check_err(fdt_size_cells(self._fdt, nodeoffset), quiet) def subnode_offset(self, parentoffset, name, quiet=()): """Get the offset of a named subnode @@ -397,8 +434,8 @@ class FdtRo(object): """ pdata = check_err_null( fdt_get_property_by_offset(self._fdt, prop_offset), quiet) - if isinstance(pdata, (int)): - return pdata + if pdata[0] is None: + return pdata[1] return Property(pdata[0], pdata[1]) def getprop(self, nodeoffset, prop_name, quiet=()): @@ -419,9 +456,38 @@ class FdtRo(object): """ pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), quiet) - if isinstance(pdata, (int)): - return pdata - return Property(prop_name, bytes(pdata[0])) + if pdata[0] is None: + return pdata[1] + return Property(prop_name, bytearray(pdata[0])) + + def hasprop(self, nodeoffset, prop_name, quiet=()): + """Check if a node has a property + + This can be used to check boolean properties + + Args: + nodeoffset: Node offset containing property to check + prop_name: Name of property to check + quiet: Errors to ignore (empty to raise on all errors). Note that + NOTFOUND is added internally by this function so need not be + provided + + Returns: + True if the property exists in the node, else False. If an error + other than -NOTFOUND is returned by fdt_getprop() then the error + is return (-ve integer) + + Raises: + FdtError if any error occurs other than NOTFOUND (e.g. the + nodeoffset is invalid) + """ + pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), + quiet + (NOTFOUND,)) + if pdata[0] is None: + if pdata[1] == -NOTFOUND: + return False + return pdata[1] + return True def get_phandle(self, nodeoffset): """Get the phandle of a node @@ -447,6 +513,29 @@ class FdtRo(object): """ return fdt_get_alias(self._fdt, name) + def get_path(self, nodeoffset, size_hint=1024, quiet=()): + """Get the full path of a node + + Args: + nodeoffset: Node offset to check + size_hint: Hint for size of returned string + + Returns: + Full path to the node + + Raises: + FdtException if an error occurs + """ + while True: + ret, path = fdt_get_path(self._fdt, nodeoffset, size_hint) + if ret == -NOSPACE: + size_hint *= 2 + continue + err = check_err(ret, quiet) + if err: + return err + return path + def parent_offset(self, nodeoffset, quiet=()): """Get the offset of a node's parent @@ -551,6 +640,42 @@ class Fdt(FdtRo): del self._fdt[self.totalsize():] return err + def add_mem_rsv(self, addr, size, quiet=()): + """Add a memory reserve-map record + + This asks the client program not to use the given region of memory, + e.g. because something was loaded there. + + Args: + addr: Start address of the region to reserve + size: Size of the region to reserve, in bytes + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if there is no space for another record, or another + error occurs + """ + return check_err(fdt_add_mem_rsv(self._fdt, addr, size), quiet) + + def del_mem_rsv(self, index, quiet=()): + """Remove the indexed memory reserve-map record + + Args: + index: Record to remove (0=first) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if there is no record at @index, or another error + occurs + """ + return check_err(fdt_del_mem_rsv(self._fdt, index), quiet) + def set_name(self, nodeoffset, name, quiet=()): """Set the name of a node @@ -586,6 +711,32 @@ class Fdt(FdtRo): return check_err(fdt_setprop(self._fdt, nodeoffset, prop_name, val, len(val)), quiet) + def setprop_bool(self, nodeoffset, prop_name, val, quiet=()): + """Set the boolean value of a property + + Either: + adds the property if not already present; or + deletes the property if present + + Args: + nodeoffset: Node offset containing the property to create/delete + prop_name: Name of property + val: Boolean value to write (i.e. True or False) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + exists = self.hasprop(nodeoffset, prop_name, quiet) + if val != exists: + if val: + return self.setprop(nodeoffset, prop_name, b'', quiet=quiet) + else: + return self.delprop(nodeoffset, prop_name, quiet=quiet) + def setprop_u32(self, nodeoffset, prop_name, val, quiet=()): """Set the value of a property @@ -720,6 +871,21 @@ class Property(bytearray): def as_int64(self): return self.as_cell('q') + def as_list(self, fmt): + return list(map(lambda x: x[0], struct.iter_unpack('>' + fmt, self))) + + def as_uint32_list(self): + return self.as_list('L') + + def as_int32_list(self): + return self.as_list('l') + + def as_uint64_list(self): + return self.as_list('Q') + + def as_int64_list(self): + return self.as_list('q') + def as_str(self): """Unicode is supported by decoding from UTF-8""" if self[-1] != 0: @@ -728,6 +894,13 @@ class Property(bytearray): raise ValueError('Property contains embedded nul characters') return self[:-1].decode('utf-8') + def as_stringlist(self): + """Unicode is supported by decoding from UTF-8""" + if self[-1] != 0: + raise ValueError('Property lacks nul termination') + parts = self[:-1].split(b'\x00') + return list(map(lambda x: x.decode('utf-8'), parts)) + class FdtSw(FdtRo): """Software interface to create a device tree from scratch @@ -756,7 +929,7 @@ class FdtSw(FdtRo): device tree. This will be increased automatically as needed as new items are added to the tree. """ - INC_SIZE = 1024 # Expand size by this much when out of space + INC_SIZE = 1024 # Expand size by at least this much when out of space def __init__(self, size_hint=None): """Create a new FdtSw object @@ -801,6 +974,10 @@ class FdtSw(FdtRo): -NOSPACE then the FDT will be expanded to have more space, and True will be returned, indicating that the operation needs to be tried again. + Each expansion copies the whole tree into a new buffer, so the size is + at least doubled rather than grown by a fixed amount, to keep the total + amount of copying proportional to the size of the tree. + Args: val: Return value from the operation that was attempted @@ -808,7 +985,7 @@ class FdtSw(FdtRo): True if the operation must be retried, else False """ if check_err(val, QUIET_NOSPACE) < 0: - self.resize(len(self._fdt) + self.INC_SIZE) + self.resize(len(self._fdt) + max(len(self._fdt), self.INC_SIZE)) return True return False @@ -935,7 +1112,7 @@ class FdtSw(FdtRo): Args: name: Name of property to add - val: Value of property + val: Value of property (bytes) quiet: Errors to ignore (empty to raise on all errors) Raises: @@ -996,6 +1173,9 @@ class NodeAdder(): %rename(fdt_property) fdt_property_func; +%immutable fdt_property::data; +%immutable fdt_node_header::name; + /* * fdt32_t is a big-endian 32-bit value defined to uint32_t in libfdt_env.h * so use the same type here. @@ -1045,14 +1225,16 @@ typedef uint32_t fdt32_t; /* typemap used for fdt_getprop() */ %typemap(out) (const void *) { - if (!$1) + if (!$1) { $result = Py_None; - else + Py_INCREF($result); + } else { %#if PY_VERSION_HEX >= 0x03000000 - $result = Py_BuildValue("y#", $1, *arg4); + $result = Py_BuildValue("y#", $1, (Py_ssize_t)*arg4); %#else - $result = Py_BuildValue("s#", $1, *arg4); + $result = Py_BuildValue("s#", $1, (Py_ssize_t)*arg4); %#endif + } } /* typemap used for fdt_setprop() */ @@ -1088,14 +1270,14 @@ typedef uint32_t fdt32_t; %typemap(argout) uint64_t * { PyObject *val = PyLong_FromUnsignedLongLong(*arg$argnum); - if (!result) { - if (PyTuple_GET_SIZE(resultobj) == 0) - resultobj = val; - else - resultobj = SWIG_AppendOutput(resultobj, val); - } + resultobj = SWIG_AppendOutput(resultobj, val); } +%include "cstring.i" + +%cstring_output_maxsize(char *buf, int buflen); +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); + /* We have both struct fdt_property and a function fdt_property() */ %warnfilter(302) fdt_property; diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index a858da127bf..66d346b1272 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -10,6 +10,7 @@ Written by Simon Glass <[email protected]> from argparse import ArgumentParser import os import shutil +import struct import sys import tempfile import unittest @@ -754,6 +755,178 @@ class TestProp(unittest.TestCase): self.dtb.GetFilename()) +class TestPylibfdt(unittest.TestCase): + """Tests for the parts of pylibfdt that dtoc does not itself use + + These bindings come from upstream dtc and have no coverage elsewhere in + U-Boot, so exercise them directly rather than through the Fdt wrapper. + """ + + @classmethod + def setUpClass(cls): + tools.prepare_output_dir(None) + + @classmethod + def tearDownClass(cls): + tools.finalise_output_dir() + + def setUp(self): + self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts')) + self.fdt = self.dtb.GetFdtObj() + self.node = self.fdt.path_offset('/spl-test') + + def test_hasprop(self): + """Test checking for the presence of a property""" + self.assertTrue(self.fdt.hasprop(self.node, 'boolval')) + self.assertTrue(self.fdt.hasprop(self.node, 'intval')) + self.assertFalse(self.fdt.hasprop(self.node, 'missing')) + + def test_hasprop_bad_node(self): + """Test that hasprop() still reports errors other than NOTFOUND""" + with self.assertRaises(libfdt.FdtException) as exc: + self.fdt.hasprop(-1, 'boolval') + self.assertEqual(-libfdt.BADOFFSET, exc.exception.err) + + def test_setprop_bool(self): + """Test creating and deleting a boolean property""" + # Leave room for the new property + self.fdt.resize(self.fdt.totalsize() + 1024) + node = self.fdt.path_offset('/spl-test') + + self.fdt.setprop_bool(node, 'newbool', True) + self.assertTrue(self.fdt.hasprop(node, 'newbool')) + self.assertEqual(b'', self.fdt.getprop(node, 'newbool')) + + # Setting it again should be a no-op + self.fdt.setprop_bool(node, 'newbool', True) + self.assertTrue(self.fdt.hasprop(node, 'newbool')) + + self.fdt.setprop_bool(node, 'newbool', False) + self.assertFalse(self.fdt.hasprop(node, 'newbool')) + + # Deleting it again should also be a no-op + self.fdt.setprop_bool(node, 'newbool', False) + self.assertFalse(self.fdt.hasprop(node, 'newbool')) + + def test_get_path(self): + """Test reading back the full path of a node""" + node = self.fdt.path_offset('/i2c@0/pmic@9') + self.assertEqual('/i2c@0/pmic@9', self.fdt.get_path(node)) + self.assertEqual('/', self.fdt.get_path(0)) + + def test_get_path_no_space(self): + """Test that get_path() retries with a larger buffer as needed""" + node = self.fdt.path_offset('/i2c@0/pmic@9') + self.assertEqual('/i2c@0/pmic@9', self.fdt.get_path(node, size_hint=1)) + + def test_get_path_bad_node(self): + """Test get_path() on an invalid node offset""" + with self.assertRaises(libfdt.FdtException) as exc: + self.fdt.get_path(-1) + self.assertEqual(-libfdt.BADOFFSET, exc.exception.err) + + def test_as_stringlist(self): + """Test decoding a string-list property""" + prop = self.fdt.getprop(self.node, 'stringarray') + self.assertEqual(['multi-word', 'message'], prop.as_stringlist()) + + prop = self.fdt.getprop(self.node, 'stringval') + self.assertEqual(['message'], prop.as_stringlist()) + + def test_as_int_lists(self): + """Test decoding integer-array properties""" + prop = self.fdt.getprop(self.node, 'intarray') + self.assertEqual([2, 3, 4], prop.as_uint32_list()) + self.assertEqual([2, 3, 4], prop.as_int32_list()) + + prop = self.fdt.getprop(self.node, 'int64val') + self.assertEqual([0x123456789abcdef0], prop.as_uint64_list()) + self.assertEqual([0x123456789abcdef0], prop.as_int64_list()) + + def test_as_int_lists_negative(self): + """Test that the signed accessors differ from the unsigned ones""" + self.fdt.resize(self.fdt.totalsize() + 1024) + node = self.fdt.path_offset('/spl-test') + + self.fdt.setprop(node, 'negs32', struct.pack('>ll', -1, -2)) + prop = self.fdt.getprop(node, 'negs32') + self.assertEqual([-1, -2], prop.as_int32_list()) + self.assertEqual([0xffffffff, 0xfffffffe], prop.as_uint32_list()) + + self.fdt.setprop(node, 'negs64', struct.pack('>qq', -1, -2)) + prop = self.fdt.getprop(node, 'negs64') + self.assertEqual([-1, -2], prop.as_int64_list()) + self.assertEqual([0xffffffffffffffff, 0xfffffffffffffffe], + prop.as_uint64_list()) + + def test_address_and_size_cells(self): + """Test reading the cell counts a node uses for its children""" + self.assertEqual(1, self.fdt.address_cells(0)) + self.assertEqual(1, self.fdt.size_cells(0)) + + # i2c@0 overrides both + i2c = self.fdt.path_offset('/i2c@0') + self.assertEqual(1, self.fdt.address_cells(i2c)) + self.assertEqual(0, self.fdt.size_cells(i2c)) + + def test_mem_rsv(self): + """Test adding, reading back and deleting reserve-map records""" + self.assertEqual(0, self.fdt.num_mem_rsv()) + + # Leave room for the new records + self.fdt.resize(self.fdt.totalsize() + 1024) + self.fdt.add_mem_rsv(0xdeadbeef00000000, 0x100000) + self.fdt.add_mem_rsv(0x1000, 0x2000) + self.assertEqual(2, self.fdt.num_mem_rsv()) + self.assertEqual([0xdeadbeef00000000, 0x100000], + list(self.fdt.get_mem_rsv(0))) + self.assertEqual([0x1000, 0x2000], list(self.fdt.get_mem_rsv(1))) + + self.fdt.del_mem_rsv(0) + self.assertEqual(1, self.fdt.num_mem_rsv()) + self.assertEqual([0x1000, 0x2000], list(self.fdt.get_mem_rsv(0))) + + def test_del_mem_rsv_missing(self): + """Test deleting a reserve-map record which is not there""" + with self.assertRaises(libfdt.FdtException) as exc: + self.fdt.del_mem_rsv(0) + self.assertEqual(-libfdt.NOTFOUND, exc.exception.err) + + def test_fdtsw_growth(self): + """Test that running out of space at least doubles the buffer""" + fdtsw = libfdt.FdtSw() + size = len(fdtsw._fdt) + self.assertEqual(fdtsw.INC_SIZE, size) + + self.assertTrue(fdtsw.check_space(-libfdt.NOSPACE)) + self.assertEqual(size * 2, len(fdtsw._fdt)) + + self.assertTrue(fdtsw.check_space(-libfdt.NOSPACE)) + self.assertEqual(size * 4, len(fdtsw._fdt)) + + # Anything that is not an out-of-space error leaves the buffer alone + self.assertFalse(fdtsw.check_space(0)) + self.assertEqual(size * 4, len(fdtsw._fdt)) + + def test_fdtsw_build(self): + """Test building a tree large enough to need several expansions""" + fdtsw = libfdt.FdtSw() + fdtsw.finish_reservemap() + fdtsw.begin_node('') + for i in range(50): + fdtsw.begin_node(f'node{i}') + fdtsw.property('data', b'x' * 200) + fdtsw.end_node() + fdtsw.end_node() + + out = fdtsw.as_fdt() + self.assertGreater(out.totalsize(), fdtsw.INC_SIZE * 4) + for i in range(50): + node = out.path_offset(f'/node{i}') + self.assertEqual(f'/node{i}', out.get_path(node)) + self.assertEqual(b'x' * 200, out.getprop(node, 'data')) + + class TestFdtUtil(unittest.TestCase): """Tests for the fdt_util module @@ -970,7 +1143,7 @@ def run_tests(names, processes): test_name = names[0] if names else None result = test_util.run_test_suites( 'test_fdt', False, False, False, False, processes, test_name, None, - [TestFdt, TestNode, TestProp, TestFdtUtil]) + [TestFdt, TestNode, TestProp, TestPylibfdt, TestFdtUtil]) return (0 if result.wasSuccessful() else 1) |
