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