[PATCH 28/49] dtoc: Support scanning of structs in header files

Simon Glass sjg at chromium.org
Tue Dec 29 04:35:14 CET 2020


Drivers can have private / platform data contained in structs and these
struct definitions are generally kept in header files. In order to
generate build-time devices, dtoc needs to generate code that declares
the data contained in those structs. This generated code must include the
relevant header file, to avoid a build error.

We need a way for dtoc to scan header files for struct definitions. Then,
when it wants to generate code that uses a struct, it can make sure it
includes the correct header file, first.

Add a parser for struct information, similar to drivers. Keep a dict of
the structs that were found.

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 tools/dtoc/src_scan.py      | 86 +++++++++++++++++++++++++++++++++++--
 tools/dtoc/test_src_scan.py | 45 +++++++++++++++++++
 2 files changed, 128 insertions(+), 3 deletions(-)

diff --git a/tools/dtoc/src_scan.py b/tools/dtoc/src_scan.py
index 3245d02e09b..bf3e5de9b1e 100644
--- a/tools/dtoc/src_scan.py
+++ b/tools/dtoc/src_scan.py
@@ -126,6 +126,22 @@ class UclassDriver:
         return hash(self.uclass_id)
 
 
+class Struct:
+    """Holds information about a struct definition
+
+    Attributes:
+        name: Struct name, e.g. 'fred' if the struct is 'struct fred'
+        fname: Filename containing the struct, in a format that C files can
+            include, e.g. 'asm/clk.h'
+    """
+    def __init__(self, name, fname):
+        self.name = name
+        self.fname =fname
+
+    def __repr__(self):
+        return ("Struct(name='%s', fname='%s')" % (self.name, self.fname))
+
+
 class Scanner:
     """Scanning of the U-Boot source tree
 
@@ -151,6 +167,9 @@ class Scanner:
         _uclass: Dict of uclass information
             key: uclass name, e.g. 'UCLASS_I2C'
             value: UClassDriver
+        _structs: Dict of all structs found in U-Boot:
+            key: Name of struct
+            value: Struct object
     """
     def __init__(self, basedir, warning_disabled, drivers_additional):
         """Set up a new Scanner
@@ -167,6 +186,7 @@ class Scanner:
         self._of_match = {}
         self._compat_to_driver = {}
         self._uclass = {}
+        self._structs = {}
 
     def get_normalized_compat_name(self, node):
         """Get a node's normalized compat name
@@ -204,6 +224,41 @@ class Scanner:
 
         return compat_list_c[0], compat_list_c[1:]
 
+    def _parse_structs(self, fname, buff):
+        """Parse a H file to extract struct definitions contained within
+
+        This parses 'struct xx {' definitions to figure out what structs this
+        header defines.
+
+        Args:
+            buff (str): Contents of file
+            fname (str): Filename (to use when printing errors)
+        """
+        structs = {}
+
+        re_struct = re.compile('^struct ([a-z0-9_]+) {$')
+        re_asm = re.compile('../arch/[a-z0-9]+/include/asm/(.*)')
+        prefix = ''
+        for line in buff.splitlines():
+            # Handle line continuation
+            if prefix:
+                line = prefix + line
+                prefix = ''
+            if line.endswith('\\'):
+                prefix = line[:-1]
+                continue
+
+            m_struct = re_struct.match(line)
+            if m_struct:
+                name = m_struct.group(1)
+                include_dir = os.path.join(self._basedir, 'include')
+                rel_fname = os.path.relpath(fname, include_dir)
+                m_asm = re_asm.match(rel_fname)
+                if m_asm:
+                    rel_fname = 'asm/' + m_asm.group(1)
+                structs[name] = Struct(name, rel_fname)
+        self._structs.update(structs)
+
     @classmethod
     def _get_re_for_member(cls, member):
         """_get_re_for_member: Get a compiled regular expression
@@ -482,6 +537,29 @@ class Scanner:
                     continue
                 self._driver_aliases[alias[1]] = alias[0]
 
+    def scan_header(self, fname):
+        """Scan a header file to build a list of struct definitions
+
+        It updates the following members:
+            _structs - updated with new Struct records for each struct found
+                in the file
+
+        Args
+            fname: header filename to scan
+        """
+        with open(fname, encoding='utf-8') as inf:
+            try:
+                buff = inf.read()
+            except UnicodeDecodeError:
+                # This seems to happen on older Python versions
+                print("Skipping file '%s' due to unicode error" % fname)
+                return
+
+            # If this file has any U_BOOT_DRIVER() declarations, process it to
+            # obtain driver information
+            if 'struct' in buff:
+                self._parse_structs(fname, buff)
+
     def scan_drivers(self):
         """Scan the driver folders to build a list of driver names and aliases
 
@@ -494,9 +572,11 @@ class Scanner:
             if rel_path.startswith('build') or rel_path.startswith('.git'):
                 continue
             for fname in filenames:
-                if not fname.endswith('.c'):
-                    continue
-                self.scan_driver(dirpath + '/' + fname)
+                pathname = dirpath + '/' + fname
+                if fname.endswith('.c'):
+                    self.scan_driver(pathname)
+                elif fname.endswith('.h'):
+                    self.scan_header(pathname)
 
         for fname in self._drivers_additional:
             if not isinstance(fname, str) or len(fname) == 0:
diff --git a/tools/dtoc/test_src_scan.py b/tools/dtoc/test_src_scan.py
index 641d6495de3..a0b0e097eb2 100644
--- a/tools/dtoc/test_src_scan.py
+++ b/tools/dtoc/test_src_scan.py
@@ -318,3 +318,48 @@ UCLASS_DRIVER(i2c) = {
             scan._parse_uclass_driver('file.c', buff)
         self.assertIn("file.c: Cannot parse uclass ID in driver 'i2c'",
                       str(exc.exception))
+
+    def test_struct_scan(self):
+        """Test collection of struct info"""
+        buff = '''
+/* some comment */
+struct some_struct1 {
+	struct i2c_msg *msgs;
+	uint nmsgs;
+};
+'''
+        scan = src_scan.Scanner(None, False, None)
+        scan._basedir = os.path.join(OUR_PATH, '..', '..')
+        scan._parse_structs('arch/arm/include/asm/file.h', buff)
+        self.assertIn('some_struct1', scan._structs)
+        struc = scan._structs['some_struct1']
+        self.assertEqual('some_struct1', struc.name)
+        self.assertEqual('asm/file.h', struc.fname)
+
+        buff = '''
+/* another comment */
+struct another_struct {
+	int speed_hz;
+	int max_transaction_bytes;
+};
+'''
+        scan._parse_structs('include/file2.h', buff)
+        self.assertIn('another_struct', scan._structs)
+        struc = scan._structs['another_struct']
+        self.assertEqual('another_struct', struc.name)
+        self.assertEqual('file2.h', struc.fname)
+
+        self.assertEqual(2, len(scan._structs))
+
+        self.assertEqual("Struct(name='another_struct', fname='file2.h')",
+                         str(struc))
+
+    def test_struct_scan_errors(self):
+        """Test scanning a header file with an invalid unicode file"""
+        output = tools.GetOutputFilename('output.h')
+        tools.WriteFile(output, b'struct this is a test \x81 of bad unicode')
+
+        scan = src_scan.Scanner(None, False, None)
+        with test_util.capture_sys_output() as (stdout, _):
+            scan.scan_header(output)
+        self.assertIn('due to unicode error', stdout.getvalue())
-- 
2.29.2.729.g45daf8777d-goog



More information about the U-Boot mailing list