1#! /bin/sh
2# SPDX-License-Identifier: GPL-2.0
3# Copyright (c) 2020, Google LLC. All rights reserved.
4# Author: Saravana Kannan <saravanak@google.com>
5
6function help() {
7	cat << EOF
8Usage: $(basename $0) [-c|-d|-m|-f] [filter options] <list of devices>
9
10This script needs to be run on the target device once it has booted to a
11shell.
12
13The script takes as input a list of one or more device directories under
14/sys/devices and then lists the probe dependency chain (suppliers and
15parents) of these devices. It does a breadth first search of the dependency
16chain, so the last entry in the output is close to the root of the
17dependency chain.
18
19By default it lists the full path to the devices under /sys/devices.
20
21It also takes an optional modifier flag as the first parameter to change
22what information is listed in the output. If the requested information is
23not available, the device name is printed.
24
25  -c	lists the compatible string of the dependencies
26  -d	lists the driver name of the dependencies that have probed
27  -m	lists the module name of the dependencies that have a module
28  -f	list the firmware node path of the dependencies
29  -g	list the dependencies as edges and nodes for graphviz
30  -t	list the dependencies as edges for tsort
31
32The filter options provide a way to filter out some dependencies:
33  --allow-no-driver	By default dependencies that don't have a driver
34			attached are ignored. This is to avoid following
35			device links to "class" devices that are created
36			when the consumer probes (as in, not a probe
37			dependency). If you want to follow these links
38			anyway, use this flag.
39
40  --exclude-devlinks	Don't follow device links when tracking probe
41			dependencies.
42
43  --exclude-parents	Don't follow parent devices when tracking probe
44			dependencies.
45
46EOF
47}
48
49function dev_to_detail() {
50	local i=0
51	while [ $i -lt ${#OUT_LIST[@]} ]
52	do
53		local C=${OUT_LIST[i]}
54		local S=${OUT_LIST[i+1]}
55		local D="'$(detail_chosen $C $S)'"
56		if [ ! -z "$D" ]
57		then
58			# This weirdness is needed to work with toybox when
59			# using the -t option.
60			printf '%05u\t%s\n' ${i} "$D" | tr -d \'
61		fi
62		i=$((i+2))
63	done
64}
65
66function already_seen() {
67	local i=0
68	while [ $i -lt ${#OUT_LIST[@]} ]
69	do
70		if [ "$1" = "${OUT_LIST[$i]}" ]
71		then
72			# if-statement treats 0 (no-error) as true
73			return 0
74		fi
75		i=$(($i+2))
76	done
77
78	# if-statement treats 1 (error) as false
79	return 1
80}
81
82# Return 0 (no-error/true) if parent was added
83function add_parent() {
84
85	if [ ${ALLOW_PARENTS} -eq 0 ]
86	then
87		return 1
88	fi
89
90	local CON=$1
91	# $CON could be a symlink path. So, we need to find the real path and
92	# then go up one level to find the real parent.
93	local PARENT=$(realpath $CON/..)
94
95	while [ ! -e ${PARENT}/driver ]
96	do
97		if [ "$PARENT" = "/sys/devices" ]
98		then
99			return 1
100		fi
101		PARENT=$(realpath $PARENT/..)
102	done
103
104	CONSUMERS+=($PARENT)
105	OUT_LIST+=(${CON} ${PARENT})
106	return 0
107}
108
109# Return 0 (no-error/true) if one or more suppliers were added
110function add_suppliers() {
111	local CON=$1
112	local RET=1
113
114	if [ ${ALLOW_DEVLINKS} -eq 0 ]
115	then
116		return 1
117	fi
118
119	SUPPLIER_LINKS=$(ls -1d $CON/supplier:* 2>/dev/null)
120	for SL in $SUPPLIER_LINKS;
121	do
122		SYNC_STATE=$(cat $SL/sync_state_only)
123
124		# sync_state_only links are proxy dependencies.
125		# They can also have cycles. So, don't follow them.
126		if [ "$SYNC_STATE" != '0' ]
127		then
128			continue
129		fi
130
131		SUPPLIER=$(realpath $SL/supplier)
132
133		if [ ! -e $SUPPLIER/driver -a ${ALLOW_NO_DRIVER} -eq 0 ]
134		then
135			continue
136		fi
137
138		CONSUMERS+=($SUPPLIER)
139		OUT_LIST+=(${CON} ${SUPPLIER})
140		RET=0
141	done
142
143	return $RET
144}
145
146function detail_compat() {
147	f=$1/of_node/compatible
148	if [ -e $f ]
149	then
150		echo -n $(cat $f)
151	else
152		echo -n $1
153	fi
154}
155
156function detail_module() {
157	f=$1/driver/module
158	if [ -e $f ]
159	then
160		echo -n $(basename $(realpath $f))
161	else
162		echo -n $1
163	fi
164}
165
166function detail_driver() {
167	f=$1/driver
168	if [ -e $f ]
169	then
170		echo -n $(basename $(realpath $f))
171	else
172		echo -n $1
173	fi
174}
175
176function detail_fwnode() {
177	f=$1/firmware_node
178	if [ ! -e $f ]
179	then
180		f=$1/of_node
181	fi
182
183	if [ -e $f ]
184	then
185		echo -n $(realpath $f)
186	else
187		echo -n $1
188	fi
189}
190
191function detail_graphviz() {
192	if [ "$2" != "ROOT" ]
193	then
194		echo -n "\"$(basename $2)\"->\"$(basename $1)\""
195	else
196		echo -n "\"$(basename $1)\""
197	fi
198}
199
200function detail_tsort() {
201	echo -n "\"$2\" \"$1\""
202}
203
204function detail_device() { echo -n $1; }
205
206alias detail=detail_device
207ALLOW_NO_DRIVER=0
208ALLOW_DEVLINKS=1
209ALLOW_PARENTS=1
210
211while [ $# -gt 0 ]
212do
213	ARG=$1
214	case $ARG in
215		--help)
216			help
217			exit 0
218			;;
219		-c)
220			alias detail=detail_compat
221			;;
222		-m)
223			alias detail=detail_module
224			;;
225		-d)
226			alias detail=detail_driver
227			;;
228		-f)
229			alias detail=detail_fwnode
230			;;
231		-g)
232			alias detail=detail_graphviz
233			;;
234		-t)
235			alias detail=detail_tsort
236			;;
237		--allow-no-driver)
238			ALLOW_NO_DRIVER=1
239			;;
240		--exclude-devlinks)
241			ALLOW_DEVLINKS=0
242			;;
243		--exclude-parents)
244			ALLOW_PARENTS=0
245			;;
246		*)
247			# Stop at the first argument that's not an option.
248			break
249			;;
250	esac
251	shift
252done
253
254function detail_chosen() {
255	detail $1 $2
256}
257
258if [ $# -eq 0 ]
259then
260	help
261	exit 1
262fi
263
264CONSUMERS=($@)
265OUT_LIST=()
266
267# Do a breadth first, non-recursive tracking of suppliers. The parent is also
268# considered a "supplier" as a device can't probe without its parent.
269i=0
270while [ $i -lt ${#CONSUMERS[@]} ]
271do
272	CONSUMER=$(realpath ${CONSUMERS[$i]})
273	i=$(($i+1))
274
275	if already_seen ${CONSUMER}
276	then
277		continue
278	fi
279
280	# If this is not a device with a driver, we don't care about its
281	# suppliers.
282	if [ ! -e ${CONSUMER}/driver -a ${ALLOW_NO_DRIVER} -eq 0 ]
283	then
284		continue
285	fi
286
287	ROOT=1
288
289	# Add suppliers to CONSUMERS list and output the consumer details.
290	#
291	# We don't need to worry about a cycle in the dependency chain causing
292	# infinite loops. That's because the kernel doesn't allow cycles in
293	# device links unless it's a sync_state_only device link. And we ignore
294	# sync_state_only device links inside add_suppliers.
295	if add_suppliers ${CONSUMER}
296	then
297		ROOT=0
298	fi
299
300	if add_parent ${CONSUMER}
301	then
302		ROOT=0
303	fi
304
305	if [ $ROOT -eq 1 ]
306	then
307		OUT_LIST+=(${CONSUMER} "ROOT")
308	fi
309done
310
311# Can NOT combine sort and uniq using sort -suk2 because stable sort in toybox
312# isn't really stable.
313dev_to_detail | sort -k2 -k1 | uniq -f 1 | sort | cut -f2-
314
315exit 0
316