1#! /bin/sh 2# SPDX-License-Identifier: GPL-2.0 3# Copyright (c) 2020, Google LLC. All rights reserved. 4# Author: Saravana Kannan <saravanak@google.com> 5 6function help() { 7 cat << EOF 8Usage: $(basename $0) [-c|-d|-m|-f] [filter options] <list of devices> 9 10This script needs to be run on the target device once it has booted to a 11shell. 12 13The script takes as input a list of one or more device directories under 14/sys/devices and then lists the probe dependency chain (suppliers and 15parents) of these devices. It does a breadth first search of the dependency 16chain, so the last entry in the output is close to the root of the 17dependency chain. 18 19By default it lists the full path to the devices under /sys/devices. 20 21It also takes an optional modifier flag as the first parameter to change 22what information is listed in the output. If the requested information is 23not available, the device name is printed. 24 25 -c lists the compatible string of the dependencies 26 -d lists the driver name of the dependencies that have probed 27 -m lists the module name of the dependencies that have a module 28 -f list the firmware node path of the dependencies 29 -g list the dependencies as edges and nodes for graphviz 30 -t list the dependencies as edges for tsort 31 32The filter options provide a way to filter out some dependencies: 33 --allow-no-driver By default dependencies that don't have a driver 34 attached are ignored. This is to avoid following 35 device links to "class" devices that are created 36 when the consumer probes (as in, not a probe 37 dependency). If you want to follow these links 38 anyway, use this flag. 39 40 --exclude-devlinks Don't follow device links when tracking probe 41 dependencies. 42 43 --exclude-parents Don't follow parent devices when tracking probe 44 dependencies. 45 46EOF 47} 48 49function dev_to_detail() { 50 local i=0 51 while [ $i -lt ${#OUT_LIST[@]} ] 52 do 53 local C=${OUT_LIST[i]} 54 local S=${OUT_LIST[i+1]} 55 local D="'$(detail_chosen $C $S)'" 56 if [ ! -z "$D" ] 57 then 58 # This weirdness is needed to work with toybox when 59 # using the -t option. 60 printf '%05u\t%s\n' ${i} "$D" | tr -d \' 61 fi 62 i=$((i+2)) 63 done 64} 65 66function already_seen() { 67 local i=0 68 while [ $i -lt ${#OUT_LIST[@]} ] 69 do 70 if [ "$1" = "${OUT_LIST[$i]}" ] 71 then 72 # if-statement treats 0 (no-error) as true 73 return 0 74 fi 75 i=$(($i+2)) 76 done 77 78 # if-statement treats 1 (error) as false 79 return 1 80} 81 82# Return 0 (no-error/true) if parent was added 83function add_parent() { 84 85 if [ ${ALLOW_PARENTS} -eq 0 ] 86 then 87 return 1 88 fi 89 90 local CON=$1 91 # $CON could be a symlink path. So, we need to find the real path and 92 # then go up one level to find the real parent. 93 local PARENT=$(realpath $CON/..) 94 95 while [ ! -e ${PARENT}/driver ] 96 do 97 if [ "$PARENT" = "/sys/devices" ] 98 then 99 return 1 100 fi 101 PARENT=$(realpath $PARENT/..) 102 done 103 104 CONSUMERS+=($PARENT) 105 OUT_LIST+=(${CON} ${PARENT}) 106 return 0 107} 108 109# Return 0 (no-error/true) if one or more suppliers were added 110function add_suppliers() { 111 local CON=$1 112 local RET=1 113 114 if [ ${ALLOW_DEVLINKS} -eq 0 ] 115 then 116 return 1 117 fi 118 119 SUPPLIER_LINKS=$(ls -1d $CON/supplier:* 2>/dev/null) 120 for SL in $SUPPLIER_LINKS; 121 do 122 SYNC_STATE=$(cat $SL/sync_state_only) 123 124 # sync_state_only links are proxy dependencies. 125 # They can also have cycles. So, don't follow them. 126 if [ "$SYNC_STATE" != '0' ] 127 then 128 continue 129 fi 130 131 SUPPLIER=$(realpath $SL/supplier) 132 133 if [ ! -e $SUPPLIER/driver -a ${ALLOW_NO_DRIVER} -eq 0 ] 134 then 135 continue 136 fi 137 138 CONSUMERS+=($SUPPLIER) 139 OUT_LIST+=(${CON} ${SUPPLIER}) 140 RET=0 141 done 142 143 return $RET 144} 145 146function detail_compat() { 147 f=$1/of_node/compatible 148 if [ -e $f ] 149 then 150 echo -n $(cat $f) 151 else 152 echo -n $1 153 fi 154} 155 156function detail_module() { 157 f=$1/driver/module 158 if [ -e $f ] 159 then 160 echo -n $(basename $(realpath $f)) 161 else 162 echo -n $1 163 fi 164} 165 166function detail_driver() { 167 f=$1/driver 168 if [ -e $f ] 169 then 170 echo -n $(basename $(realpath $f)) 171 else 172 echo -n $1 173 fi 174} 175 176function detail_fwnode() { 177 f=$1/firmware_node 178 if [ ! -e $f ] 179 then 180 f=$1/of_node 181 fi 182 183 if [ -e $f ] 184 then 185 echo -n $(realpath $f) 186 else 187 echo -n $1 188 fi 189} 190 191function detail_graphviz() { 192 if [ "$2" != "ROOT" ] 193 then 194 echo -n "\"$(basename $2)\"->\"$(basename $1)\"" 195 else 196 echo -n "\"$(basename $1)\"" 197 fi 198} 199 200function detail_tsort() { 201 echo -n "\"$2\" \"$1\"" 202} 203 204function detail_device() { echo -n $1; } 205 206alias detail=detail_device 207ALLOW_NO_DRIVER=0 208ALLOW_DEVLINKS=1 209ALLOW_PARENTS=1 210 211while [ $# -gt 0 ] 212do 213 ARG=$1 214 case $ARG in 215 --help) 216 help 217 exit 0 218 ;; 219 -c) 220 alias detail=detail_compat 221 ;; 222 -m) 223 alias detail=detail_module 224 ;; 225 -d) 226 alias detail=detail_driver 227 ;; 228 -f) 229 alias detail=detail_fwnode 230 ;; 231 -g) 232 alias detail=detail_graphviz 233 ;; 234 -t) 235 alias detail=detail_tsort 236 ;; 237 --allow-no-driver) 238 ALLOW_NO_DRIVER=1 239 ;; 240 --exclude-devlinks) 241 ALLOW_DEVLINKS=0 242 ;; 243 --exclude-parents) 244 ALLOW_PARENTS=0 245 ;; 246 *) 247 # Stop at the first argument that's not an option. 248 break 249 ;; 250 esac 251 shift 252done 253 254function detail_chosen() { 255 detail $1 $2 256} 257 258if [ $# -eq 0 ] 259then 260 help 261 exit 1 262fi 263 264CONSUMERS=($@) 265OUT_LIST=() 266 267# Do a breadth first, non-recursive tracking of suppliers. The parent is also 268# considered a "supplier" as a device can't probe without its parent. 269i=0 270while [ $i -lt ${#CONSUMERS[@]} ] 271do 272 CONSUMER=$(realpath ${CONSUMERS[$i]}) 273 i=$(($i+1)) 274 275 if already_seen ${CONSUMER} 276 then 277 continue 278 fi 279 280 # If this is not a device with a driver, we don't care about its 281 # suppliers. 282 if [ ! -e ${CONSUMER}/driver -a ${ALLOW_NO_DRIVER} -eq 0 ] 283 then 284 continue 285 fi 286 287 ROOT=1 288 289 # Add suppliers to CONSUMERS list and output the consumer details. 290 # 291 # We don't need to worry about a cycle in the dependency chain causing 292 # infinite loops. That's because the kernel doesn't allow cycles in 293 # device links unless it's a sync_state_only device link. And we ignore 294 # sync_state_only device links inside add_suppliers. 295 if add_suppliers ${CONSUMER} 296 then 297 ROOT=0 298 fi 299 300 if add_parent ${CONSUMER} 301 then 302 ROOT=0 303 fi 304 305 if [ $ROOT -eq 1 ] 306 then 307 OUT_LIST+=(${CONSUMER} "ROOT") 308 fi 309done 310 311# Can NOT combine sort and uniq using sort -suk2 because stable sort in toybox 312# isn't really stable. 313dev_to_detail | sort -k2 -k1 | uniq -f 1 | sort | cut -f2- 314 315exit 0 316