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
15shopt -s extglob
16
17function 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
39resolve_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
49cache_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
71cache_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
96cache_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
114cache_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
125is_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
131cache_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
149cache_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
174cache_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
198get_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
207cache_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
214iter_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
232function 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
264function 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
285function 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()
297function 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
307function 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
332cmp_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
373lt() { cmp_versions "$1" "<" "$2"; }
374gt() { cmp_versions "$1" ">" "$2"; }
375le() { cmp_versions "$1" "<=" "$2"; }
376ge() { cmp_versions "$1" ">=" "$2"; }
377eq() { cmp_versions "$1" "==" "$2"; }
378neq() { ! eq "$1" "$2"; }
379
380block_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
410get_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
422get_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
434map_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
482is_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
493is_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
505kmsg() {
506	((UID == 0)) || return 0
507	[[ -w /dev/kmsg && $(< /proc/sys/kernel/printk_devkmsg) == on ]] || return 0
508	echo "$*" > /dev/kmsg
509}
510
511function 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
526function 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
544if [[ -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"
549fi
550
551# Make sure we have access to proper binaries installed in pkgdep/common.sh
552if [[ -e /etc/opt/spdk-pkgdep/paths/export.sh ]]; then
553	source /etc/opt/spdk-pkgdep/paths/export.sh
554fi > /dev/null
555