xref: /spdk/scripts/common.sh (revision fecffda6ecf8853b82edccde429b68252f0a62c5)
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		if [[ -e $pci/driver ]]; then
98			driver=$(readlink -f "$pci/driver")
99			driver=${driver##*/}
100		else
101			driver=unbound
102		fi
103		if [[ -e $pci/modalias ]]; then
104			mod=$(< "$pci/modalias")
105		fi
106		cache_pci "${pci##*/}" "$class" "$vendor" "$device" "$driver" "$mod"
107	done
108}
109
110cache_pci_bus_lspci() {
111	hash lspci 2> /dev/null || return 1
112
113	cache_pci_init || return 0
114
115	local dev
116	while read -ra dev; do
117		dev=("${dev[@]//\"/}")
118		# lspci splits ls byte of the class (prog. interface) into a separate
119		# field if it's != 0. Look for it and normalize the value to fit with
120		# what kernel exposes under sysfs.
121		if [[ ${dev[*]} =~ -p([0-9]+) ]]; then
122			dev[1]+=${BASH_REMATCH[1]}
123		else
124			dev[1]+=00
125		fi
126		# pci class vendor device
127		cache_pci "${dev[@]::4}"
128	done < <(lspci -Dnmm)
129}
130
131cache_pci_bus_pciconf() {
132	hash pciconf 2> /dev/null || return 1
133
134	cache_pci_init || return 0
135
136	local class vendor device
137	local pci pci_info
138	local chip driver
139
140	while read -r pci pci_info; do
141		driver=${pci%@*}
142		pci=${pci##*pci} pci=${pci%:}
143		source <(echo "$pci_info")
144		# pciconf under FreeBSD 13.1 provides vendor and device IDs in its
145		# output under separate, dedicated fields. For 12.x they need to
146		# be extracted from the chip field.
147		if [[ -n $chip ]]; then
148			vendor=$(printf '0x%04x' $((chip & 0xffff)))
149			device=$(printf '0x%04x' $(((chip >> 16) & 0xffff)))
150		fi
151		cache_pci "$pci" "$class" "$vendor" "$device" "$driver"
152	done < <(pciconf -l)
153}
154
155cache_pci_bus() {
156	case "$(uname -s)" in
157		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
158		FreeBSD) cache_pci_bus_pciconf ;;
159	esac
160}
161
162iter_all_pci_sysfs() {
163	cache_pci_bus_sysfs || return 1
164
165	# default to class of the nvme devices
166	local find=${1:-0x010802} findx=$2
167	local pci pcis
168
169	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
170	read -ra pcis <<< "${pci_bus_cache["$find"]}"
171
172	if ((findx)); then
173		printf '%s\n' "${pcis[@]::findx}"
174	else
175		printf '%s\n' "${pcis[@]}"
176	fi
177}
178
179# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
180function iter_all_pci_class_code() {
181	local class
182	local subclass
183	local progif
184	class="$(printf %02x $((0x$1)))"
185	subclass="$(printf %02x $((0x$2)))"
186	progif="$(printf %02x $((0x$3)))"
187
188	if hash lspci &> /dev/null; then
189		if [ "$progif" != "00" ]; then
190			lspci -mm -n -D \
191				| grep -i -- "-p${progif}" \
192				| awk -v cc="\"${class}${subclass}\"" -F " " \
193					'{if (cc ~ $2) print $1}' | tr -d '"'
194		else
195			lspci -mm -n -D \
196				| awk -v cc="\"${class}${subclass}\"" -F " " \
197					'{if (cc ~ $2) print $1}' | tr -d '"'
198		fi
199	elif hash pciconf &> /dev/null; then
200		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
201			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
202		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
203	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
204		:
205	else
206		echo "Missing PCI enumeration utility" >&2
207		exit 1
208	fi
209}
210
211# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
212function iter_all_pci_dev_id() {
213	local ven_id
214	local dev_id
215	ven_id="$(printf %04x $((0x$1)))"
216	dev_id="$(printf %04x $((0x$2)))"
217
218	if hash lspci &> /dev/null; then
219		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
220			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
221	elif hash pciconf &> /dev/null; then
222		local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \
223			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
224		echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}"
225	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
226		:
227	else
228		echo "Missing PCI enumeration utility" >&2
229		exit 1
230	fi
231}
232
233function iter_pci_dev_id() {
234	local bdf=""
235
236	for bdf in $(iter_all_pci_dev_id "$@"); do
237		if pci_can_use "$bdf"; then
238			echo "$bdf"
239		fi
240	done
241}
242
243# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED
244# See function pci_can_use()
245function iter_pci_class_code() {
246	local bdf=""
247
248	for bdf in $(iter_all_pci_class_code "$@"); do
249		if pci_can_use "$bdf"; then
250			echo "$bdf"
251		fi
252	done
253}
254
255function nvme_in_userspace() {
256	# Check used drivers. If it's not vfio-pci or uio-pci-generic
257	# then most likely PCI_ALLOWED option was used for setup.sh
258	# and we do not want to use that disk.
259
260	local bdf bdfs
261	local nvmes
262
263	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
264		nvmes=(${pci_bus_cache["0x010802"]})
265	else
266		nvmes=($(iter_pci_class_code 01 08 02))
267	fi
268
269	for bdf in "${nvmes[@]}"; do
270		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
271			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then
272			continue
273		fi
274		bdfs+=("$bdf")
275	done
276	((${#bdfs[@]})) || return 1
277	printf '%s\n' "${bdfs[@]}"
278}
279
280cmp_versions() {
281	local ver1 ver1_l
282	local ver2 ver2_l
283
284	IFS=".-:" read -ra ver1 <<< "$1"
285	IFS=".-:" read -ra ver2 <<< "$3"
286	local op=$2
287
288	ver1_l=${#ver1[@]}
289	ver2_l=${#ver2[@]}
290
291	local lt=0 gt=0 eq=0 v
292	case "$op" in
293		"<") : $((eq = gt = 1)) ;;
294		">") : $((eq = lt = 1)) ;;
295		"<=") : $((gt = 1)) ;;
296		">=") : $((lt = 1)) ;;
297		"==") : $((lt = gt = 1)) ;;
298	esac
299
300	decimal() (
301		local d=${1,,}
302		if [[ $d =~ ^[0-9]+$ ]]; then
303			echo $((10#$d))
304		elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then
305			d=${d/0x/}
306			echo $((0x$d))
307		else
308			echo 0
309		fi
310	)
311
312	for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do
313		ver1[v]=$(decimal "${ver1[v]}")
314		ver2[v]=$(decimal "${ver2[v]}")
315		((ver1[v] > ver2[v])) && return "$gt"
316		((ver1[v] < ver2[v])) && return "$lt"
317	done
318	[[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq"
319}
320
321lt() { cmp_versions "$1" "<" "$2"; }
322gt() { cmp_versions "$1" ">" "$2"; }
323le() { cmp_versions "$1" "<=" "$2"; }
324ge() { cmp_versions "$1" ">=" "$2"; }
325eq() { cmp_versions "$1" "==" "$2"; }
326neq() { ! eq "$1" "$2"; }
327
328block_in_use() {
329	local block=$1 data pt
330	# Skip devices that are in use - simple blkid it to see if
331	# there's any metadata (pt, fs, etc.) present on the drive.
332	# FIXME: Special case to ignore atari as a potential false
333	# positive:
334	# https://github.com/spdk/spdk/issues/2079
335	# Devices with SPDK's GPT part type are not considered to
336	# be in use.
337
338	if "$rootdir/scripts/spdk-gpt.py" "$block"; then
339		return 1
340	fi
341
342	data=$(blkid "/dev/${block##*/}") || data=none
343
344	if [[ $data == none ]]; then
345		return 1
346	fi
347
348	pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}") || pt=none
349
350	if [[ $pt == none || $pt == atari ]]; then
351		return 1
352	fi
353
354	# Devices used in SPDK tests always create GPT partitions
355	# with label containing SPDK_TEST string. Such devices were
356	# part of the tests before, so are not considered in use.
357	if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then
358		return 1
359	fi
360
361	return 0
362}
363
364get_spdk_gpt() {
365	local spdk_guid
366
367	[[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1
368
369	IFS="()" read -r _ spdk_guid _ < <(grep SPDK_GPT_PART_TYPE_GUID "$rootdir/module/bdev/gpt/gpt.h")
370	spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/}
371
372	echo "$spdk_guid"
373}
374
375if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then
376	# Adjust uname to report the operating system as WSL, Msys or Cygwin
377	# and the kernel name as Windows. Define kill() to invoke the SIGTERM
378	# handler before causing a hard stop with TerminateProcess.
379	source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh"
380fi
381