xref: /spdk/scripts/common.sh (revision b6875e1ce57743f3b1416016b9c624d79a862af9)
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%:} # E.g.: nvme0@pci0:0:16:0: -> 0:16:0
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	fi
201	echo "$driver"
202}
203
204cache_pci_bus() {
205	case "$(uname -s)" in
206		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
207		FreeBSD) cache_pci_bus_pciconf ;;
208	esac
209}
210
211iter_all_pci_sysfs() {
212	cache_pci_bus_sysfs || return 1
213
214	# default to class of the nvme devices
215	local find=${1:-0x010802} findx=$2
216	local pci pcis
217
218	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
219	read -ra pcis <<< "${pci_bus_cache["$find"]}"
220
221	if ((findx)); then
222		printf '%s\n' "${pcis[@]::findx}"
223	else
224		printf '%s\n' "${pcis[@]}"
225	fi
226}
227
228# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
229function iter_all_pci_class_code() {
230	local class
231	local subclass
232	local progif
233	class="$(printf %02x $((0x$1)))"
234	subclass="$(printf %02x $((0x$2)))"
235	progif="$(printf %02x $((0x$3)))"
236
237	if hash lspci &> /dev/null; then
238		if [ "$progif" != "00" ]; then
239			lspci -mm -n -D \
240				| grep -i -- "-p${progif}" \
241				| awk -v cc="\"${class}${subclass}\"" -F " " \
242					'{if (cc ~ $2) print $1}' | tr -d '"'
243		else
244			lspci -mm -n -D \
245				| awk -v cc="\"${class}${subclass}\"" -F " " \
246					'{if (cc ~ $2) print $1}' | tr -d '"'
247		fi
248	elif hash pciconf &> /dev/null; then
249		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
250			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
251		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
252	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
253		:
254	else
255		echo "Missing PCI enumeration utility" >&2
256		exit 1
257	fi
258}
259
260# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
261function iter_all_pci_dev_id() {
262	local ven_id
263	local dev_id
264	ven_id="$(printf %04x $((0x$1)))"
265	dev_id="$(printf %04x $((0x$2)))"
266
267	if hash lspci &> /dev/null; then
268		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
269			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
270	elif hash pciconf &> /dev/null; then
271		local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \
272			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
273		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
274	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
275		:
276	else
277		echo "Missing PCI enumeration utility" >&2
278		exit 1
279	fi
280}
281
282function iter_pci_dev_id() {
283	local bdf=""
284
285	for bdf in $(iter_all_pci_dev_id "$@"); do
286		if pci_can_use "$bdf"; then
287			echo "$bdf"
288		fi
289	done
290}
291
292# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED
293# See function pci_can_use()
294function iter_pci_class_code() {
295	local bdf=""
296
297	for bdf in $(iter_all_pci_class_code "$@"); do
298		if pci_can_use "$bdf"; then
299			echo "$bdf"
300		fi
301	done
302}
303
304function nvme_in_userspace() {
305	# Check used drivers. If it's not vfio-pci or uio-pci-generic
306	# then most likely PCI_ALLOWED option was used for setup.sh
307	# and we do not want to use that disk.
308
309	local bdf bdfs
310	local nvmes
311
312	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
313		nvmes=(${pci_bus_cache["0x010802"]})
314	else
315		nvmes=($(iter_pci_class_code 01 08 02))
316	fi
317
318	for bdf in "${nvmes[@]}"; do
319		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
320			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then
321			continue
322		fi
323		bdfs+=("$bdf")
324	done
325	((${#bdfs[@]})) || return 1
326	printf '%s\n' "${bdfs[@]}"
327}
328
329cmp_versions() {
330	local ver1 ver1_l
331	local ver2 ver2_l
332
333	IFS=".-:" read -ra ver1 <<< "$1"
334	IFS=".-:" read -ra ver2 <<< "$3"
335	local op=$2
336
337	ver1_l=${#ver1[@]}
338	ver2_l=${#ver2[@]}
339
340	local lt=0 gt=0 eq=0 v
341	case "$op" in
342		"<") : $((eq = gt = 1)) ;;
343		">") : $((eq = lt = 1)) ;;
344		"<=") : $((gt = 1)) ;;
345		">=") : $((lt = 1)) ;;
346		"==") : $((lt = gt = 1)) ;;
347	esac
348
349	decimal() (
350		local d=${1,,}
351		if [[ $d =~ ^[0-9]+$ ]]; then
352			echo $((10#$d))
353		elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then
354			d=${d/0x/}
355			echo $((0x$d))
356		else
357			echo 0
358		fi
359	)
360
361	for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do
362		ver1[v]=$(decimal "${ver1[v]}")
363		ver2[v]=$(decimal "${ver2[v]}")
364		((ver1[v] > ver2[v])) && return "$gt"
365		((ver1[v] < ver2[v])) && return "$lt"
366	done
367	[[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq"
368}
369
370lt() { cmp_versions "$1" "<" "$2"; }
371gt() { cmp_versions "$1" ">" "$2"; }
372le() { cmp_versions "$1" "<=" "$2"; }
373ge() { cmp_versions "$1" ">=" "$2"; }
374eq() { cmp_versions "$1" "==" "$2"; }
375neq() { ! eq "$1" "$2"; }
376
377block_in_use() {
378	local block=$1 pt
379	# Skip devices that are in use - simple blkid it to see if
380	# there's any metadata (pt, fs, etc.) present on the drive.
381	# FIXME: Special case to ignore atari as a potential false
382	# positive:
383	# https://github.com/spdk/spdk/issues/2079
384	# Devices with SPDK's GPT part type are not considered to
385	# be in use.
386
387	if "$rootdir/scripts/spdk-gpt.py" "$block"; then
388		return 1
389	fi
390
391	if ! pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}"); then
392		return 1
393	elif [[ $pt == atari ]]; then
394		return 1
395	fi
396
397	# Devices used in SPDK tests always create GPT partitions
398	# with label containing SPDK_TEST string. Such devices were
399	# part of the tests before, so are not considered in use.
400	if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then
401		return 1
402	fi
403
404	return 0
405}
406
407get_spdk_gpt_old() {
408	local spdk_guid
409
410	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
411
412	GPT_H="$rootdir/module/bdev/gpt/gpt.h"
413	IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID_OLD "$GPT_H")
414	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
415
416	echo "$spdk_guid"
417}
418
419get_spdk_gpt() {
420	local spdk_guid
421
422	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
423
424	GPT_H="$rootdir/module/bdev/gpt/gpt.h"
425	IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID "$GPT_H")
426	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
427
428	echo "$spdk_guid"
429}
430
431map_supported_devices() {
432	local type=${1^^}
433	local ids dev_types dev_type dev_id bdf bdfs vmd _vmd
434
435	local -gA nvme_d
436	local -gA ioat_d dsa_d iaa_d
437	local -gA virtio_d
438	local -gA vmd_d nvme_vmd_d vmd_nvme_d vmd_nvme_count
439	local -gA all_devices_d types_d all_devices_type_d
440
441	ids+="PCI_DEVICE_ID_INTEL_IOAT" dev_types+="IOAT"
442	ids+="|PCI_DEVICE_ID_INTEL_DSA" dev_types+="|DSA"
443	ids+="|PCI_DEVICE_ID_INTEL_IAA" dev_types+="|IAA"
444	ids+="|PCI_DEVICE_ID_VIRTIO" dev_types+="|VIRTIO"
445	ids+="|PCI_DEVICE_ID_INTEL_VMD" dev_types+="|VMD"
446	ids+="|SPDK_PCI_CLASS_NVME" dev_types+="|NVME"
447
448	[[ -e $rootdir/include/spdk/pci_ids.h ]] || return 1
449
450	((${#pci_bus_cache[@]} == 0)) && cache_pci_bus
451
452	while read -r _ dev_type dev_id; do
453		[[ $dev_type == *$type* ]] || continue
454		bdfs=(${pci_bus_cache["0x8086:$dev_id"]})
455		[[ $dev_type == *NVME* ]] && bdfs=(${pci_bus_cache["$dev_id"]})
456		[[ $dev_type == *VIRT* ]] && bdfs=(${pci_bus_cache["0x1af4:$dev_id"]})
457		[[ $dev_type =~ ($dev_types) ]] && dev_type=${BASH_REMATCH[1],,}
458		types_d["$dev_type"]=1
459		for bdf in "${bdfs[@]}"; do
460			eval "${dev_type}_d[$bdf]=0"
461			all_devices_d["$bdf"]=0
462			all_devices_type_d["$bdf"]=$dev_type
463		done
464	done < <(grep -E "$ids" "$rootdir/include/spdk/pci_ids.h")
465
466	# Rebuild vmd refs from the very cratch to not have duplicates in case we were called
467	# multiple times.
468	unset -v "${!_vmd_@}"
469
470	for bdf in "${!nvme_d[@]}"; do
471		vmd=$(is_nvme_behind_vmd "$bdf") && _vmd=${vmd//[:.]/_} || continue
472		nvme_vmd_d["$bdf"]=$vmd
473		vmd_nvme_d["$vmd"]="_vmd_${_vmd}_nvmes[@]"
474		((++vmd_nvme_count["$vmd"]))
475		eval "_vmd_${_vmd}_nvmes+=($bdf)"
476	done
477}
478
479is_nvme_behind_vmd() {
480	local nvme_bdf=$1 dev_path
481
482	IFS="/" read -ra dev_path < <(readlink -f "/sys/bus/pci/devices/$nvme_bdf")
483
484	for dev in "${dev_path[@]}"; do
485		[[ -n $dev && -n ${vmd_d["$dev"]} ]] && echo $dev && return 0
486	done
487	return 1
488}
489
490is_nvme_iommu_shared_with_vmd() {
491	local nvme_bdf=$1 vmd
492
493	# This use-case is quite specific to vfio-pci|iommu setup
494	is_iommu_enabled || return 1
495
496	[[ -n ${nvme_vmd_d["$nvme_bdf"]} ]] || return 1
497	# nvme is behind VMD ...
498	((pci_iommu_groups["$nvme_bdf"] == pci_iommu_groups["${nvme_vmd_d["$nvme_bdf"]}"])) || return 1
499	# ... and it shares iommu_group with it
500}
501
502kmsg() {
503	((UID == 0)) || return 0
504	[[ -w /dev/kmsg && $(< /proc/sys/kernel/printk_devkmsg) == on ]] || return 0
505	echo "$*" > /dev/kmsg
506}
507
508if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then
509	# Adjust uname to report the operating system as WSL, Msys or Cygwin
510	# and the kernel name as Windows. Define kill() to invoke the SIGTERM
511	# handler before causing a hard stop with TerminateProcess.
512	source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh"
513fi
514
515# Make sure we have access to proper binaries installed in pkgdep/common.sh
516if [[ -e /etc/opt/spdk-pkgdep/paths/export.sh ]]; then
517	source /etc/opt/spdk-pkgdep/paths/export.sh
518fi > /dev/null
519