From acf5cb88b403540408e87d078d916269df371584 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 3 Feb 2021 06:00:55 -0700 Subject: [PATCH] dtoc: Support scanning of structs in header files 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 --- 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 3245d02e09..bf3e5de9b1 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 641d6495de..a0b0e097eb 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.39.5