xref: /spdk/scripts/common/setup/interactive.sh (revision 2d65fd75787eb0bb41e61d598e380b026090c148)
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			s | status) status ;;
85			r | reset)
86				yn "Are you sure you want jump to reset mode?" || continue
87				mode=reset
88				return
89				;;
90			u | update)
91				CMD=reset cache_pci_bus
92				collect_devices
93				;;
94			hp) hugepages ;;
95		esac
96	done
97}
98
99gdevices() {
100	if [[ $type == all ]]; then
101		local -gn dev_ref=all_devices_d
102	else
103		local -gn dev_ref=${type}_d
104	fi
105}
106
107pdevices() {
108	gdevices
109
110	local set_marker=$1
111	local use_map=()
112	local -A markers=()
113
114	use_map[0]="not used" use_map[1]="used"
115	markers["not used"]=pick markers["used"]=skip
116
117	if ((${#dev_ref[@]} == 0)); then
118		echo "No devices found"
119	else
120		for dev in "${!dev_ref[@]}"; do
121			printf '%s- %s [%s, %s] (%s@%s:%s)%s\n' \
122				"${set_marker:+${markers["${use_map[all_devices_d["$dev"]]}"]} }" \
123				"$dev" "${use_map[all_devices_d["$dev"]]}" "${pci_bus_driver["$dev"]:-none}" \
124				"${all_devices_type_d["$dev"]}" \
125				"${pci_ids_vendor["$dev"]}" \
126				"${pci_ids_device["$dev"]}" \
127				"${nvme_vmd_d["$dev"]:+"@(VMD -> ${nvme_vmd_d["$dev"]})"}"
128		done
129	fi
130}
131
132ctype() {
133	local type_to_set
134	local -n types_ref=types_d
135
136	while read -rp "(${!types_ref[*]} all)> " type_to_set; do
137		type_to_set=${type_to_set,,}
138		if [[ -z $type_to_set ]]; then
139			return
140		elif [[ -n ${types_ref["$type_to_set"]} || $type_to_set == all ]]; then
141			type=$type_to_set
142			return
143		fi
144	done
145}
146
147fdevices() {
148	local action=${1:-0} bdf
149	local am=()
150	local -gA action_0 action_1
151
152	am[0]=PCI_BLOCKED am[1]=PCI_ALLOWED
153
154	gdevices
155	local -n action_ref=action_${action}
156	local -n action_ref_rev=action_$((!action))
157
158	while read -rp "(${!am[action]:-BDF})> " bdf; do
159		bdf=${bdf,,}
160		if [[ -z $bdf ]]; then
161			return
162		elif [[ -n ${dev_ref["$bdf"]} ]]; then
163			if [[ -n ${action_ref["$bdf"]} ]]; then
164				unset -v "action_ref[$bdf]"
165			else
166				action_ref["$bdf"]=1
167				unset -v "action_ref_rev[$bdf]"
168			fi
169			eval "${am[action]}='${!action_ref[*]}'"
170			eval "${am[!action]}='${!action_ref_rev[*]}'"
171		elif [[ -z ${dev_ref["$bdf"]} ]]; then
172			unset -v "action_ref[$bdf]"
173			eval "${am[action]}='${!action_ref[*]}'"
174		fi
175	done
176}
177
178editor() {
179	local devs_list=() devs_picked=() devs_skipped=()
180	local editor=${VISUAL:-${EDITOR:-vim}}
181	local tmp_file
182
183	type -P "$editor" > /dev/null || return
184
185	mapfile -t devs_list < <(pdevices markers)
186
187	tmp_file=$(mktemp -u)
188	cat <<- ODEVICES > "$tmp_file" || return
189		# Listing '$type' devices
190		#   Devices marked as "used" (i.e. contains any data) will be skipped by default
191		#   Devices marked as "not used" (i.e. does not contain data) will be picked by default
192
193		$(printf '%s\n' "${devs_list[@]}")
194
195		# p, pick devices
196		# s, skip devices
197
198		$([[ $editor == vi?(m) ]] && echo "# :cq[!] to not save any changes")
199	ODEVICES
200
201	"$editor" "$tmp_file" || return
202	[[ -s $tmp_file ]] || return
203
204	local action dev _type
205	while read -r action _ dev _type; do
206		case "${action,,}" in
207			s | skip)
208				[[ $_type != *"not used"* ]] && continue
209				devs_skipped+=("$dev")
210				;;
211			p | pick)
212				[[ $_type == *"not used"* ]] && continue
213				devs_picked+=("$dev")
214				;;
215		esac
216	done < "$tmp_file"
217	rm "$tmp_file"
218
219	if [[ $2 == quick ]] && ((${#devs_picked[@]} > 0)); then
220		if ! yn "Detected data on some of the devices (${devs_picked[*]}). Continue?"; then
221			return 1
222		fi
223	fi
224
225	"$1" < <(printf '%s\n' "${devs_skipped[@]}" "${devs_picked[@]}")
226
227}
228
229odevices() {
230	local bdf
231
232	type=all gdevices
233
234	while read -rp "(BDF)> " bdf; do
235		bdf=${bdf,,}
236		if [[ -z $bdf ]]; then
237			return
238		elif [[ -n ${dev_ref["$bdf"]} ]]; then
239			dev_ref["$bdf"]=$((!dev_ref["$bdf"]))
240		fi
241	done
242}
243
244bdevices() {
245	[[ $os == Linux ]] || return 0
246
247	local bdfd bdf driver
248
249	gdevices
250
251	while read -rp "(BDF->driver)> " bdfd; do
252		bdfd=${bdfd,,}
253		if [[ -z $bdfd ]]; then
254			return
255		fi
256
257		bdf=${bdfd/->*/} driver=${bdfd/*->/}
258
259		if [[ $driver == "${drivers_d["$bdf"]}" ]]; then
260			echo "$bdf already bound to $driver"
261			continue
262		fi
263
264		if [[ -n ${dev_ref["$bdf"]} && -n $driver ]]; then
265			if yn "$bdf currently bound to ${drivers_d["$bdf"]:-none}. Bind to $driver?"; then
266				linux_bind_driver "$bdf" "$driver"
267			fi
268		fi
269	done
270}
271
272status() {
273	local _os=${os,,}
274
275	if [[ $(type -t "status_${_os}") == function ]]; then
276		"status_${_os}"
277	fi
278}
279
280hugepages() {
281	[[ $os == Linux ]] || return 0
282	local hp
283
284	HUGE_EVEN_ALLOC=no
285	while read -rp "('clear' 'even' 'commit' HUGEMEM[=$HUGEMEM MB])> " hp; do
286		hp=${hp,,}
287		if [[ -z $hp ]]; then
288			return
289		elif [[ $hp == clear ]]; then
290			clear_hugepages
291			return
292		elif [[ $hp == even ]]; then
293			HUGE_EVEN_ALLOC=yes
294		elif [[ $hp =~ ^[1-9][0-9]*$ ]]; then
295			NRHUGE=""
296			HUGEMEM=$hp
297		elif [[ $hp == commit ]]; then
298			set_hp
299			configure_linux_hugepages
300			return
301		fi
302	done
303}
304