| #! /bin/sh | 
 | # SPDX-License-Identifier: GPL-2.0 | 
 | # Copyright (c) 2020, Google LLC. All rights reserved. | 
 | # Author: Saravana Kannan <saravanak@google.com> | 
 |  | 
 | function help() { | 
 | 	cat << EOF | 
 | Usage: $(basename $0) [-c|-d|-m|-f] [filter options] <list of devices> | 
 |  | 
 | This script needs to be run on the target device once it has booted to a | 
 | shell. | 
 |  | 
 | The script takes as input a list of one or more device directories under | 
 | /sys/devices and then lists the probe dependency chain (suppliers and | 
 | parents) of these devices. It does a breadth first search of the dependency | 
 | chain, so the last entry in the output is close to the root of the | 
 | dependency chain. | 
 |  | 
 | By default it lists the full path to the devices under /sys/devices. | 
 |  | 
 | It also takes an optional modifier flag as the first parameter to change | 
 | what information is listed in the output. If the requested information is | 
 | not available, the device name is printed. | 
 |  | 
 |   -c	lists the compatible string of the dependencies | 
 |   -d	lists the driver name of the dependencies that have probed | 
 |   -m	lists the module name of the dependencies that have a module | 
 |   -f	list the firmware node path of the dependencies | 
 |   -g	list the dependencies as edges and nodes for graphviz | 
 |   -t	list the dependencies as edges for tsort | 
 |  | 
 | The filter options provide a way to filter out some dependencies: | 
 |   --allow-no-driver	By default dependencies that don't have a driver | 
 | 			attached are ignored. This is to avoid following | 
 | 			device links to "class" devices that are created | 
 | 			when the consumer probes (as in, not a probe | 
 | 			dependency). If you want to follow these links | 
 | 			anyway, use this flag. | 
 |  | 
 |   --exclude-devlinks	Don't follow device links when tracking probe | 
 | 			dependencies. | 
 |  | 
 |   --exclude-parents	Don't follow parent devices when tracking probe | 
 | 			dependencies. | 
 |  | 
 | EOF | 
 | } | 
 |  | 
 | function dev_to_detail() { | 
 | 	local i=0 | 
 | 	while [ $i -lt ${#OUT_LIST[@]} ] | 
 | 	do | 
 | 		local C=${OUT_LIST[i]} | 
 | 		local S=${OUT_LIST[i+1]} | 
 | 		local D="'$(detail_chosen $C $S)'" | 
 | 		if [ ! -z "$D" ] | 
 | 		then | 
 | 			# This weirdness is needed to work with toybox when | 
 | 			# using the -t option. | 
 | 			printf '%05u\t%s\n' ${i} "$D" | tr -d \' | 
 | 		fi | 
 | 		i=$((i+2)) | 
 | 	done | 
 | } | 
 |  | 
 | function already_seen() { | 
 | 	local i=0 | 
 | 	while [ $i -lt ${#OUT_LIST[@]} ] | 
 | 	do | 
 | 		if [ "$1" = "${OUT_LIST[$i]}" ] | 
 | 		then | 
 | 			# if-statement treats 0 (no-error) as true | 
 | 			return 0 | 
 | 		fi | 
 | 		i=$(($i+2)) | 
 | 	done | 
 |  | 
 | 	# if-statement treats 1 (error) as false | 
 | 	return 1 | 
 | } | 
 |  | 
 | # Return 0 (no-error/true) if parent was added | 
 | function add_parent() { | 
 |  | 
 | 	if [ ${ALLOW_PARENTS} -eq 0 ] | 
 | 	then | 
 | 		return 1 | 
 | 	fi | 
 |  | 
 | 	local CON=$1 | 
 | 	# $CON could be a symlink path. So, we need to find the real path and | 
 | 	# then go up one level to find the real parent. | 
 | 	local PARENT=$(realpath $CON/..) | 
 |  | 
 | 	while [ ! -e ${PARENT}/driver ] | 
 | 	do | 
 | 		if [ "$PARENT" = "/sys/devices" ] | 
 | 		then | 
 | 			return 1 | 
 | 		fi | 
 | 		PARENT=$(realpath $PARENT/..) | 
 | 	done | 
 |  | 
 | 	CONSUMERS+=($PARENT) | 
 | 	OUT_LIST+=(${CON} ${PARENT}) | 
 | 	return 0 | 
 | } | 
 |  | 
 | # Return 0 (no-error/true) if one or more suppliers were added | 
 | function add_suppliers() { | 
 | 	local CON=$1 | 
 | 	local RET=1 | 
 |  | 
 | 	if [ ${ALLOW_DEVLINKS} -eq 0 ] | 
 | 	then | 
 | 		return 1 | 
 | 	fi | 
 |  | 
 | 	SUPPLIER_LINKS=$(ls -1d $CON/supplier:* 2>/dev/null) | 
 | 	for SL in $SUPPLIER_LINKS; | 
 | 	do | 
 | 		SYNC_STATE=$(cat $SL/sync_state_only) | 
 |  | 
 | 		# sync_state_only links are proxy dependencies. | 
 | 		# They can also have cycles. So, don't follow them. | 
 | 		if [ "$SYNC_STATE" != '0' ] | 
 | 		then | 
 | 			continue | 
 | 		fi | 
 |  | 
 | 		SUPPLIER=$(realpath $SL/supplier) | 
 |  | 
 | 		if [ ! -e $SUPPLIER/driver -a ${ALLOW_NO_DRIVER} -eq 0 ] | 
 | 		then | 
 | 			continue | 
 | 		fi | 
 |  | 
 | 		CONSUMERS+=($SUPPLIER) | 
 | 		OUT_LIST+=(${CON} ${SUPPLIER}) | 
 | 		RET=0 | 
 | 	done | 
 |  | 
 | 	return $RET | 
 | } | 
 |  | 
 | function detail_compat() { | 
 | 	f=$1/of_node/compatible | 
 | 	if [ -e $f ] | 
 | 	then | 
 | 		echo -n $(cat $f) | 
 | 	else | 
 | 		echo -n $1 | 
 | 	fi | 
 | } | 
 |  | 
 | function detail_module() { | 
 | 	f=$1/driver/module | 
 | 	if [ -e $f ] | 
 | 	then | 
 | 		echo -n $(basename $(realpath $f)) | 
 | 	else | 
 | 		echo -n $1 | 
 | 	fi | 
 | } | 
 |  | 
 | function detail_driver() { | 
 | 	f=$1/driver | 
 | 	if [ -e $f ] | 
 | 	then | 
 | 		echo -n $(basename $(realpath $f)) | 
 | 	else | 
 | 		echo -n $1 | 
 | 	fi | 
 | } | 
 |  | 
 | function detail_fwnode() { | 
 | 	f=$1/firmware_node | 
 | 	if [ ! -e $f ] | 
 | 	then | 
 | 		f=$1/of_node | 
 | 	fi | 
 |  | 
 | 	if [ -e $f ] | 
 | 	then | 
 | 		echo -n $(realpath $f) | 
 | 	else | 
 | 		echo -n $1 | 
 | 	fi | 
 | } | 
 |  | 
 | function detail_graphviz() { | 
 | 	if [ "$2" != "ROOT" ] | 
 | 	then | 
 | 		echo -n "\"$(basename $2)\"->\"$(basename $1)\"" | 
 | 	else | 
 | 		echo -n "\"$(basename $1)\"" | 
 | 	fi | 
 | } | 
 |  | 
 | function detail_tsort() { | 
 | 	echo -n "\"$2\" \"$1\"" | 
 | } | 
 |  | 
 | function detail_device() { echo -n $1; } | 
 |  | 
 | alias detail=detail_device | 
 | ALLOW_NO_DRIVER=0 | 
 | ALLOW_DEVLINKS=1 | 
 | ALLOW_PARENTS=1 | 
 |  | 
 | while [ $# -gt 0 ] | 
 | do | 
 | 	ARG=$1 | 
 | 	case $ARG in | 
 | 		--help) | 
 | 			help | 
 | 			exit 0 | 
 | 			;; | 
 | 		-c) | 
 | 			alias detail=detail_compat | 
 | 			;; | 
 | 		-m) | 
 | 			alias detail=detail_module | 
 | 			;; | 
 | 		-d) | 
 | 			alias detail=detail_driver | 
 | 			;; | 
 | 		-f) | 
 | 			alias detail=detail_fwnode | 
 | 			;; | 
 | 		-g) | 
 | 			alias detail=detail_graphviz | 
 | 			;; | 
 | 		-t) | 
 | 			alias detail=detail_tsort | 
 | 			;; | 
 | 		--allow-no-driver) | 
 | 			ALLOW_NO_DRIVER=1 | 
 | 			;; | 
 | 		--exclude-devlinks) | 
 | 			ALLOW_DEVLINKS=0 | 
 | 			;; | 
 | 		--exclude-parents) | 
 | 			ALLOW_PARENTS=0 | 
 | 			;; | 
 | 		*) | 
 | 			# Stop at the first argument that's not an option. | 
 | 			break | 
 | 			;; | 
 | 	esac | 
 | 	shift | 
 | done | 
 |  | 
 | function detail_chosen() { | 
 | 	detail $1 $2 | 
 | } | 
 |  | 
 | if [ $# -eq 0 ] | 
 | then | 
 | 	help | 
 | 	exit 1 | 
 | fi | 
 |  | 
 | CONSUMERS=($@) | 
 | OUT_LIST=() | 
 |  | 
 | # Do a breadth first, non-recursive tracking of suppliers. The parent is also | 
 | # considered a "supplier" as a device can't probe without its parent. | 
 | i=0 | 
 | while [ $i -lt ${#CONSUMERS[@]} ] | 
 | do | 
 | 	CONSUMER=$(realpath ${CONSUMERS[$i]}) | 
 | 	i=$(($i+1)) | 
 |  | 
 | 	if already_seen ${CONSUMER} | 
 | 	then | 
 | 		continue | 
 | 	fi | 
 |  | 
 | 	# If this is not a device with a driver, we don't care about its | 
 | 	# suppliers. | 
 | 	if [ ! -e ${CONSUMER}/driver -a ${ALLOW_NO_DRIVER} -eq 0 ] | 
 | 	then | 
 | 		continue | 
 | 	fi | 
 |  | 
 | 	ROOT=1 | 
 |  | 
 | 	# Add suppliers to CONSUMERS list and output the consumer details. | 
 | 	# | 
 | 	# We don't need to worry about a cycle in the dependency chain causing | 
 | 	# infinite loops. That's because the kernel doesn't allow cycles in | 
 | 	# device links unless it's a sync_state_only device link. And we ignore | 
 | 	# sync_state_only device links inside add_suppliers. | 
 | 	if add_suppliers ${CONSUMER} | 
 | 	then | 
 | 		ROOT=0 | 
 | 	fi | 
 |  | 
 | 	if add_parent ${CONSUMER} | 
 | 	then | 
 | 		ROOT=0 | 
 | 	fi | 
 |  | 
 | 	if [ $ROOT -eq 1 ] | 
 | 	then | 
 | 		OUT_LIST+=(${CONSUMER} "ROOT") | 
 | 	fi | 
 | done | 
 |  | 
 | # Can NOT combine sort and uniq using sort -suk2 because stable sort in toybox | 
 | # isn't really stable. | 
 | dev_to_detail | sort -k2 -k1 | uniq -f 1 | sort | cut -f2- | 
 |  | 
 | exit 0 |