xref: /spdk/scripts/common.sh (revision 0098e636761237b77c12c30c2408263a5d2260cc)
1# Common shell utility functions
2
3# Check if PCI device is in PCI_ALLOWED and not in PCI_BLOCKED
4# Env:
5# if PCI_ALLOWED is empty assume device is allowed
6# if PCI_BLOCKED is empty assume device is NOT blocked
7# Params:
8# $1 - PCI BDF
9function pci_can_use() {
10	local i
11
12	# The '\ ' part is important
13	if [[ " $PCI_BLOCKED " =~ \ $1\  ]]; then
14		return 1
15	fi
16
17	if [[ -z "$PCI_ALLOWED" ]]; then
18		#no allow list specified, bind all devices
19		return 0
20	fi
21
22	for i in $PCI_ALLOWED; do
23		if [ "$i" == "$1" ]; then
24			return 0
25		fi
26	done
27
28	return 1
29}
30
31resolve_mod() {
32	local mod=$1 aliases=()
33
34	if aliases=($(modprobe -R "$mod")); then
35		echo "${aliases[0]}"
36	else
37		echo "unknown"
38	fi 2> /dev/null
39}
40
41cache_pci_init() {
42	local -gA pci_bus_cache
43	local -gA pci_ids_vendor
44	local -gA pci_ids_device
45	local -gA pci_bus_driver
46	local -gA pci_mod_driver
47	local -gA pci_mod_resolved
48
49	[[ -z ${pci_bus_cache[*]} || $CMD == reset ]] || return 1
50
51	pci_bus_cache=()
52	pci_bus_ids_vendor=()
53	pci_bus_ids_device=()
54	pci_bus_driver=()
55	pci_mod_driver=()
56	pci_mod_resolved=()
57}
58
59cache_pci() {
60	local pci=$1 class=$2 vendor=$3 device=$4 driver=$5 mod=$6
61
62	if [[ -n $class ]]; then
63		class=0x${class/0x/}
64		pci_bus_cache["$class"]="${pci_bus_cache["$class"]:+${pci_bus_cache["$class"]} }$pci"
65	fi
66	if [[ -n $vendor && -n $device ]]; then
67		vendor=0x${vendor/0x/} device=0x${device/0x/}
68		pci_bus_cache["$vendor:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci"
69
70		pci_ids_vendor["$pci"]=$vendor
71		pci_ids_device["$pci"]=$device
72	fi
73	if [[ -n $driver ]]; then
74		pci_bus_driver["$pci"]=$driver
75	fi
76	if [[ -n $mod ]]; then
77		pci_mod_driver["$pci"]=$mod
78		pci_mod_resolved["$pci"]=$(resolve_mod "$mod")
79	fi
80}
81
82cache_pci_bus_sysfs() {
83	[[ -e /sys/bus/pci/devices ]] || return 1
84
85	cache_pci_init || return 0
86
87	local pci
88	local class vendor device driver mod
89
90	for pci in /sys/bus/pci/devices/*; do
91		class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device") driver="" mod=""
92		if [[ -e $pci/driver ]]; then
93			driver=$(readlink -f "$pci/driver")
94			driver=${driver##*/}
95		else
96			driver=unbound
97		fi
98		if [[ -e $pci/modalias ]]; then
99			mod=$(< "$pci/modalias")
100		fi
101		cache_pci "${pci##*/}" "$class" "$vendor" "$device" "$driver" "$mod"
102	done
103}
104
105cache_pci_bus_lspci() {
106	hash lspci 2> /dev/null || return 1
107
108	cache_pci_init || return 0
109
110	local dev
111	while read -ra dev; do
112		dev=("${dev[@]//\"/}")
113		# lspci splits ls byte of the class (prog. interface) into a separate
114		# field if it's != 0. Look for it and normalize the value to fit with
115		# what kernel exposes under sysfs.
116		if [[ ${dev[*]} =~ -p([0-9]+) ]]; then
117			dev[1]+=${BASH_REMATCH[1]}
118		else
119			dev[1]+=00
120		fi
121		# pci class vendor device
122		cache_pci "${dev[@]::4}"
123	done < <(lspci -Dnmm)
124}
125
126cache_pci_bus_pciconf() {
127	hash pciconf 2> /dev/null || return 1
128
129	cache_pci_init || return 0
130
131	local class vendor device
132	local pci pci_info
133	local chip driver
134
135	while read -r pci pci_info; do
136		driver=${pci%@*}
137		pci=${pci##*pci} pci=${pci%:}
138		source <(echo "$pci_info")
139		# pciconf under FreeBSD 13.1 provides vendor and device IDs in its
140		# output under separate, dedicated fields. For 12.x they need to
141		# be extracted from the chip field.
142		if [[ -n $chip ]]; then
143			vendor=$(printf '0x%04x' $((chip & 0xffff)))
144			device=$(printf '0x%04x' $(((chip >> 16) & 0xffff)))
145		fi
146		cache_pci "$pci" "$class" "$vendor" "$device" "$driver"
147	done < <(pciconf -l)
148}
149
150cache_pci_bus() {
151	case "$(uname -s)" in
152		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
153		FreeBSD) cache_pci_bus_pciconf ;;
154	esac
155}
156
157iter_all_pci_sysfs() {
158	cache_pci_bus_sysfs || return 1
159
160	# default to class of the nvme devices
161	local find=${1:-0x010802} findx=$2
162	local pci pcis
163
164	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
165	read -ra pcis <<< "${pci_bus_cache["$find"]}"
166
167	if ((findx)); then
168		printf '%s\n' "${pcis[@]::findx}"
169	else
170		printf '%s\n' "${pcis[@]}"
171	fi
172}
173
174# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
175function iter_all_pci_class_code() {
176	local class
177	local subclass
178	local progif
179	class="$(printf %02x $((0x$1)))"
180	subclass="$(printf %02x $((0x$2)))"
181	progif="$(printf %02x $((0x$3)))"
182
183	if hash lspci &> /dev/null; then
184		if [ "$progif" != "00" ]; then
185			lspci -mm -n -D \
186				| grep -i -- "-p${progif}" \
187				| awk -v cc="\"${class}${subclass}\"" -F " " \
188					'{if (cc ~ $2) print $1}' | tr -d '"'
189		else
190			lspci -mm -n -D \
191				| awk -v cc="\"${class}${subclass}\"" -F " " \
192					'{if (cc ~ $2) print $1}' | tr -d '"'
193		fi
194	elif hash pciconf &> /dev/null; then
195		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
196			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
197		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
198	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
199		:
200	else
201		echo "Missing PCI enumeration utility" >&2
202		exit 1
203	fi
204}
205
206# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
207function iter_all_pci_dev_id() {
208	local ven_id
209	local dev_id
210	ven_id="$(printf %04x $((0x$1)))"
211	dev_id="$(printf %04x $((0x$2)))"
212
213	if hash lspci &> /dev/null; then
214		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
215			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
216	elif hash pciconf &> /dev/null; then
217		local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \
218			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
219		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
220	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
221		:
222	else
223		echo "Missing PCI enumeration utility" >&2
224		exit 1
225	fi
226}
227
228function iter_pci_dev_id() {
229	local bdf=""
230
231	for bdf in $(iter_all_pci_dev_id "$@"); do
232		if pci_can_use "$bdf"; then
233			echo "$bdf"
234		fi
235	done
236}
237
238# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED
239# See function pci_can_use()
240function iter_pci_class_code() {
241	local bdf=""
242
243	for bdf in $(iter_all_pci_class_code "$@"); do
244		if pci_can_use "$bdf"; then
245			echo "$bdf"
246		fi
247	done
248}
249
250function nvme_in_userspace() {
251	# Check used drivers. If it's not vfio-pci or uio-pci-generic
252	# then most likely PCI_ALLOWED option was used for setup.sh
253	# and we do not want to use that disk.
254
255	local bdf bdfs
256	local nvmes
257
258	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
259		nvmes=(${pci_bus_cache["0x010802"]})
260	else
261		nvmes=($(iter_pci_class_code 01 08 02))
262	fi
263
264	for bdf in "${nvmes[@]}"; do
265		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
266			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then
267			continue
268		fi
269		bdfs+=("$bdf")
270	done
271	((${#bdfs[@]})) || return 1
272	printf '%s\n' "${bdfs[@]}"
273}
274
275cmp_versions() {
276	local ver1 ver1_l
277	local ver2 ver2_l
278
279	IFS=".-:" read -ra ver1 <<< "$1"
280	IFS=".-:" read -ra ver2 <<< "$3"
281	local op=$2
282
283	ver1_l=${#ver1[@]}
284	ver2_l=${#ver2[@]}
285
286	local lt=0 gt=0 eq=0 v
287	case "$op" in
288		"<") : $((eq = gt = 1)) ;;
289		">") : $((eq = lt = 1)) ;;
290		"<=") : $((gt = 1)) ;;
291		">=") : $((lt = 1)) ;;
292		"==") : $((lt = gt = 1)) ;;
293	esac
294
295	decimal() (
296		local d=${1,,}
297		if [[ $d =~ ^[0-9]+$ ]]; then
298			echo $((10#$d))
299		elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then
300			d=${d/0x/}
301			echo $((0x$d))
302		else
303			echo 0
304		fi
305	)
306
307	for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do
308		ver1[v]=$(decimal "${ver1[v]}")
309		ver2[v]=$(decimal "${ver2[v]}")
310		((ver1[v] > ver2[v])) && return "$gt"
311		((ver1[v] < ver2[v])) && return "$lt"
312	done
313	[[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq"
314}
315
316lt() { cmp_versions "$1" "<" "$2"; }
317gt() { cmp_versions "$1" ">" "$2"; }
318le() { cmp_versions "$1" "<=" "$2"; }
319ge() { cmp_versions "$1" ">=" "$2"; }
320eq() { cmp_versions "$1" "==" "$2"; }
321neq() { ! eq "$1" "$2"; }
322
323block_in_use() {
324	local block=$1 data pt
325	# Skip devices that are in use - simple blkid it to see if
326	# there's any metadata (pt, fs, etc.) present on the drive.
327	# FIXME: Special case to ignore atari as a potential false
328	# positive:
329	# https://github.com/spdk/spdk/issues/2079
330	# Devices with SPDK's GPT part type are not considered to
331	# be in use.
332
333	if "$rootdir/scripts/spdk-gpt.py" "$block"; then
334		return 1
335	fi
336
337	data=$(blkid "/dev/${block##*/}") || data=none
338
339	if [[ $data == none ]]; then
340		return 1
341	fi
342
343	pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}") || pt=none
344
345	if [[ $pt == none || $pt == atari ]]; then
346		return 1
347	fi
348
349	# Devices used in SPDK tests always create GPT partitions
350	# with label containing SPDK_TEST string. Such devices were
351	# part of the tests before, so are not considered in use.
352	if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then
353		return 1
354	fi
355
356	return 0
357}
358
359get_spdk_gpt() {
360	local spdk_guid
361
362	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
363
364	IFS="()" read -r _ spdk_guid _ < <(grep SPDK_GPT_PART_TYPE_GUID "$rootdir/module/bdev/gpt/gpt.h")
365	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
366
367	echo "$spdk_guid"
368}
369
370if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then
371	# Adjust uname to report the operating system as WSL, Msys or Cygwin
372	# and the kernel name as Windows. Define kill() to invoke the SIGTERM
373	# handler before causing a hard stop with TerminateProcess.
374	source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh"
375fi
376