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 <sjg@chromium.org>
This commit is contained in:
Simon Glass 2021-02-03 06:00:55 -07:00
parent 1a8b4b9d94
commit acf5cb88b4
2 changed files with 128 additions and 3 deletions

View File

@ -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:

View File

@ -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())