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