]>
Commit | Line | Data |
---|---|---|
51fae39b SK |
1 | #! /bin/sh |
2 | # SPDX-License-Identifier: GPL-2.0 | |
3 | # Copyright (c) 2020, Google LLC. All rights reserved. | |
4 | # Author: Saravana Kannan <[email protected]> | |
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 |