summaryrefslogtreecommitdiff
path: root/scripts/dtc/dt-extract-compatibles
blob: 6570efabaa649ca42efee312b5b7253b2687424b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only

import fnmatch
import os
import re
import argparse


def parse_of_declare_macros(data, include_driver_macros=True):
	""" Find all compatible strings in OF_DECLARE() style macros """
	compat_list = []
	# CPU_METHOD_OF_DECLARE does not have a compatible string
	if include_driver_macros:
		re_macros = r'(?<!CPU_METHOD_)(IRQCHIP|OF)_(DECLARE|MATCH)(_DRIVER)?\(.*?\)'
	else:
		re_macros = r'(?<!CPU_METHOD_)(IRQCHIP|OF)_(DECLARE|MATCH)\(.*?\)'
	for m in re.finditer(re_macros, data):
		try:
			compat = re.search(r'"(.*?)"', m[0])[1]
		except:
			# Fails on compatible strings in #define, so just skip
			continue
		compat_list += [compat]

	return compat_list


def parse_of_device_id(data, match_table_list=None):
	""" Find all compatible strings in of_device_id structs """
	compat_list = []
	for m in re.finditer(r'of_device_id(\s+\S+)?\s+(\S+)\[\](\s+\S+)?\s*=\s*({.*?);', data):
		if match_table_list is not None and m[2] not in match_table_list:
			continue
		compat_list += re.findall(r'\.compatible\s+=\s+"(\S+)"', m[4])

	return compat_list


def parse_of_match_table(data):
	""" Find all driver's of_match_table """
	match_table_list = []
	for m in re.finditer(r'\.of_match_table\s+=\s+(of_match_ptr\()?([a-zA-Z0-9_-]+)', data):
		match_table_list.append(m[2])

	return match_table_list


def parse_of_functions(data, func_name):
	""" Find all compatibles in the last argument of a given function """
	compat_list = []
	for m in re.finditer(rf'{func_name}\(([a-zA-Z0-9_>\(\)"\-]+,\s)*"([a-zA-Z0-9_,-]+)"\)', data):
		compat_list.append(m[2])

	return compat_list


def parse_compatibles(file, compat_ignore_list):
	with open(file, 'r', encoding='utf-8') as f:
		data = f.read().replace('\n', '')

	if compat_ignore_list is not None:
		# For a compatible in the DT to be matched to a driver it needs to show
		# up in a driver's of_match_table
		match_table_list = parse_of_match_table(data)
		compat_list = parse_of_device_id(data, match_table_list)

		compat_list = [compat for compat in compat_list if compat not in compat_ignore_list]
	else:
		compat_list = parse_of_declare_macros(data)
		compat_list += parse_of_device_id(data)
		compat_list += parse_of_functions(data, "_is_compatible")
		compat_list += parse_of_functions(data, "of_find_compatible_node")
		compat_list += parse_of_functions(data, "for_each_compatible_node")
		compat_list += parse_of_functions(data, "of_get_compatible_child")

	return compat_list

def parse_compatibles_to_ignore(file):
	with open(file, 'r', encoding='utf-8') as f:
		data = f.read().replace('\n', '')

	# Compatibles that show up in OF_DECLARE macros can't be expected to
	# match a driver, except for the _DRIVER ones.
	return parse_of_declare_macros(data, include_driver_macros=False)


def print_compat(filename, compatibles):
	if not compatibles:
		return
	if show_filename:
		compat_str = ' '.join(compatibles)
		print(filename + ": compatible(s): " + compat_str)
	else:
		print(*compatibles, sep='\n')

def glob_without_symlinks(root, glob):
	for path, dirs, files in os.walk(root):
		# Ignore hidden directories
		for d in dirs:
			if fnmatch.fnmatch(d, ".*"):
				dirs.remove(d)
		for f in files:
			if fnmatch.fnmatch(f, glob):
				yield os.path.join(path, f)

def files_to_parse(path_args):
	for f in path_args:
		if os.path.isdir(f):
			for filename in glob_without_symlinks(f, "*.c"):
				yield filename
		else:
			yield f

show_filename = False

if __name__ == "__main__":
	ap = argparse.ArgumentParser()
	ap.add_argument("cfile", type=str, nargs='*', help="C source files or directories to parse")
	ap.add_argument('-H', '--with-filename', help="Print filename with compatibles", action="store_true")
	ap.add_argument('-d', '--driver-match', help="Only print compatibles that should match to a driver", action="store_true")
	args = ap.parse_args()

	show_filename = args.with_filename
	compat_ignore_list = None

	if args.driver_match:
		compat_ignore_list = []
		for f in files_to_parse(args.cfile):
			compat_ignore_list.extend(parse_compatibles_to_ignore(f))

	for f in files_to_parse(args.cfile):
		compat_list = parse_compatibles(f, compat_ignore_list)
		print_compat(f, compat_list)