xref: /spdk/scripts/common.sh (revision 12fbe739a31b09aff0d05f354d4f3bbef99afc55)
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
54	[[ -z ${pci_bus_cache[*]} || $CMD == reset ]] || return 1
55
56	pci_bus_cache=()
57	pci_bus_ids_vendor=()
58	pci_bus_ids_device=()
59	pci_bus_driver=()
60	pci_mod_driver=()
61	pci_mod_resolved=()
62}
63
64cache_pci() {
65	local pci=$1 class=$2 vendor=$3 device=$4 driver=$5 mod=$6
66
67	if [[ -n $class ]]; then
68		class=0x${class/0x/}
69		pci_bus_cache["$class"]="${pci_bus_cache["$class"]:+${pci_bus_cache["$class"]} }$pci"
70	fi
71	if [[ -n $vendor && -n $device ]]; then
72		vendor=0x${vendor/0x/} device=0x${device/0x/}
73		pci_bus_cache["$vendor:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci"
74
75		pci_ids_vendor["$pci"]=$vendor
76		pci_ids_device["$pci"]=$device
77	fi
78	if [[ -n $driver ]]; then
79		pci_bus_driver["$pci"]=$driver
80	fi
81	if [[ -n $mod ]]; then
82		pci_mod_driver["$pci"]=$mod
83		pci_mod_resolved["$pci"]=$(resolve_mod "$mod")
84	fi
85}
86
87cache_pci_bus_sysfs() {
88	[[ -e /sys/bus/pci/devices ]] || return 1
89
90	cache_pci_init || return 0
91
92	local pci
93	local class vendor device driver mod
94
95	for pci in /sys/bus/pci/devices/*; do
96		class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device") driver="" mod=""
97		driver=$(get_pci_driver_sysfs "${pci##*/}")
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 driver
122		# lspci supports driver listing only under Linux, however, it's not
123		# included when specific display mode (i.e. -mm) is in use, even if
124		# extra -k is slapped on the cmdline. So with that in mind, just
125		# get that info from sysfs.
126		cache_pci "${dev[@]::4}" "$(get_pci_driver_sysfs "${dev[0]}")"
127	done < <(lspci -Dnmm)
128}
129
130cache_pci_bus_pciconf() {
131	hash pciconf 2> /dev/null || return 1
132
133	cache_pci_init || return 0
134
135	local class vendor device
136	local pci pci_info
137	local chip driver
138
139	while read -r pci pci_info; do
140		driver=${pci%@*}
141		pci=${pci##*pci} pci=${pci%:}
142		source <(echo "$pci_info")
143		# pciconf under FreeBSD 13.1 provides vendor and device IDs in its
144		# output under separate, dedicated fields. For 12.x they need to
145		# be extracted from the chip field.
146		if [[ -n $chip ]]; then
147			vendor=$(printf '0x%04x' $((chip & 0xffff)))
148			device=$(printf '0x%04x' $(((chip >> 16) & 0xffff)))
149		fi
150		cache_pci "$pci" "$class" "$vendor" "$device" "$driver"
151	done < <(pciconf -l)
152}
153
154get_pci_driver_sysfs() {
155	local pci=/sys/bus/pci/devices/$1 driver
156
157	if [[ -e $pci/driver ]]; then
158		driver=$(readlink -f "$pci/driver") driver=${driver##*/}
159	else
160		driver=unbound
161	fi
162	echo "$driver"
163}
164
165cache_pci_bus() {
166	case "$(uname -s)" in
167		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
168		FreeBSD) cache_pci_bus_pciconf ;;
169	esac
170}
171
172iter_all_pci_sysfs() {
173	cache_pci_bus_sysfs || return 1
174
175	# default to class of the nvme devices
176	local find=${1:-0x010802} findx=$2
177	local pci pcis
178
179	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
180	read -ra pcis <<< "${pci_bus_cache["$find"]}"
181
182	if ((findx)); then
183		printf '%s\n' "${pcis[@]::findx}"
184	else
185		printf '%s\n' "${pcis[@]}"
186	fi
187}
188
189# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
190function iter_all_pci_class_code() {
191	local class
192	local subclass
193	local progif
194	class="$(printf %02x $((0x$1)))"
195	subclass="$(printf %02x $((0x$2)))"
196	progif="$(printf %02x $((0x$3)))"
197
198	if hash lspci &> /dev/null; then
199		if [ "$progif" != "00" ]; then
200			lspci -mm -n -D \
201				| grep -i -- "-p${progif}" \
202				| awk -v cc="\"${class}${subclass}\"" -F " " \
203					'{if (cc ~ $2) print $1}' | tr -d '"'
204		else
205			lspci -mm -n -D \
206				| awk -v cc="\"${class}${subclass}\"" -F " " \
207					'{if (cc ~ $2) print $1}' | tr -d '"'
208		fi
209	elif hash pciconf &> /dev/null; then
210		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
211			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
212		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
213	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
214		:
215	else
216		echo "Missing PCI enumeration utility" >&2
217		exit 1
218	fi
219}
220
221# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
222function iter_all_pci_dev_id() {
223	local ven_id
224	local dev_id
225	ven_id="$(printf %04x $((0x$1)))"
226	dev_id="$(printf %04x $((0x$2)))"
227
228	if hash lspci &> /dev/null; then
229		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
230			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
231	elif hash pciconf &> /dev/null; then
232		local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \
233			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
234		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
235	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
236		:
237	else
238		echo "Missing PCI enumeration utility" >&2
239		exit 1
240	fi
241}
242
243function iter_pci_dev_id() {
244	local bdf=""
245
246	for bdf in $(iter_all_pci_dev_id "$@"); do
247		if pci_can_use "$bdf"; then
248			echo "$bdf"
249		fi
250	done
251}
252
253# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED
254# See function pci_can_use()
255function iter_pci_class_code() {
256	local bdf=""
257
258	for bdf in $(iter_all_pci_class_code "$@"); do
259		if pci_can_use "$bdf"; then
260			echo "$bdf"
261		fi
262	done
263}
264
265function nvme_in_userspace() {
266	# Check used drivers. If it's not vfio-pci or uio-pci-generic
267	# then most likely PCI_ALLOWED option was used for setup.sh
268	# and we do not want to use that disk.
269
270	local bdf bdfs
271	local nvmes
272
273	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
274		nvmes=(${pci_bus_cache["0x010802"]})
275	else
276		nvmes=($(iter_pci_class_code 01 08 02))
277	fi
278
279	for bdf in "${nvmes[@]}"; do
280		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
281			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then
282			continue
283		fi
284		bdfs+=("$bdf")
285	done
286	((${#bdfs[@]})) || return 1
287	printf '%s\n' "${bdfs[@]}"
288}
289
290cmp_versions() {
291	local ver1 ver1_l
292	local ver2 ver2_l
293
294	IFS=".-:" read -ra ver1 <<< "$1"
295	IFS=".-:" read -ra ver2 <<< "$3"
296	local op=$2
297
298	ver1_l=${#ver1[@]}
299	ver2_l=${#ver2[@]}
300
301	local lt=0 gt=0 eq=0 v
302	case "$op" in
303		"<") : $((eq = gt = 1)) ;;
304		">") : $((eq = lt = 1)) ;;
305		"<=") : $((gt = 1)) ;;
306		">=") : $((lt = 1)) ;;
307		"==") : $((lt = gt = 1)) ;;
308	esac
309
310	decimal() (
311		local d=${1,,}
312		if [[ $d =~ ^[0-9]+$ ]]; then
313			echo $((10#$d))
314		elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then
315			d=${d/0x/}
316			echo $((0x$d))
317		else
318			echo 0
319		fi
320	)
321
322	for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do
323		ver1[v]=$(decimal "${ver1[v]}")
324		ver2[v]=$(decimal "${ver2[v]}")
325		((ver1[v] > ver2[v])) && return "$gt"
326		((ver1[v] < ver2[v])) && return "$lt"
327	done
328	[[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq"
329}
330
331lt() { cmp_versions "$1" "<" "$2"; }
332gt() { cmp_versions "$1" ">" "$2"; }
333le() { cmp_versions "$1" "<=" "$2"; }
334ge() { cmp_versions "$1" ">=" "$2"; }
335eq() { cmp_versions "$1" "==" "$2"; }
336neq() { ! eq "$1" "$2"; }
337
338block_in_use() {
339	local block=$1 pt
340	# Skip devices that are in use - simple blkid it to see if
341	# there's any metadata (pt, fs, etc.) present on the drive.
342	# FIXME: Special case to ignore atari as a potential false
343	# positive:
344	# https://github.com/spdk/spdk/issues/2079
345	# Devices with SPDK's GPT part type are not considered to
346	# be in use.
347
348	if "$rootdir/scripts/spdk-gpt.py" "$block"; then
349		return 1
350	fi
351
352	if ! pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}"); then
353		return 1
354	elif [[ $pt == atari ]]; then
355		return 1
356	fi
357
358	# Devices used in SPDK tests always create GPT partitions
359	# with label containing SPDK_TEST string. Such devices were
360	# part of the tests before, so are not considered in use.
361	if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then
362		return 1
363	fi
364
365	return 0
366}
367
368get_spdk_gpt_old() {
369	local spdk_guid
370
371	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
372
373	GPT_H="$rootdir/module/bdev/gpt/gpt.h"
374	IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID_OLD "$GPT_H")
375	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
376
377	echo "$spdk_guid"
378}
379
380get_spdk_gpt() {
381	local spdk_guid
382
383	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
384
385	GPT_H="$rootdir/module/bdev/gpt/gpt.h"
386	IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID "$GPT_H")
387	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
388
389	echo "$spdk_guid"
390}
391
392if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then
393	# Adjust uname to report the operating system as WSL, Msys or Cygwin
394	# and the kernel name as Windows. Define kill() to invoke the SIGTERM
395	# handler before causing a hard stop with TerminateProcess.
396	source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh"
397fi
398
399# Make sure we have access to proper binaries installed in pkgdep/common.sh
400if [[ -e /etc/opt/spdk-pkgdep/paths/export.sh ]]; then
401	source /etc/opt/spdk-pkgdep/paths/export.sh
402fi > /dev/null
403