| #! /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 |