xref: /spdk/scripts/common/setup/interactive.sh (revision 0070858e33ec0937e93e68b06a7f170b02a352b6)
1 #!/usr/bin/env bash
2 #  SPDX-License-Identifier: BSD-3-Clause
3 #  Copyright (C) 2023 Intel Corporation
4 #  All rights reserved.
5 #
6 set +e
7 
8 yn() {
9 	local -A yn=()
10 	local _yn
11 
12 	yn["y"]=0 yn["n"]=1
13 
14 	while read -rp "$* (y|N)> " _yn || true; do
15 		_yn=${_yn::1} _yn=${_yn,,} _yn=${_yn:-n}
16 		[[ -n ${yn["$_yn"]} ]] && return "${yn["$_yn"]}"
17 	done
18 }
19 
20 elevate() {
21 	((UID != 0)) || return 0
22 
23 	if yn "You ($UID) need to be root to commit any changes. Elevate privileges?"; then
24 		exec sudo -E "$rootdir/scripts/setup.sh" interactive "$@"
25 	fi
26 }
27 
28 stdin() {
29 	[[ ! -t 0 ]] || return 0
30 
31 	echo "Requested interactive mode but stdin is not attached to a terminal, bailing" >&2
32 	return 1
33 }
34 
35 main_menu() {
36 	local type=all answer quick_mode=$1
37 
38 	stdin || return 1
39 	elevate "$quick_mode"
40 
41 	case "${quick_mode,,}" in
42 		config | reset)
43 			editor odevices quick && mode=$quick_mode
44 			return
45 			;;
46 	esac
47 
48 	while ((1)); do
49 		cat <<- MENU
50 
51 			1) List PCI Devices [Currently Listing: "$type"]
52 			2) Change Devices To List
53 			3) Mark Device As Blocked (${PCI_BLOCKED:-none})
54 			4) Mark Device As Allowed (${PCI_ALLOWED:-none})
55 			5) Override Device In-Use Status
56 			$([[ $os == Linux ]] && echo "6) Bind Device")
57 
58 			c) configure
59 			s) status
60 			r) reset
61 			$([[ $os == Linux ]] && echo "hp) hugepages")
62 
63 			Q) Quit
64 			U) Update Devices View
65 
66 		MENU
67 
68 		read -rp "> " answer || answer=q
69 
70 		case "${answer,,}" in
71 			1) pdevices ;;
72 			2) ctype && pdevices ;;
73 			3) fdevices 0 ;;
74 			4) fdevices 1 ;;
75 			5) odevices ;;
76 			5e) editor odevices ;;
77 			6) bdevices ;;&
78 			q) yn "Are you sure you want to quit?" && return 1 ;;
79 			c | commit | config)
80 				yn "Are you sure you want jump to config mode?" || continue
81 				mode=config
82 				return
83 				;;
84 			hp) hugepages ;;
85 			s | status) status ;;
86 			r | reset)
87 				yn "Are you sure you want jump to reset mode?" || continue
88 				mode=reset
89 				return
90 				;;
91 			6 | u | update) update_status ;;
92 		esac
93 	done
94 }
95 
96 gdevices() {
97 	if [[ $type == all ]]; then
98 		local -gn dev_ref=all_devices_d
99 	else
100 		local -gn dev_ref=${type}_d
101 	fi
102 }
103 
104 pdevices() {
105 	gdevices
106 
107 	local set_marker=$1
108 	local use_map=()
109 	local -A markers=()
110 
111 	use_map[0]="not used" use_map[1]="used"
112 	markers["not used"]=pick markers["used"]=skip
113 
114 	if ((${#dev_ref[@]} == 0)); then
115 		echo "No devices found"
116 	else
117 		for dev in "${!dev_ref[@]}"; do
118 			printf '%s- %s [%s, %s] (%s@%s:%s)%s\n' \
119 				"${set_marker:+${markers["${use_map[all_devices_d["$dev"]]}"]} }" \
120 				"$dev" "${use_map[all_devices_d["$dev"]]}" "${pci_bus_driver["$dev"]:-none}" \
121 				"${all_devices_type_d["$dev"]}" \
122 				"${pci_ids_vendor["$dev"]}" \
123 				"${pci_ids_device["$dev"]}" \
124 				"${nvme_vmd_d["$dev"]:+"@(VMD -> ${nvme_vmd_d["$dev"]})"}"
125 		done
126 	fi
127 }
128 
129 ctype() {
130 	local type_to_set
131 	local -n types_ref=types_d
132 
133 	while read -rp "(${!types_ref[*]} all)> " type_to_set; do
134 		type_to_set=${type_to_set,,}
135 		if [[ -z $type_to_set ]]; then
136 			return
137 		elif [[ -n ${types_ref["$type_to_set"]} || $type_to_set == all ]]; then
138 			type=$type_to_set
139 			return
140 		fi
141 	done
142 }
143 
144 fdevices() {
145 	local action=${1:-0} bdf
146 	local am=()
147 	local -gA action_0 action_1
148 
149 	am[0]=PCI_BLOCKED am[1]=PCI_ALLOWED
150 
151 	gdevices
152 	local -n action_ref=action_${action}
153 	local -n action_ref_rev=action_$((!action))
154 
155 	while read -rp "(${!am[action]:-BDF})> " bdf; do
156 		bdf=${bdf,,}
157 		if [[ -z $bdf ]]; then
158 			return
159 		elif [[ -n ${dev_ref["$bdf"]} ]]; then
160 			if [[ -n ${action_ref["$bdf"]} ]]; then
161 				unset -v "action_ref[$bdf]"
162 			else
163 				action_ref["$bdf"]=1
164 				unset -v "action_ref_rev[$bdf]"
165 			fi
166 			eval "${am[action]}='${!action_ref[*]}'"
167 			eval "${am[!action]}='${!action_ref_rev[*]}'"
168 		elif [[ -z ${dev_ref["$bdf"]} ]]; then
169 			unset -v "action_ref[$bdf]"
170 			eval "${am[action]}='${!action_ref[*]}'"
171 		fi
172 	done
173 }
174 
175 editor() {
176 	local devs_list=() devs_picked=() devs_skipped=()
177 	local editor=${VISUAL:-${EDITOR:-vim}}
178 	local tmp_file
179 
180 	type -P "$editor" > /dev/null || return
181 
182 	mapfile -t devs_list < <(pdevices markers)
183 
184 	tmp_file=$(mktemp -u)
185 	cat <<- ODEVICES > "$tmp_file" || return
186 		# Listing '$type' devices
187 		#   Devices marked as "used" (i.e. contains any data) will be skipped by default
188 		#   Devices marked as "not used" (i.e. does not contain data) will be picked by default
189 
190 		$(printf '%s\n' "${devs_list[@]}")
191 
192 		# p, pick devices
193 		# s, skip devices
194 
195 		$([[ $editor == vi?(m) ]] && echo "# :cq[!] to not save any changes")
196 	ODEVICES
197 
198 	"$editor" "$tmp_file" || return
199 	[[ -s $tmp_file ]] || return
200 
201 	local action dev _type
202 	while read -r action _ dev _type; do
203 		case "${action,,}" in
204 			s | skip)
205 				[[ $_type != *"not used"* ]] && continue
206 				devs_skipped+=("$dev")
207 				;;
208 			p | pick)
209 				[[ $_type == *"not used"* ]] && continue
210 				devs_picked+=("$dev")
211 				;;
212 		esac
213 	done < "$tmp_file"
214 	rm "$tmp_file"
215 
216 	if [[ $2 == quick ]] && ((${#devs_picked[@]} > 0)); then
217 		if ! yn "Detected data on some of the devices (${devs_picked[*]}). Continue?"; then
218 			return 1
219 		fi
220 	fi
221 
222 	"$1" < <(printf '%s\n' "${devs_skipped[@]}" "${devs_picked[@]}")
223 
224 }
225 
226 odevices() {
227 	local bdf
228 
229 	type=all gdevices
230 
231 	while read -rp "(BDF)> " bdf; do
232 		bdf=${bdf,,}
233 		if [[ -z $bdf ]]; then
234 			return
235 		elif [[ -n ${dev_ref["$bdf"]} ]]; then
236 			dev_ref["$bdf"]=$((!dev_ref["$bdf"]))
237 		fi
238 	done
239 }
240 
241 bdevices() {
242 	[[ $os == Linux ]] || return 0
243 
244 	local bdf driver
245 
246 	gdevices
247 
248 	while read -rp "(BDF)> " bdf; do
249 		bdf=${bdf,,}
250 		if [[ -z $bdf ]]; then
251 			return
252 		fi
253 
254 		[[ -n ${dev_ref["$bdf"]} ]] || continue
255 
256 		pdriver "$bdf"
257 
258 		while read -rp "Select driver ($bdf)> " driver; do
259 			driver=${driver,,}
260 			if [[ -z $driver ]]; then
261 				continue 2
262 			fi
263 			if [[ $driver == "${pci_bus_driver["$bdf"]}" ]]; then
264 				echo "$bdf already bound to $driver"
265 				continue
266 			fi
267 			break
268 		done
269 
270 		# Try to be nice and silently attempt to load the driver just in case
271 		modprobe -q "$driver" || true
272 
273 		if yn "$bdf currently bound to ${pci_bus_driver["$bdf"]:-none}. Bind to $driver?"; then
274 			linux_bind_driver "$bdf" "$driver"
275 			return
276 		fi
277 	done
278 }
279 
280 pdriver() {
281 	local bdf=$1
282 
283 	cat <<- DRIVER
284 
285 		$bdf:
286 		  main driver: $(collect_driver "$bdf")
287 		  current driver: ${pci_bus_driver["$bdf"]:-none}
288 
289 	DRIVER
290 }
291 
292 status() {
293 	local _os=${os,,}
294 
295 	if [[ $(type -t "status_${_os}") == function ]]; then
296 		"status_${_os}"
297 	fi 2> /dev/null
298 }
299 
300 hugepages() {
301 	[[ $os == Linux ]] || return 0
302 	local hp
303 
304 	while read -rp "('clear' 'even' 'commit' HUGEMEM[=$HUGEMEM MB])> " hp; do
305 		hp=${hp,,}
306 		if [[ -z $hp ]]; then
307 			return
308 		elif [[ $hp == clear ]]; then
309 			clear_hugepages
310 			return
311 		elif [[ $hp =~ ^[1-9][0-9]*$ ]]; then
312 			NRHUGE=""
313 			HUGEMEM=$hp
314 		elif [[ $hp == commit ]]; then
315 			set_hp
316 			configure_linux_hugepages
317 			return
318 		fi
319 	done
320 }
321 
322 update_status() {
323 	CMD=reset cache_pci_bus
324 	collect_devices
325 }
326