162306a36Sopenharmony_ci#! /bin/sh
262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
362306a36Sopenharmony_ci# Copyright (c) 2020, Google LLC. All rights reserved.
462306a36Sopenharmony_ci# Author: Saravana Kannan <saravanak@google.com>
562306a36Sopenharmony_ci
662306a36Sopenharmony_cifunction help() {
762306a36Sopenharmony_ci	cat << EOF
862306a36Sopenharmony_ciUsage: $(basename $0) [-c|-d|-m|-f] [filter options] <list of devices>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ciThis script needs to be run on the target device once it has booted to a
1162306a36Sopenharmony_cishell.
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ciThe script takes as input a list of one or more device directories under
1462306a36Sopenharmony_ci/sys/devices and then lists the probe dependency chain (suppliers and
1562306a36Sopenharmony_ciparents) of these devices. It does a breadth first search of the dependency
1662306a36Sopenharmony_cichain, so the last entry in the output is close to the root of the
1762306a36Sopenharmony_cidependency chain.
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciBy default it lists the full path to the devices under /sys/devices.
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciIt also takes an optional modifier flag as the first parameter to change
2262306a36Sopenharmony_ciwhat information is listed in the output. If the requested information is
2362306a36Sopenharmony_cinot available, the device name is printed.
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci  -c	lists the compatible string of the dependencies
2662306a36Sopenharmony_ci  -d	lists the driver name of the dependencies that have probed
2762306a36Sopenharmony_ci  -m	lists the module name of the dependencies that have a module
2862306a36Sopenharmony_ci  -f	list the firmware node path of the dependencies
2962306a36Sopenharmony_ci  -g	list the dependencies as edges and nodes for graphviz
3062306a36Sopenharmony_ci  -t	list the dependencies as edges for tsort
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciThe filter options provide a way to filter out some dependencies:
3362306a36Sopenharmony_ci  --allow-no-driver	By default dependencies that don't have a driver
3462306a36Sopenharmony_ci			attached are ignored. This is to avoid following
3562306a36Sopenharmony_ci			device links to "class" devices that are created
3662306a36Sopenharmony_ci			when the consumer probes (as in, not a probe
3762306a36Sopenharmony_ci			dependency). If you want to follow these links
3862306a36Sopenharmony_ci			anyway, use this flag.
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci  --exclude-devlinks	Don't follow device links when tracking probe
4162306a36Sopenharmony_ci			dependencies.
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci  --exclude-parents	Don't follow parent devices when tracking probe
4462306a36Sopenharmony_ci			dependencies.
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ciEOF
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cifunction dev_to_detail() {
5062306a36Sopenharmony_ci	local i=0
5162306a36Sopenharmony_ci	while [ $i -lt ${#OUT_LIST[@]} ]
5262306a36Sopenharmony_ci	do
5362306a36Sopenharmony_ci		local C=${OUT_LIST[i]}
5462306a36Sopenharmony_ci		local S=${OUT_LIST[i+1]}
5562306a36Sopenharmony_ci		local D="'$(detail_chosen $C $S)'"
5662306a36Sopenharmony_ci		if [ ! -z "$D" ]
5762306a36Sopenharmony_ci		then
5862306a36Sopenharmony_ci			# This weirdness is needed to work with toybox when
5962306a36Sopenharmony_ci			# using the -t option.
6062306a36Sopenharmony_ci			printf '%05u\t%s\n' ${i} "$D" | tr -d \'
6162306a36Sopenharmony_ci		fi
6262306a36Sopenharmony_ci		i=$((i+2))
6362306a36Sopenharmony_ci	done
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cifunction already_seen() {
6762306a36Sopenharmony_ci	local i=0
6862306a36Sopenharmony_ci	while [ $i -lt ${#OUT_LIST[@]} ]
6962306a36Sopenharmony_ci	do
7062306a36Sopenharmony_ci		if [ "$1" = "${OUT_LIST[$i]}" ]
7162306a36Sopenharmony_ci		then
7262306a36Sopenharmony_ci			# if-statement treats 0 (no-error) as true
7362306a36Sopenharmony_ci			return 0
7462306a36Sopenharmony_ci		fi
7562306a36Sopenharmony_ci		i=$(($i+2))
7662306a36Sopenharmony_ci	done
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	# if-statement treats 1 (error) as false
7962306a36Sopenharmony_ci	return 1
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci# Return 0 (no-error/true) if parent was added
8362306a36Sopenharmony_cifunction add_parent() {
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if [ ${ALLOW_PARENTS} -eq 0 ]
8662306a36Sopenharmony_ci	then
8762306a36Sopenharmony_ci		return 1
8862306a36Sopenharmony_ci	fi
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	local CON=$1
9162306a36Sopenharmony_ci	# $CON could be a symlink path. So, we need to find the real path and
9262306a36Sopenharmony_ci	# then go up one level to find the real parent.
9362306a36Sopenharmony_ci	local PARENT=$(realpath $CON/..)
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	while [ ! -e ${PARENT}/driver ]
9662306a36Sopenharmony_ci	do
9762306a36Sopenharmony_ci		if [ "$PARENT" = "/sys/devices" ]
9862306a36Sopenharmony_ci		then
9962306a36Sopenharmony_ci			return 1
10062306a36Sopenharmony_ci		fi
10162306a36Sopenharmony_ci		PARENT=$(realpath $PARENT/..)
10262306a36Sopenharmony_ci	done
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	CONSUMERS+=($PARENT)
10562306a36Sopenharmony_ci	OUT_LIST+=(${CON} ${PARENT})
10662306a36Sopenharmony_ci	return 0
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci# Return 0 (no-error/true) if one or more suppliers were added
11062306a36Sopenharmony_cifunction add_suppliers() {
11162306a36Sopenharmony_ci	local CON=$1
11262306a36Sopenharmony_ci	local RET=1
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if [ ${ALLOW_DEVLINKS} -eq 0 ]
11562306a36Sopenharmony_ci	then
11662306a36Sopenharmony_ci		return 1
11762306a36Sopenharmony_ci	fi
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	SUPPLIER_LINKS=$(ls -1d $CON/supplier:* 2>/dev/null)
12062306a36Sopenharmony_ci	for SL in $SUPPLIER_LINKS;
12162306a36Sopenharmony_ci	do
12262306a36Sopenharmony_ci		SYNC_STATE=$(cat $SL/sync_state_only)
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		# sync_state_only links are proxy dependencies.
12562306a36Sopenharmony_ci		# They can also have cycles. So, don't follow them.
12662306a36Sopenharmony_ci		if [ "$SYNC_STATE" != '0' ]
12762306a36Sopenharmony_ci		then
12862306a36Sopenharmony_ci			continue
12962306a36Sopenharmony_ci		fi
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci		SUPPLIER=$(realpath $SL/supplier)
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci		if [ ! -e $SUPPLIER/driver -a ${ALLOW_NO_DRIVER} -eq 0 ]
13462306a36Sopenharmony_ci		then
13562306a36Sopenharmony_ci			continue
13662306a36Sopenharmony_ci		fi
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		CONSUMERS+=($SUPPLIER)
13962306a36Sopenharmony_ci		OUT_LIST+=(${CON} ${SUPPLIER})
14062306a36Sopenharmony_ci		RET=0
14162306a36Sopenharmony_ci	done
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	return $RET
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cifunction detail_compat() {
14762306a36Sopenharmony_ci	f=$1/of_node/compatible
14862306a36Sopenharmony_ci	if [ -e $f ]
14962306a36Sopenharmony_ci	then
15062306a36Sopenharmony_ci		echo -n $(cat $f)
15162306a36Sopenharmony_ci	else
15262306a36Sopenharmony_ci		echo -n $1
15362306a36Sopenharmony_ci	fi
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cifunction detail_module() {
15762306a36Sopenharmony_ci	f=$1/driver/module
15862306a36Sopenharmony_ci	if [ -e $f ]
15962306a36Sopenharmony_ci	then
16062306a36Sopenharmony_ci		echo -n $(basename $(realpath $f))
16162306a36Sopenharmony_ci	else
16262306a36Sopenharmony_ci		echo -n $1
16362306a36Sopenharmony_ci	fi
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cifunction detail_driver() {
16762306a36Sopenharmony_ci	f=$1/driver
16862306a36Sopenharmony_ci	if [ -e $f ]
16962306a36Sopenharmony_ci	then
17062306a36Sopenharmony_ci		echo -n $(basename $(realpath $f))
17162306a36Sopenharmony_ci	else
17262306a36Sopenharmony_ci		echo -n $1
17362306a36Sopenharmony_ci	fi
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cifunction detail_fwnode() {
17762306a36Sopenharmony_ci	f=$1/firmware_node
17862306a36Sopenharmony_ci	if [ ! -e $f ]
17962306a36Sopenharmony_ci	then
18062306a36Sopenharmony_ci		f=$1/of_node
18162306a36Sopenharmony_ci	fi
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if [ -e $f ]
18462306a36Sopenharmony_ci	then
18562306a36Sopenharmony_ci		echo -n $(realpath $f)
18662306a36Sopenharmony_ci	else
18762306a36Sopenharmony_ci		echo -n $1
18862306a36Sopenharmony_ci	fi
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cifunction detail_graphviz() {
19262306a36Sopenharmony_ci	if [ "$2" != "ROOT" ]
19362306a36Sopenharmony_ci	then
19462306a36Sopenharmony_ci		echo -n "\"$(basename $2)\"->\"$(basename $1)\""
19562306a36Sopenharmony_ci	else
19662306a36Sopenharmony_ci		echo -n "\"$(basename $1)\""
19762306a36Sopenharmony_ci	fi
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cifunction detail_tsort() {
20162306a36Sopenharmony_ci	echo -n "\"$2\" \"$1\""
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cifunction detail_device() { echo -n $1; }
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cialias detail=detail_device
20762306a36Sopenharmony_ciALLOW_NO_DRIVER=0
20862306a36Sopenharmony_ciALLOW_DEVLINKS=1
20962306a36Sopenharmony_ciALLOW_PARENTS=1
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ciwhile [ $# -gt 0 ]
21262306a36Sopenharmony_cido
21362306a36Sopenharmony_ci	ARG=$1
21462306a36Sopenharmony_ci	case $ARG in
21562306a36Sopenharmony_ci		--help)
21662306a36Sopenharmony_ci			help
21762306a36Sopenharmony_ci			exit 0
21862306a36Sopenharmony_ci			;;
21962306a36Sopenharmony_ci		-c)
22062306a36Sopenharmony_ci			alias detail=detail_compat
22162306a36Sopenharmony_ci			;;
22262306a36Sopenharmony_ci		-m)
22362306a36Sopenharmony_ci			alias detail=detail_module
22462306a36Sopenharmony_ci			;;
22562306a36Sopenharmony_ci		-d)
22662306a36Sopenharmony_ci			alias detail=detail_driver
22762306a36Sopenharmony_ci			;;
22862306a36Sopenharmony_ci		-f)
22962306a36Sopenharmony_ci			alias detail=detail_fwnode
23062306a36Sopenharmony_ci			;;
23162306a36Sopenharmony_ci		-g)
23262306a36Sopenharmony_ci			alias detail=detail_graphviz
23362306a36Sopenharmony_ci			;;
23462306a36Sopenharmony_ci		-t)
23562306a36Sopenharmony_ci			alias detail=detail_tsort
23662306a36Sopenharmony_ci			;;
23762306a36Sopenharmony_ci		--allow-no-driver)
23862306a36Sopenharmony_ci			ALLOW_NO_DRIVER=1
23962306a36Sopenharmony_ci			;;
24062306a36Sopenharmony_ci		--exclude-devlinks)
24162306a36Sopenharmony_ci			ALLOW_DEVLINKS=0
24262306a36Sopenharmony_ci			;;
24362306a36Sopenharmony_ci		--exclude-parents)
24462306a36Sopenharmony_ci			ALLOW_PARENTS=0
24562306a36Sopenharmony_ci			;;
24662306a36Sopenharmony_ci		*)
24762306a36Sopenharmony_ci			# Stop at the first argument that's not an option.
24862306a36Sopenharmony_ci			break
24962306a36Sopenharmony_ci			;;
25062306a36Sopenharmony_ci	esac
25162306a36Sopenharmony_ci	shift
25262306a36Sopenharmony_cidone
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cifunction detail_chosen() {
25562306a36Sopenharmony_ci	detail $1 $2
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ciif [ $# -eq 0 ]
25962306a36Sopenharmony_cithen
26062306a36Sopenharmony_ci	help
26162306a36Sopenharmony_ci	exit 1
26262306a36Sopenharmony_cifi
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ciCONSUMERS=($@)
26562306a36Sopenharmony_ciOUT_LIST=()
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci# Do a breadth first, non-recursive tracking of suppliers. The parent is also
26862306a36Sopenharmony_ci# considered a "supplier" as a device can't probe without its parent.
26962306a36Sopenharmony_cii=0
27062306a36Sopenharmony_ciwhile [ $i -lt ${#CONSUMERS[@]} ]
27162306a36Sopenharmony_cido
27262306a36Sopenharmony_ci	CONSUMER=$(realpath ${CONSUMERS[$i]})
27362306a36Sopenharmony_ci	i=$(($i+1))
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	if already_seen ${CONSUMER}
27662306a36Sopenharmony_ci	then
27762306a36Sopenharmony_ci		continue
27862306a36Sopenharmony_ci	fi
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	# If this is not a device with a driver, we don't care about its
28162306a36Sopenharmony_ci	# suppliers.
28262306a36Sopenharmony_ci	if [ ! -e ${CONSUMER}/driver -a ${ALLOW_NO_DRIVER} -eq 0 ]
28362306a36Sopenharmony_ci	then
28462306a36Sopenharmony_ci		continue
28562306a36Sopenharmony_ci	fi
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	ROOT=1
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	# Add suppliers to CONSUMERS list and output the consumer details.
29062306a36Sopenharmony_ci	#
29162306a36Sopenharmony_ci	# We don't need to worry about a cycle in the dependency chain causing
29262306a36Sopenharmony_ci	# infinite loops. That's because the kernel doesn't allow cycles in
29362306a36Sopenharmony_ci	# device links unless it's a sync_state_only device link. And we ignore
29462306a36Sopenharmony_ci	# sync_state_only device links inside add_suppliers.
29562306a36Sopenharmony_ci	if add_suppliers ${CONSUMER}
29662306a36Sopenharmony_ci	then
29762306a36Sopenharmony_ci		ROOT=0
29862306a36Sopenharmony_ci	fi
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	if add_parent ${CONSUMER}
30162306a36Sopenharmony_ci	then
30262306a36Sopenharmony_ci		ROOT=0
30362306a36Sopenharmony_ci	fi
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	if [ $ROOT -eq 1 ]
30662306a36Sopenharmony_ci	then
30762306a36Sopenharmony_ci		OUT_LIST+=(${CONSUMER} "ROOT")
30862306a36Sopenharmony_ci	fi
30962306a36Sopenharmony_cidone
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci# Can NOT combine sort and uniq using sort -suk2 because stable sort in toybox
31262306a36Sopenharmony_ci# isn't really stable.
31362306a36Sopenharmony_cidev_to_detail | sort -k2 -k1 | uniq -f 1 | sort | cut -f2-
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ciexit 0
316