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