xref: /spdk/scripts/common.sh (revision cb7af50cfdf0f6fa3c3b3755969b8d386cbaca34)
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
14 
15 shopt -s extglob
16 
17 function pci_can_use() {
18 	local i
19 
20 	# The '\ ' part is important
21 	if [[ " $PCI_BLOCKED " =~ \ $1\  ]]; then
22 		return 1
23 	fi
24 
25 	if [[ -z "$PCI_ALLOWED" ]]; then
26 		#no allow list specified, bind all devices
27 		return 0
28 	fi
29 
30 	for i in $PCI_ALLOWED; do
31 		if [ "$i" == "$1" ]; then
32 			return 0
33 		fi
34 	done
35 
36 	return 1
37 }
38 
39 resolve_mod() {
40 	local mod=$1 aliases=()
41 
42 	if aliases=($(modprobe -R "$mod")); then
43 		echo "${aliases[0]}"
44 	else
45 		echo "unknown"
46 	fi 2> /dev/null
47 }
48 
49 cache_pci_init() {
50 	local -gA pci_bus_cache
51 	local -gA pci_ids_vendor
52 	local -gA pci_ids_device
53 	local -gA pci_bus_driver
54 	local -gA pci_mod_driver
55 	local -gA pci_mod_resolved
56 	local -gA pci_iommu_groups
57 	local -ga iommu_groups
58 
59 	[[ -z ${pci_bus_cache[*]} || $CMD == reset ]] || return 1
60 
61 	pci_bus_cache=()
62 	pci_bus_ids_vendor=()
63 	pci_bus_ids_device=()
64 	pci_bus_driver=()
65 	pci_mod_driver=()
66 	pci_mod_resolved=()
67 	pci_iommu_groups=()
68 	iommu_groups=()
69 }
70 
71 cache_pci() {
72 	local pci=$1 class=$2 vendor=$3 device=$4 driver=$5 mod=$6
73 
74 	if [[ -n $class ]]; then
75 		class=0x${class/0x/}
76 		pci_bus_cache["$class"]="${pci_bus_cache["$class"]:+${pci_bus_cache["$class"]} }$pci"
77 	fi
78 	if [[ -n $vendor && -n $device ]]; then
79 		vendor=0x${vendor/0x/} device=0x${device/0x/}
80 		pci_bus_cache["$vendor:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci"
81 
82 		pci_ids_vendor["$pci"]=$vendor
83 		pci_ids_device["$pci"]=$device
84 	fi
85 	if [[ -n $driver ]]; then
86 		pci_bus_driver["$pci"]=$driver
87 	fi
88 	if [[ -n $mod ]]; then
89 		pci_mod_driver["$pci"]=$mod
90 		pci_mod_resolved["$pci"]=$(resolve_mod "$mod")
91 	fi
92 
93 	cache_pci_iommu_group "$pci"
94 }
95 
96 cache_iommu_group() {
97 	local iommu_group=$1 pcis=() pci
98 
99 	[[ -e /sys/kernel/iommu_groups/$iommu_group/type ]] || return 0
100 
101 	local -n _iommu_group_ref=_iommu_group_$iommu_group
102 
103 	iommu_groups[iommu_group]="_iommu_group_${iommu_group}[@]"
104 
105 	for pci in "/sys/kernel/iommu_groups/$iommu_group/devices/"*; do
106 		pci=${pci##*/}
107 		[[ -n ${pci_iommu_groups["$pci"]} ]] && continue
108 		pci_iommu_groups["$pci"]=$iommu_group
109 		_iommu_group_ref+=("$pci")
110 	done
111 
112 }
113 
114 cache_pci_iommu_group() {
115 	local pci=$1 iommu_group
116 
117 	[[ -e /sys/bus/pci/devices/$pci/iommu_group ]] || return 0
118 
119 	iommu_group=$(readlink -f "/sys/bus/pci/devices/$pci/iommu_group")
120 	iommu_group=${iommu_group##*/}
121 
122 	cache_iommu_group "$iommu_group"
123 }
124 
125 is_iommu_enabled() {
126 	[[ -e /sys/kernel/iommu_groups/0 ]] && return 0
127 	[[ -e /sys/module/vfio/parameters/enable_unsafe_noiommu_mode ]] || return 1
128 	[[ $(< /sys/module/vfio/parameters/enable_unsafe_noiommu_mode) == Y ]]
129 }
130 
131 cache_pci_bus_sysfs() {
132 	[[ -e /sys/bus/pci/devices ]] || return 1
133 
134 	cache_pci_init || return 0
135 
136 	local pci
137 	local class vendor device driver mod
138 
139 	for pci in /sys/bus/pci/devices/*; do
140 		class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device") driver="" mod=""
141 		driver=$(get_pci_driver_sysfs "${pci##*/}")
142 		if [[ -e $pci/modalias ]]; then
143 			mod=$(< "$pci/modalias")
144 		fi
145 		cache_pci "${pci##*/}" "$class" "$vendor" "$device" "$driver" "$mod"
146 	done
147 }
148 
149 cache_pci_bus_lspci() {
150 	hash lspci 2> /dev/null || return 1
151 
152 	cache_pci_init || return 0
153 
154 	local dev
155 	while read -ra dev; do
156 		dev=("${dev[@]//\"/}")
157 		# lspci splits ls byte of the class (prog. interface) into a separate
158 		# field if it's != 0. Look for it and normalize the value to fit with
159 		# what kernel exposes under sysfs.
160 		if [[ ${dev[*]} =~ -p([0-9]+) ]]; then
161 			dev[1]+=${BASH_REMATCH[1]}
162 		else
163 			dev[1]+=00
164 		fi
165 		# pci class vendor device driver
166 		# lspci supports driver listing only under Linux, however, it's not
167 		# included when specific display mode (i.e. -mm) is in use, even if
168 		# extra -k is slapped on the cmdline. So with that in mind, just
169 		# get that info from sysfs.
170 		cache_pci "${dev[@]::4}" "$(get_pci_driver_sysfs "${dev[0]}")"
171 	done < <(lspci -Dnmm)
172 }
173 
174 cache_pci_bus_pciconf() {
175 	hash pciconf 2> /dev/null || return 1
176 
177 	cache_pci_init || return 0
178 
179 	local class vendor device
180 	local pci pci_info
181 	local chip driver
182 
183 	while read -r pci pci_info; do
184 		driver=${pci%@*}
185 		pci=${pci#*:} pci=${pci%:} # E.g.: nvme0@pci0:0:16:0: -> 0:16:0
186 		source <(echo "$pci_info")
187 		# pciconf under FreeBSD 13.1 provides vendor and device IDs in its
188 		# output under separate, dedicated fields. For 12.x they need to
189 		# be extracted from the chip field.
190 		if [[ -n $chip ]]; then
191 			vendor=$(printf '0x%04x' $((chip & 0xffff)))
192 			device=$(printf '0x%04x' $(((chip >> 16) & 0xffff)))
193 		fi
194 		cache_pci "$pci" "$class" "$vendor" "$device" "$driver"
195 	done < <(pciconf -l)
196 }
197 
198 get_pci_driver_sysfs() {
199 	local pci=/sys/bus/pci/devices/$1 driver
200 
201 	if [[ -e $pci/driver ]]; then
202 		driver=$(readlink -f "$pci/driver") driver=${driver##*/}
203 	fi
204 	echo "$driver"
205 }
206 
207 cache_pci_bus() {
208 	case "$(uname -s)" in
209 		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
210 		FreeBSD) cache_pci_bus_pciconf ;;
211 	esac
212 }
213 
214 iter_all_pci_sysfs() {
215 	cache_pci_bus_sysfs || return 1
216 
217 	# default to class of the nvme devices
218 	local find=${1:-0x010802} findx=$2
219 	local pci pcis
220 
221 	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
222 	read -ra pcis <<< "${pci_bus_cache["$find"]}"
223 
224 	if ((findx)); then
225 		printf '%s\n' "${pcis[@]::findx}"
226 	else
227 		printf '%s\n' "${pcis[@]}"
228 	fi
229 }
230 
231 # This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
232 function iter_all_pci_class_code() {
233 	local class
234 	local subclass
235 	local progif
236 	class="$(printf %02x $((0x$1)))"
237 	subclass="$(printf %02x $((0x$2)))"
238 	progif="$(printf %02x $((0x$3)))"
239 
240 	if hash lspci &> /dev/null; then
241 		if [ "$progif" != "00" ]; then
242 			lspci -mm -n -D \
243 				| grep -i -- "-p${progif}" \
244 				| awk -v cc="\"${class}${subclass}\"" -F " " \
245 					'{if (cc ~ $2) print $1}' | tr -d '"'
246 		else
247 			lspci -mm -n -D \
248 				| awk -v cc="\"${class}${subclass}\"" -F " " \
249 					'{if (cc ~ $2) print $1}' | tr -d '"'
250 		fi
251 	elif hash pciconf &> /dev/null; then
252 		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
253 			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
254 		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
255 	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
256 		:
257 	else
258 		echo "Missing PCI enumeration utility" >&2
259 		exit 1
260 	fi
261 }
262 
263 # This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
264 function iter_all_pci_dev_id() {
265 	local ven_id
266 	local dev_id
267 	ven_id="$(printf %04x $((0x$1)))"
268 	dev_id="$(printf %04x $((0x$2)))"
269 
270 	if hash lspci &> /dev/null; then
271 		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
272 			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
273 	elif hash pciconf &> /dev/null; then
274 		local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \
275 			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
276 		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
277 	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
278 		:
279 	else
280 		echo "Missing PCI enumeration utility" >&2
281 		exit 1
282 	fi
283 }
284 
285 function iter_pci_dev_id() {
286 	local bdf=""
287 
288 	for bdf in $(iter_all_pci_dev_id "$@"); do
289 		if pci_can_use "$bdf"; then
290 			echo "$bdf"
291 		fi
292 	done
293 }
294 
295 # This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED
296 # See function pci_can_use()
297 function iter_pci_class_code() {
298 	local bdf=""
299 
300 	for bdf in $(iter_all_pci_class_code "$@"); do
301 		if pci_can_use "$bdf"; then
302 			echo "$bdf"
303 		fi
304 	done
305 }
306 
307 function nvme_in_userspace() {
308 	# Check used drivers. If it's not vfio-pci or uio-pci-generic
309 	# then most likely PCI_ALLOWED option was used for setup.sh
310 	# and we do not want to use that disk.
311 
312 	local bdf bdfs
313 	local nvmes
314 
315 	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
316 		nvmes=(${pci_bus_cache["0x010802"]})
317 	else
318 		nvmes=($(iter_pci_class_code 01 08 02))
319 	fi
320 
321 	for bdf in "${nvmes[@]}"; do
322 		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
323 			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then
324 			continue
325 		fi
326 		bdfs+=("$bdf")
327 	done
328 	((${#bdfs[@]})) || return 1
329 	printf '%s\n' "${bdfs[@]}"
330 }
331 
332 cmp_versions() {
333 	local ver1 ver1_l
334 	local ver2 ver2_l
335 
336 	IFS=".-:" read -ra ver1 <<< "$1"
337 	IFS=".-:" read -ra ver2 <<< "$3"
338 	local op=$2
339 
340 	ver1_l=${#ver1[@]}
341 	ver2_l=${#ver2[@]}
342 
343 	local lt=0 gt=0 eq=0 v
344 	case "$op" in
345 		"<") : $((eq = gt = 1)) ;;
346 		">") : $((eq = lt = 1)) ;;
347 		"<=") : $((gt = 1)) ;;
348 		">=") : $((lt = 1)) ;;
349 		"==") : $((lt = gt = 1)) ;;
350 	esac
351 
352 	decimal() (
353 		local d=${1,,}
354 		if [[ $d =~ ^[0-9]+$ ]]; then
355 			echo $((10#$d))
356 		elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then
357 			d=${d/0x/}
358 			echo $((0x$d))
359 		else
360 			echo 0
361 		fi
362 	)
363 
364 	for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do
365 		ver1[v]=$(decimal "${ver1[v]}")
366 		ver2[v]=$(decimal "${ver2[v]}")
367 		((ver1[v] > ver2[v])) && return "$gt"
368 		((ver1[v] < ver2[v])) && return "$lt"
369 	done
370 	[[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq"
371 }
372 
373 lt() { cmp_versions "$1" "<" "$2"; }
374 gt() { cmp_versions "$1" ">" "$2"; }
375 le() { cmp_versions "$1" "<=" "$2"; }
376 ge() { cmp_versions "$1" ">=" "$2"; }
377 eq() { cmp_versions "$1" "==" "$2"; }
378 neq() { ! eq "$1" "$2"; }
379 
380 block_in_use() {
381 	local block=$1 pt
382 	# Skip devices that are in use - simple blkid it to see if
383 	# there's any metadata (pt, fs, etc.) present on the drive.
384 	# FIXME: Special case to ignore atari as a potential false
385 	# positive:
386 	# https://github.com/spdk/spdk/issues/2079
387 	# Devices with SPDK's GPT part type are not considered to
388 	# be in use.
389 
390 	if "$rootdir/scripts/spdk-gpt.py" "$block"; then
391 		return 1
392 	fi
393 
394 	if ! pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}"); then
395 		return 1
396 	elif [[ $pt == atari ]]; then
397 		return 1
398 	fi
399 
400 	# Devices used in SPDK tests always create GPT partitions
401 	# with label containing SPDK_TEST string. Such devices were
402 	# part of the tests before, so are not considered in use.
403 	if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then
404 		return 1
405 	fi
406 
407 	return 0
408 }
409 
410 get_spdk_gpt_old() {
411 	local spdk_guid
412 
413 	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
414 
415 	GPT_H="$rootdir/module/bdev/gpt/gpt.h"
416 	IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID_OLD "$GPT_H")
417 	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
418 
419 	echo "$spdk_guid"
420 }
421 
422 get_spdk_gpt() {
423 	local spdk_guid
424 
425 	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
426 
427 	GPT_H="$rootdir/module/bdev/gpt/gpt.h"
428 	IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID "$GPT_H")
429 	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
430 
431 	echo "$spdk_guid"
432 }
433 
434 map_supported_devices() {
435 	local type=${1^^}
436 	local ids dev_types dev_type dev_id bdf bdfs vmd _vmd
437 
438 	local -gA nvme_d
439 	local -gA ioat_d dsa_d iaa_d
440 	local -gA virtio_d
441 	local -gA vmd_d nvme_vmd_d vmd_nvme_d vmd_nvme_count
442 	local -gA all_devices_d types_d all_devices_type_d
443 
444 	ids+="PCI_DEVICE_ID_INTEL_IOAT" dev_types+="IOAT"
445 	ids+="|PCI_DEVICE_ID_INTEL_DSA" dev_types+="|DSA"
446 	ids+="|PCI_DEVICE_ID_INTEL_IAA" dev_types+="|IAA"
447 	ids+="|PCI_DEVICE_ID_VIRTIO" dev_types+="|VIRTIO"
448 	ids+="|PCI_DEVICE_ID_INTEL_VMD" dev_types+="|VMD"
449 	ids+="|SPDK_PCI_CLASS_NVME" dev_types+="|NVME"
450 
451 	[[ -e $rootdir/include/spdk/pci_ids.h ]] || return 1
452 
453 	((${#pci_bus_cache[@]} == 0)) && cache_pci_bus
454 
455 	while read -r _ dev_type dev_id; do
456 		[[ $dev_type == *$type* ]] || continue
457 		bdfs=(${pci_bus_cache["0x8086:$dev_id"]})
458 		[[ $dev_type == *NVME* ]] && bdfs=(${pci_bus_cache["$dev_id"]})
459 		[[ $dev_type == *VIRT* ]] && bdfs=(${pci_bus_cache["0x1af4:$dev_id"]})
460 		[[ $dev_type =~ ($dev_types) ]] && dev_type=${BASH_REMATCH[1],,}
461 		types_d["$dev_type"]=1
462 		for bdf in "${bdfs[@]}"; do
463 			eval "${dev_type}_d[$bdf]=0"
464 			all_devices_d["$bdf"]=0
465 			all_devices_type_d["$bdf"]=$dev_type
466 		done
467 	done < <(grep -E "$ids" "$rootdir/include/spdk/pci_ids.h")
468 
469 	# Rebuild vmd refs from the very cratch to not have duplicates in case we were called
470 	# multiple times.
471 	unset -v "${!_vmd_@}"
472 
473 	for bdf in "${!nvme_d[@]}"; do
474 		vmd=$(is_nvme_behind_vmd "$bdf") && _vmd=${vmd//[:.]/_} || continue
475 		nvme_vmd_d["$bdf"]=$vmd
476 		vmd_nvme_d["$vmd"]="_vmd_${_vmd}_nvmes[@]"
477 		((++vmd_nvme_count["$vmd"]))
478 		eval "_vmd_${_vmd}_nvmes+=($bdf)"
479 	done
480 }
481 
482 is_nvme_behind_vmd() {
483 	local nvme_bdf=$1 dev_path
484 
485 	IFS="/" read -ra dev_path < <(readlink -f "/sys/bus/pci/devices/$nvme_bdf")
486 
487 	for dev in "${dev_path[@]}"; do
488 		[[ -n $dev && -n ${vmd_d["$dev"]} ]] && echo $dev && return 0
489 	done
490 	return 1
491 }
492 
493 is_nvme_iommu_shared_with_vmd() {
494 	local nvme_bdf=$1 vmd
495 
496 	# This use-case is quite specific to vfio-pci|iommu setup
497 	is_iommu_enabled || return 1
498 
499 	[[ -n ${nvme_vmd_d["$nvme_bdf"]} ]] || return 1
500 	# nvme is behind VMD ...
501 	((pci_iommu_groups["$nvme_bdf"] == pci_iommu_groups["${nvme_vmd_d["$nvme_bdf"]}"])) || return 1
502 	# ... and it shares iommu_group with it
503 }
504 
505 kmsg() {
506 	((UID == 0)) || return 0
507 	[[ -w /dev/kmsg && $(< /proc/sys/kernel/printk_devkmsg) == on ]] || return 0
508 	echo "$*" > /dev/kmsg
509 }
510 
511 function get_block_dev_from_bdf() {
512 	local bdf=$1
513 	local block blocks=() ctrl sub
514 
515 	for block in /sys/block/!(nvme*); do
516 		if [[ $(readlink -f "$block/device") == *"/$bdf/"* ]]; then
517 			blocks+=("${block##*/}")
518 		fi
519 	done
520 
521 	blocks+=($(get_block_dev_from_nvme "$bdf"))
522 
523 	printf '%s\n' "${blocks[@]}"
524 }
525 
526 function get_block_dev_from_nvme() {
527 	local bdf=$1 block ctrl sub
528 
529 	for ctrl in /sys/class/nvme/nvme*; do
530 		[[ -e $ctrl/address && $(< "$ctrl/address") == "$bdf" ]] || continue
531 		sub=$(< "$ctrl/subsysnqn") && break
532 	done
533 
534 	[[ -n $sub ]] || return 0
535 
536 	for block in /sys/block/nvme*; do
537 		[[ -e $block/hidden && $(< "$block/hidden") == 1 ]] && continue
538 		if [[ -e $block/device/subsysnqn && $(< "$block/device/subsysnqn") == "$sub" ]]; then
539 			echo "${block##*/}"
540 		fi
541 	done
542 }
543 
544 if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then
545 	# Adjust uname to report the operating system as WSL, Msys or Cygwin
546 	# and the kernel name as Windows. Define kill() to invoke the SIGTERM
547 	# handler before causing a hard stop with TerminateProcess.
548 	source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh"
549 fi
550 
551 # Make sure we have access to proper binaries installed in pkgdep/common.sh
552 if [[ -e /etc/opt/spdk-pkgdep/paths/export.sh ]]; then
553 	source /etc/opt/spdk-pkgdep/paths/export.sh
554 fi > /dev/null
555