xref: /spdk/scripts/common.sh (revision 6a9e923da298dda69cbdaf73eed6fb0e85ec2ad2)
1# Common shell utility functions
2
3# Check if PCI device is on PCI_WHITELIST and not on PCI_BLACKLIST
4# Env:
5# if PCI_WHITELIST is empty assume device is whitelistened
6# if PCI_BLACKLIST is empty assume device is NOT blacklistened
7# Params:
8# $1 - PCI BDF
9function pci_can_use() {
10	local i
11
12	# The '\ ' part is important
13	if [[ " $PCI_BLACKLIST " =~ \ $1\  ]]; then
14		return 1
15	fi
16
17	if [[ -z "$PCI_WHITELIST" ]]; then
18		#no whitelist specified, bind all devices
19		return 0
20	fi
21
22	for i in $PCI_WHITELIST; 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"]="${pci_bus_cache["$vendor"]:+${pci_bus_cache["$vendor"]} }$pci"
53		pci_bus_cache["$device"]="${pci_bus_cache["$device"]:+${pci_bus_cache["$device"]} }$pci"
54		pci_bus_cache["$vendor:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci"
55
56		pci_ids_vendor["$pci"]=$vendor
57		pci_ids_device["$pci"]=$device
58	fi
59}
60
61cache_pci_bus_sysfs() {
62	[[ -e /sys/bus/pci/devices ]] || return 1
63
64	cache_pci_init || return 0
65
66	local pci
67	local class vendor device
68
69	for pci in /sys/bus/pci/devices/*; do
70		class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device")
71		cache_pci "${pci##*/}" "$class" "$vendor" "$device"
72	done
73}
74
75cache_pci_bus_lspci() {
76	hash lspci 2> /dev/null || return 1
77
78	cache_pci_init || return 0
79
80	local dev
81	while read -ra dev; do
82		dev=("${dev[@]//\"/}")
83		# lspci splits ls byte of the class (prog. interface) into a separate
84		# field if it's != 0. Look for it and normalize the value to fit with
85		# what kernel exposes under sysfs.
86		if [[ ${dev[*]} =~ -p([0-9]+) ]]; then
87			dev[1]+=${BASH_REMATCH[1]}
88		else
89			dev[1]+=00
90		fi
91		# pci class vendor device
92		cache_pci "${dev[@]::4}"
93	done < <(lspci -Dnmm)
94}
95
96cache_pci_bus_pciconf() {
97	hash pciconf 2> /dev/null || return 1
98
99	cache_pci_init || return 0
100
101	local class vd vendor device
102	local pci domain bus device function
103
104	while read -r pci class _ vd _; do
105		IFS=":" read -r domain bus device function _ <<< "${pci##*pci}"
106		pci=$(printf '%04x:%02x:%02x:%x' \
107			"$domain" "$bus" "$device" "$function")
108		class=$(printf '0x%06x' $((class)))
109		vendor=$(printf '0x%04x' $((vd & 0xffff)))
110		device=$(printf '0x%04x' $(((vd >> 16) & 0xffff)))
111
112		cache_pci "$pci" "$class" "$vendor" "$device"
113	done < <(pciconf -l)
114}
115
116cache_pci_bus() {
117	case "$(uname -s)" in
118		Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;;
119		FreeBSD) cache_pci_bus_pciconf ;;
120	esac
121}
122
123iter_all_pci_sysfs() {
124	cache_pci_bus_sysfs || return 1
125
126	# default to class of the nvme devices
127	local find=${1:-0x010802} findx=$2
128	local pci pcis
129
130	[[ -n ${pci_bus_cache["$find"]} ]] || return 0
131	read -ra pcis <<< "${pci_bus_cache["$find"]}"
132
133	if ((findx)); then
134		printf '%s\n' "${pcis[@]::findx}"
135	else
136		printf '%s\n' "${pcis[@]}"
137	fi
138}
139
140# This function will ignore PCI PCI_WHITELIST and PCI_BLACKLIST
141function iter_all_pci_class_code() {
142	local class
143	local subclass
144	local progif
145	class="$(printf %02x $((0x$1)))"
146	subclass="$(printf %02x $((0x$2)))"
147	progif="$(printf %02x $((0x$3)))"
148
149	if hash lspci &> /dev/null; then
150		if [ "$progif" != "00" ]; then
151			lspci -mm -n -D \
152				| grep -i -- "-p${progif}" \
153				| awk -v cc="\"${class}${subclass}\"" -F " " \
154					'{if (cc ~ $2) print $1}' | tr -d '"'
155		else
156			lspci -mm -n -D \
157				| awk -v cc="\"${class}${subclass}\"" -F " " \
158					'{if (cc ~ $2) print $1}' | tr -d '"'
159		fi
160	elif hash pciconf &> /dev/null; then
161		local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \
162			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
163		printf "%04x:%02x:%02x:%x\n" ${addr[0]} ${addr[1]} ${addr[2]} ${addr[3]}
164	elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then
165		:
166	else
167		echo "Missing PCI enumeration utility" >&2
168		exit 1
169	fi
170}
171
172# This function will ignore PCI PCI_WHITELIST and PCI_BLACKLIST
173function iter_all_pci_dev_id() {
174	local ven_id
175	local dev_id
176	ven_id="$(printf %04x $((0x$1)))"
177	dev_id="$(printf %04x $((0x$2)))"
178
179	if hash lspci &> /dev/null; then
180		lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \
181			'{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"'
182	elif hash pciconf &> /dev/null; then
183		local addr=($(pciconf -l | grep -i "chip=0x${dev_id}${ven_id}" \
184			| cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' '))
185		printf "%04x:%02x:%02x:%x\n" ${addr[0]} ${addr[1]} ${addr[2]} ${addr[3]}
186	elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then
187		:
188	else
189		echo "Missing PCI enumeration utility" >&2
190		exit 1
191	fi
192}
193
194function iter_pci_dev_id() {
195	local bdf=""
196
197	for bdf in $(iter_all_pci_dev_id "$@"); do
198		if pci_can_use "$bdf"; then
199			echo "$bdf"
200		fi
201	done
202}
203
204# This function will filter out PCI devices using PCI_WHITELIST and PCI_BLACKLIST
205# See function pci_can_use()
206function iter_pci_class_code() {
207	local bdf=""
208
209	for bdf in $(iter_all_pci_class_code "$@"); do
210		if pci_can_use "$bdf"; then
211			echo "$bdf"
212		fi
213	done
214}
215
216function nvme_in_userspace() {
217	# Check used drivers. If it's not vfio-pci or uio-pci-generic
218	# then most likely PCI_WHITELIST option was used for setup.sh
219	# and we do not want to use that disk.
220
221	local bdf bdfs
222	local nvmes
223
224	if [[ -n ${pci_bus_cache["0x010802"]} ]]; then
225		nvmes=(${pci_bus_cache["0x010802"]})
226	else
227		nvmes=($(iter_pci_class_code 01 08 02))
228	fi
229
230	for bdf in "${nvmes[@]}"; do
231		if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \
232			|| [[ $(uname -s) == FreeBSD && $(pciconf -l "pci$bdf") == nvme* ]]; then
233			continue
234		fi
235		bdfs+=("$bdf")
236	done
237	((${#bdfs[@]})) || return 1
238	printf '%s\n' "${bdfs[@]}"
239}
240