xref: /spdk/scripts/common/setup/interactive.sh (revision bf30e09abe1667ae2769aa367cde39c550bcac00)
1#!/usr/bin/env bash
2#  SPDX-License-Identifier: BSD-3-Clause
3#  Copyright (C) 2023 Intel Corporation
4#  All rights reserved.
5#
6set +e
7
8yn() {
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
20elevate() {
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
28stdin() {
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
35main_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
96gdevices() {
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
104pdevices() {
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
129ctype() {
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
144fdevices() {
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
175editor() {
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
226odevices() {
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
241bdevices() {
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
280pdriver() {
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
292status() {
293	local _os=${os,,}
294
295	if [[ $(type -t "status_${_os}") == function ]]; then
296		"status_${_os}"
297	fi 2> /dev/null
298}
299
300hugepages() {
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
322update_status() {
323	CMD=reset cache_pci_bus
324	collect_devices
325}
326