Saravana Kannan | 51fae39 | 2020-09-01 15:48:42 -0700 | [diff] [blame] | 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 | |
| 6 | function help() { |
| 7 | cat << EOF |
| 8 | Usage: $(basename $0) [-c|-d|-m|-f] [filter options] <list of devices> |
| 9 | |
| 10 | This script needs to be run on the target device once it has booted to a |
| 11 | shell. |
| 12 | |
| 13 | The 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 |
| 15 | parents) of these devices. It does a breadth first search of the dependency |
| 16 | chain, so the last entry in the output is close to the root of the |
| 17 | dependency chain. |
| 18 | |
| 19 | By default it lists the full path to the devices under /sys/devices. |
| 20 | |
| 21 | It also takes an optional modifier flag as the first parameter to change |
| 22 | what information is listed in the output. If the requested information is |
| 23 | not 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 | |
| 32 | The 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 | |
| 46 | EOF |
| 47 | } |
| 48 | |
| 49 | function 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 | |
| 66 | function 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 |
| 83 | function 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 |
| 110 | function 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 | |
| 146 | function 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 | |
| 156 | function 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 | |
| 166 | function 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 | |
| 176 | function 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 | |
| 191 | function 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 | |
| 200 | function detail_tsort() { |
| 201 | echo -n "\"$2\" \"$1\"" |
| 202 | } |
| 203 | |
| 204 | function detail_device() { echo -n $1; } |
| 205 | |
| 206 | alias detail=detail_device |
| 207 | ALLOW_NO_DRIVER=0 |
| 208 | ALLOW_DEVLINKS=1 |
| 209 | ALLOW_PARENTS=1 |
| 210 | |
| 211 | while [ $# -gt 0 ] |
| 212 | do |
| 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 |
| 252 | done |
| 253 | |
| 254 | function detail_chosen() { |
| 255 | detail $1 $2 |
| 256 | } |
| 257 | |
| 258 | if [ $# -eq 0 ] |
| 259 | then |
| 260 | help |
| 261 | exit 1 |
| 262 | fi |
| 263 | |
| 264 | CONSUMERS=($@) |
| 265 | OUT_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. |
| 269 | i=0 |
| 270 | while [ $i -lt ${#CONSUMERS[@]} ] |
| 271 | do |
| 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 |
| 309 | done |
| 310 | |
| 311 | # Can NOT combine sort and uniq using sort -suk2 because stable sort in toybox |
| 312 | # isn't really stable. |
| 313 | dev_to_detail | sort -k2 -k1 | uniq -f 1 | sort | cut -f2- |
| 314 | |
| 315 | exit 0 |