316 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			316 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #! /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
 |