diff options
Diffstat (limited to 'tools/dtoc/src_scan.py')
-rw-r--r-- | tools/dtoc/src_scan.py | 194 |
1 files changed, 184 insertions, 10 deletions
diff --git a/tools/dtoc/src_scan.py b/tools/dtoc/src_scan.py index f63c9fc166e..095fb6d4766 100644 --- a/tools/dtoc/src_scan.py +++ b/tools/dtoc/src_scan.py @@ -54,15 +54,30 @@ class Driver: Attributes: name: Name of driver. For U_BOOT_DRIVER(x) this is 'x' + fname: Filename where the driver was found + uclass_id: Name of uclass, e.g. 'UCLASS_I2C' + compat: Driver data for each compatible string: + key: Compatible string, e.g. 'rockchip,rk3288-grf' + value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + fname: Filename where the driver was found + priv (str): struct name of the priv_auto member, e.g. 'serial_priv' """ - def __init__(self, name): + def __init__(self, name, fname): self.name = name + self.fname = fname + self.uclass_id = None + self.compat = None + self.priv = '' def __eq__(self, other): - return self.name == other.name + return (self.name == other.name and + self.uclass_id == other.uclass_id and + self.compat == other.compat and + self.priv == other.priv) def __repr__(self): - return "Driver(name='%s')" % self.name + return ("Driver(name='%s', uclass_id='%s', compat=%s, priv=%s)" % + (self.name, self.uclass_id, self.compat, self.priv)) class Scanner: @@ -81,6 +96,12 @@ class Scanner: _warning_disabled: true to disable warnings about driver names not found _drivers_additional (list or str): List of additional drivers to use during scanning + _of_match: Dict holding information about compatible strings + key: Name of struct udevice_id variable + value: Dict of compatible info in that variable: + key: Compatible string, e.g. 'rockchip,rk3288-grf' + value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + _compat_to_driver: Maps compatible strings to Driver """ def __init__(self, basedir, warning_disabled, drivers_additional): """Set up a new Scanner @@ -94,6 +115,8 @@ class Scanner: self._driver_aliases = {} self._drivers_additional = drivers_additional or [] self._warning_disabled = warning_disabled + self._of_match = {} + self._compat_to_driver = {} def get_normalized_compat_name(self, node): """Get a node's normalized compat name @@ -131,10 +154,163 @@ class Scanner: return compat_list_c[0], compat_list_c[1:] + @classmethod + def _get_re_for_member(cls, member): + """_get_re_for_member: Get a compiled regular expression + + Args: + member (str): Struct member name, e.g. 'priv_auto' + + Returns: + re.Pattern: Compiled regular expression that parses: + + .member = sizeof(struct fred), + + and returns "fred" as group 1 + """ + return re.compile(r'^\s*.%s\s*=\s*sizeof\(struct\s+(.*)\),$' % member) + + def _parse_driver(self, fname, buff): + """Parse a C file to extract driver information contained within + + This parses U_BOOT_DRIVER() structs to obtain various pieces of useful + information. + + It updates the following members: + _drivers - updated with new Driver records for each driver found + in the file + _of_match - updated with each compatible string found in the file + _compat_to_driver - Maps compatible string to Driver + + Args: + fname (str): Filename being parsed (used for warnings) + buff (str): Contents of file + + Raises: + ValueError: Compatible variable is mentioned in .of_match in + U_BOOT_DRIVER() but not found in the file + """ + # Dict holding information about compatible strings collected in this + # function so far + # key: Name of struct udevice_id variable + # value: Dict of compatible info in that variable: + # key: Compatible string, e.g. 'rockchip,rk3288-grf' + # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + of_match = {} + + # Dict holding driver information collected in this function so far + # key: Driver name (C name as in U_BOOT_DRIVER(xxx)) + # value: Driver + drivers = {} + + # Collect the driver info + driver = None + re_driver = re.compile(r'U_BOOT_DRIVER\((.*)\)') + + # Collect the uclass ID, e.g. 'UCLASS_SPI' + re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)') + + # Collect the compatible string, e.g. 'rockchip,rk3288-grf' + compat = None + re_compat = re.compile(r'{\s*.compatible\s*=\s*"(.*)"\s*' + r'(,\s*.data\s*=\s*(\S*))?\s*},') + + # This is a dict of compatible strings that were found: + # key: Compatible string, e.g. 'rockchip,rk3288-grf' + # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + compat_dict = {} + + # Holds the var nane of the udevice_id list, e.g. + # 'rk3288_syscon_ids_noc' in + # static const struct udevice_id rk3288_syscon_ids_noc[] = { + ids_name = None + re_ids = re.compile(r'struct udevice_id (.*)\[\]\s*=') + + # Matches the references to the udevice_id list + re_of_match = re.compile( + r'\.of_match\s*=\s*(of_match_ptr\()?([a-z0-9_]+)(\))?,') + + # Matches the struct name for priv + re_priv = self._get_re_for_member('priv_auto') + + prefix = '' + for line in buff.splitlines(): + # Handle line continuation + if prefix: + line = prefix + line + prefix = '' + if line.endswith('\\'): + prefix = line[:-1] + continue + + driver_match = re_driver.search(line) + + # If this line contains U_BOOT_DRIVER()... + if driver: + m_id = re_id.search(line) + m_of_match = re_of_match.search(line) + m_priv = re_priv.match(line) + if m_priv: + driver.priv = m_priv.group(1) + elif m_id: + driver.uclass_id = m_id.group(1) + elif m_of_match: + compat = m_of_match.group(2) + elif '};' in line: + if driver.uclass_id and compat: + if compat not in of_match: + raise ValueError( + "%s: Unknown compatible var '%s' (found: %s)" % + (fname, compat, ','.join(of_match.keys()))) + driver.compat = of_match[compat] + + # This needs to be deterministic, since a driver may + # have multiple compatible strings pointing to it. + # We record the one earliest in the alphabet so it + # will produce the same result on all machines. + for compat_id in of_match[compat]: + old = self._compat_to_driver.get(compat_id) + if not old or driver.name < old.name: + self._compat_to_driver[compat_id] = driver + drivers[driver.name] = driver + else: + # The driver does not have a uclass or compat string. + # The first is required but the second is not, so just + # ignore this. + pass + driver = None + ids_name = None + compat = None + compat_dict = {} + + elif ids_name: + compat_m = re_compat.search(line) + if compat_m: + compat_dict[compat_m.group(1)] = compat_m.group(3) + elif '};' in line: + of_match[ids_name] = compat_dict + ids_name = None + elif driver_match: + driver_name = driver_match.group(1) + driver = Driver(driver_name, fname) + else: + ids_m = re_ids.search(line) + if ids_m: + ids_name = ids_m.group(1) + + # Make the updates based on what we found + self._drivers.update(drivers) + self._of_match.update(of_match) + def scan_driver(self, fname): """Scan a driver file to build a list of driver names and aliases - This procedure will populate self._drivers and self._driver_aliases + It updates the following members: + _drivers - updated with new Driver records for each driver found + in the file + _of_match - updated with each compatible string found in the file + _compat_to_driver - Maps compatible string to Driver + _driver_aliases - Maps alias names to driver name Args fname: Driver filename to scan @@ -147,12 +323,10 @@ class Scanner: print("Skipping file '%s' due to unicode error" % fname) return - # The following re will search for driver names declared as - # U_BOOT_DRIVER(driver_name) - drivers = re.findall(r'U_BOOT_DRIVER\((.*)\)', buff) - - for driver in drivers: - self._drivers[driver] = Driver(driver) + # If this file has any U_BOOT_DRIVER() declarations, process it to + # obtain driver information + if 'U_BOOT_DRIVER' in buff: + self._parse_driver(fname, buff) # The following re will search for driver aliases declared as # DM_DRIVER_ALIAS(alias, driver_name) |