xref: /spdk/scripts/common.sh (revision 88e3ffd7b6c5ec1ea1a660354d25f02c766092e1)
1# Common shell utility functions
2
3# Check if PCI device is in PCI_ALLOWED and not in PCI_BLOCKED
4# Env:
5# if PCI_ALLOWED is empty assume device is allowed
6# if PCI_BLOCKED is empty assume device is NOT blocked
7# Params:
8# $1 - PCI BDF
9function pci_can_use() {
10	local i
11
12	# The '\ ' part is important
13	if [[ " $PCI_BLOCKED " =~ \ $1\  ]]; then
14		return 1
15	fi
16
17	if [[ -z "$PCI_ALLOWED" ]]; then
18		#no allow list specified, bind all devices
19		return 0
20	fi
21
22	for i in $PCI_ALLOWED; do
23		if [ "$i" == "$1" ]; then
24			return 0
25		fi
26	done
27
28	return 1
29}
30
31cache_pci_init() {
32	local -gA pci_bus_cache
33	local -gA pci_ids_vendor
34	local -gA pci_ids_device
35
36	[[ -z ${pci_bus_cache[*]} || $CMD == reset ]] || return 1
37
38	pci_bus_cache=()
39	pci_bus_ids_vendor=()
40	pci_bus_ids_device=()
41}
42
43cache_pci() {
44	local pci=$1 class=$2 vendor=$3 device=$4
45
46	if [[ -n $class ]]; then
47		class=0x${class/0x/}
48		pci_bus_cache["$class"]="${pci_bus_cache["$class"]:+${pci_bus_cache["$class"]} }$pci"
49	fi
50	if [[ -n $vendor && -n $device ]]; then
51		vendor=0x${vendor/0x/} device=0x${device/0x/}
52		pci_bus_cache["$vendor:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci"
53
54		pci_ids_vendor["$pci"]=$vendor
55		pci_ids_device["$pci"]=$device
56	fi
57}
58
59cache_pci_bus_sysfs() {
60	[[ -e /sys/bus/pci/devices ]] || return 1
61
62	cache_pci_init || return 0
63
64	local pci
65	local class vendor device
66
67	for pci in /sys/bus/pci/devices/*; do
68		class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device")
69		cache_pci "${pci##*/}" "$class" "$vendor" "$device"
70	done
71}
72
73cache_pci_bus_lspci() {
74	hash lspci 2> /dev/null || return 1
75
76	cache_pci_init || return 0
77
78	local dev
79	while read -ra dev; do
80		dev=("${dev[@]//\"/}")
81		# lspci splits ls byte of the class (prog. interface) into a separate
82		# field if it's != 0. Look for it and normalize the value to fit with
83		# what kernel exposes under sysfs.
84		if [[ ${dev[*]} =~ -p([0-9]+) ]]; then
85			dev[1]+=${BASH_REMATCH[1]}
86		else
87			dev[1]+=00
88		fi
89		# pci class vendor device
90		cache_pci "${dev[@]::4}"
91	done < <(lspci -Dnmm)
92}
93
94cache_pci_bus_pciconf() {
95	hash pciconf 2> /dev/null || return 1
96
97	cache_pci_init || return 0
98
99	local class vd vendor device
100	local pci domain bus device function
101
102	while read -r pci class _ vd _; do
103		IFS=":" read -r domain bus device function _ <<< "${pci##*pci}"
104		pci=$(printf '%04x:%02x:%02x:%x' \
105			"$domain" "$bus" "$device" "$function")
106		class=$(printf '0x%06x' $((class)))
107		vendor=$(printf '0x%04x' $((vd & 0xffff)))
108		device=$(printf '0x%04x' $(((vd >> 16) & 0xffff)))
109
110		cache_pci "$pci" "$class" "$vendor" "$device"
111	done < <(pciconf -l)
112}
113
114cache_pci_bus() {
115	case "$(uname -s)" in
116		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
117		FreeBSD) cache_pci_bus_pciconf ;;
118	esac
119}
120
121iter_all_pci_sysfs() {
122	cache_pci_bus_sysfs || return 1
123
124	# default to class of the nvme devices
125	local find=${1:-0x010802} findx=$2
126	local pci pcis
127
128	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
129	read -ra pcis <<< "${pci_bus_cache["$find"]}"
130
131	if ((findx)); then
132		printf '%s\n' "${pcis[@]::findx}"
133	else
134		printf '%s\n' "${pcis[@]}"
135	fi
136}
137
138# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
139function iter_all_pci_class_code() {
140	local class
141	local subclass
142	local progif
143	class="$(printf %02x $((0x$1)))"
144	subclass="$(printf %02x $((0x$2)))"
145	progif="$(printf %02x $((0x$3)))"
146
147	if hash lspci &> /dev/null; then
148		if [ "$progif" != "00" ]; then
149			lspci -mm -n -D \
150				| grep -i -- "-p${progif}" \
151				| awk -v cc="\"${class}${subclass}\"" -F " " \
152					'{if (cc ~ $2) print $1}' | tr -d '"'
153		else
154			lspci -mm -n -D \
155				| awk -v cc="\"${class}${subclass}\"" -F " " \
156					'{if (cc ~ $2) print $1}' | tr -d '"'
157		fi
158	elif hash pciconf &> /dev/null; then
159		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
160			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
161		printf "%04x:%02x:%02x:%x\n" ${addr[0]} ${addr[1]} ${addr[2]} ${addr[3]}
162	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
163		:
164	else
165		echo "Missing PCI enumeration utility" >&2
166		exit 1
167	fi
168}
169
170# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED
171function iter_all_pci_dev_id() {
172	local ven_id
173	local dev_id
174	ven_id="$(printf %04x $((0x$1)))"
175	dev_id="$(printf %04x $((0x$2)))"
176
177	if hash lspci &> /dev/null; then
178		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
179			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
180	elif hash pciconf &> /dev/null; then
181		local addr=($(pciconf -l | grep -i "chip=0x${dev_id}${ven_id}" \
182			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
183		printf "%04x:%02x:%02x:%x\n" ${addr[0]} ${addr[1]} ${addr[2]} ${addr[3]}
184	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
185		:
186	else
187		echo "Missing PCI enumeration utility" >&2
188		exit 1
189	fi
190}
191
192function iter_pci_dev_id() {
193	local bdf=""
194
195	for bdf in $(iter_all_pci_dev_id "$@"); do
196		if pci_can_use "$bdf"; then
197			echo "$bdf"
198		fi
199	done
200}
201
202# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED
203# See function pci_can_use()
204function iter_pci_class_code() {
205	local bdf=""
206
207	for bdf in $(iter_all_pci_class_code "$@"); do
208		if pci_can_use "$bdf"; then
209			echo "$bdf"
210		fi
211	done
212}
213
214function nvme_in_userspace() {
215	# Check used drivers. If it's not vfio-pci or uio-pci-generic
216	# then most likely PCI_ALLOWED option was used for setup.sh
217	# and we do not want to use that disk.
218
219	local bdf bdfs
220	local nvmes
221
222	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
223		nvmes=(${pci_bus_cache["0x010802"]})
224	else
225		nvmes=($(iter_pci_class_code 01 08 02))
226	fi
227
228	for bdf in "${nvmes[@]}"; do
229		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
230			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then
231			continue
232		fi
233		bdfs+=("$bdf")
234	done
235	((${#bdfs[@]})) || return 1
236	printf '%s\n' "${bdfs[@]}"
237}
238
239cmp_versions() {
240	local ver1 ver1_l
241	local ver2 ver2_l
242
243	IFS=".-:" read -ra ver1 <<< "$1"
244	IFS=".-:" read -ra ver2 <<< "$3"
245	local op=$2
246
247	ver1_l=${#ver1[@]}
248	ver2_l=${#ver2[@]}
249
250	local lt=0 gt=0 eq=0 v
251	case "$op" in
252		"<") : $((eq = gt = 1)) ;;
253		">") : $((eq = lt = 1)) ;;
254		"<=") : $((gt = 1)) ;;
255		">=") : $((lt = 1)) ;;
256		"==") : $((lt = gt = 1)) ;;
257	esac
258
259	decimal() (
260		local d=${1,,}
261		if [[ $d =~ ^[0-9]+$ ]]; then
262			echo $((10#$d))
263		elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then
264			d=${d/0x/}
265			echo $((0x$d))
266		else
267			echo 0
268		fi
269	)
270
271	for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do
272		ver1[v]=$(decimal "${ver1[v]}")
273		ver2[v]=$(decimal "${ver2[v]}")
274		((ver1[v] > ver2[v])) && return "$gt"
275		((ver1[v] < ver2[v])) && return "$lt"
276	done
277	[[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq"
278}
279
280lt() { cmp_versions "$1" "<" "$2"; }
281gt() { cmp_versions "$1" ">" "$2"; }
282le() { cmp_versions "$1" "<=" "$2"; }
283ge() { cmp_versions "$1" ">=" "$2"; }
284eq() { cmp_versions "$1" "==" "$2"; }
285neq() { ! eq "$1" "$2"; }
286