From 9c6838053bd1ca49a60d892c8da2bf7728dc8d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Cab=C3=A9?= Date: Tue, 1 Oct 2024 11:33:55 +0200 Subject: [PATCH] doc: _scripts: gen_devicetree_rest: add link to driver sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When generating the documentation pages for the devicetree bindings, add a link to the .c file implementing the corresponding driver, or folder likely to contain the driver implementation, using heuristics to determine the most likely location of the driver sources. Signed-off-by: Benjamin Cabé --- doc/_scripts/gen_devicetree_rest.py | 80 ++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/doc/_scripts/gen_devicetree_rest.py b/doc/_scripts/gen_devicetree_rest.py index 4637aca7ae9d1c2..1026041f5ff06c1 100644 --- a/doc/_scripts/gen_devicetree_rest.py +++ b/doc/_scripts/gen_devicetree_rest.py @@ -165,8 +165,9 @@ def main(): setup_logging(args.verbose) bindings = load_bindings(args.dts_roots, args.dts_folders) base_binding = load_base_binding() + driver_sources = load_driver_sources() vnd_lookup = VndLookup(args.vendor_prefixes, bindings) - dump_content(bindings, base_binding, vnd_lookup, args.out_dir, + dump_content(bindings, base_binding, vnd_lookup, driver_sources, args.out_dir, args.turbo_mode) def parse_args(): @@ -243,7 +244,60 @@ def load_base_binding(): return edtlib.Binding(os.fspath(base_yaml), base_includes, require_compatible=False, require_description=False) -def dump_content(bindings, base_binding, vnd_lookup, out_dir, turbo_mode): +def load_driver_sources(): + driver_sources = {} + dt_drv_compat_occurrences = defaultdict(list) + + dt_drv_compat_pattern = re.compile(r"#define DT_DRV_COMPAT\s+(.*)") + device_dt_inst_define_pattern = re.compile(r"DEVICE_DT_INST_DEFINE") + + folders_to_scan = ["boards", "drivers", "modules", "soc", "subsys"] + + # When looking at folders_to_scan, a file is considered as a likely driver source if: + # - There is only one and only one file with a "#define DT_DRV_COMPAT " for a given + # compatible. + # - or, a file contains both a "#define DT_DRV_COMPAT " and a + # DEVICE_DT_INST_DEFINE(...) call. + + for folder in folders_to_scan: + for dirpath, _, filenames in os.walk(ZEPHYR_BASE / folder): + for filename in filenames: + if not filename.endswith(('.c', '.h')): + continue + filepath = Path(dirpath) / filename + with open(filepath, "r", encoding="utf-8") as f: + content = f.read() + + relative_path = filepath.relative_to(ZEPHYR_BASE) + + # Find all DT_DRV_COMPAT occurrences in the file + dt_drv_compat_matches = dt_drv_compat_pattern.findall(content) + for compatible in dt_drv_compat_matches: + dt_drv_compat_occurrences[compatible].append(relative_path) + + if dt_drv_compat_matches and device_dt_inst_define_pattern.search(content): + for compatible in dt_drv_compat_matches: + if compatible in driver_sources: + # Mark as ambiguous if multiple files define the same compatible + driver_sources[compatible] = None + else: + driver_sources[compatible] = relative_path + + # Remove ambiguous driver sources + driver_sources = {k: v for k, v in driver_sources.items() if v is not None} + + # Consider DT_DRV_COMPATs with only one occurrence as driver sources + for compatible, occurrences in dt_drv_compat_occurrences.items(): + if compatible not in driver_sources and len(occurrences) == 1: + path = occurrences[0] + # Assume the driver is defined in the enclosing folder if it's a header file + if path.suffix == ".h": + path = path.parent + driver_sources[compatible] = path + + return driver_sources + +def dump_content(bindings, base_binding, vnd_lookup, driver_sources, out_dir, turbo_mode): # Dump the generated .rst files for a vnd2bindings dict. # Files are only written if they are changed. Existing .rst # files which would not be written by the 'vnd2bindings' @@ -256,7 +310,7 @@ def dump_content(bindings, base_binding, vnd_lookup, out_dir, turbo_mode): write_dummy_index(bindings, out_dir) else: write_bindings_rst(vnd_lookup, out_dir) - write_orphans(bindings, base_binding, vnd_lookup, out_dir) + write_orphans(bindings, base_binding, vnd_lookup, driver_sources, out_dir) def setup_bindings_dir(bindings, out_dir): # Make a set of all the Path objects we will be creating for @@ -382,7 +436,7 @@ def write_bindings_rst(vnd_lookup, out_dir): write_if_updated(out_dir / 'bindings.rst', string_io.getvalue()) -def write_orphans(bindings, base_binding, vnd_lookup, out_dir): +def write_orphans(bindings, base_binding, vnd_lookup, driver_sources, out_dir): # Write out_dir / bindings / foo / binding_page.rst for each binding # in 'bindings', along with any "disambiguation" pages needed when a # single compatible string can be handled by multiple bindings. @@ -415,7 +469,7 @@ def write_orphans(bindings, base_binding, vnd_lookup, out_dir): string_io = io.StringIO() print_binding_page(binding, base_names, vnd_lookup, - dup_compat2bindings, string_io) + driver_sources, dup_compat2bindings, string_io) written = write_if_updated(out_dir / 'bindings' / binding_filename(binding), @@ -443,7 +497,7 @@ def write_orphans(bindings, base_binding, vnd_lookup, out_dir): logging.info('done writing :orphan: files; %d files needed updates', num_written) -def print_binding_page(binding, base_names, vnd_lookup, dup_compats, +def print_binding_page(binding, base_names, vnd_lookup, driver_sources,dup_compats, string_io): # Print the rst content for 'binding' to 'string_io'. The # 'dup_compats' argument should support membership testing for @@ -500,6 +554,20 @@ def print_binding_page(binding, base_names, vnd_lookup, dup_compats, f':ref:`{vnd_lookup.vendor(vnd)} <{vnd_lookup.target(vnd)}>`\n', file=string_io) + # Link to driver implementation (if it exists). + compatible = compatible.replace(",", "_") + compatible = compatible.replace("-", "_") + if compatible in driver_sources: + print_block( + f"""\ + Driver implementation + ********************* + + An implementation of a driver matching this compatible is available in :zephyr_file:`{driver_sources[compatible]}`. + """, + string_io, + ) + # Binding description. if binding.bus: bus_help = f'These nodes are "{binding.bus}" bus nodes.'