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%:} # E.g.: nvme0@pci0:0:16:0: -> 0:16:0 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 fi 201 echo "$driver" 202} 203 204cache_pci_bus() { 205 case "$(uname -s)" in 206 Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;; 207 FreeBSD) cache_pci_bus_pciconf ;; 208 esac 209} 210 211iter_all_pci_sysfs() { 212 cache_pci_bus_sysfs || return 1 213 214 # default to class of the nvme devices 215 local find=${1:-0x010802} findx=$2 216 local pci pcis 217 218 [[ -n ${pci_bus_cache["$find"]} ]] || return 0 219 read -ra pcis <<< "${pci_bus_cache["$find"]}" 220 221 if ((findx)); then 222 printf '%s\n' "${pcis[@]::findx}" 223 else 224 printf '%s\n' "${pcis[@]}" 225 fi 226} 227 228# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED 229function iter_all_pci_class_code() { 230 local class 231 local subclass 232 local progif 233 class="$(printf %02x $((0x$1)))" 234 subclass="$(printf %02x $((0x$2)))" 235 progif="$(printf %02x $((0x$3)))" 236 237 if hash lspci &> /dev/null; then 238 if [ "$progif" != "00" ]; then 239 lspci -mm -n -D \ 240 | grep -i -- "-p${progif}" \ 241 | awk -v cc="\"${class}${subclass}\"" -F " " \ 242 '{if (cc ~ $2) print $1}' | tr -d '"' 243 else 244 lspci -mm -n -D \ 245 | awk -v cc="\"${class}${subclass}\"" -F " " \ 246 '{if (cc ~ $2) print $1}' | tr -d '"' 247 fi 248 elif hash pciconf &> /dev/null; then 249 local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \ 250 | cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' ')) 251 echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}" 252 elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then 253 : 254 else 255 echo "Missing PCI enumeration utility" >&2 256 exit 1 257 fi 258} 259 260# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED 261function iter_all_pci_dev_id() { 262 local ven_id 263 local dev_id 264 ven_id="$(printf %04x $((0x$1)))" 265 dev_id="$(printf %04x $((0x$2)))" 266 267 if hash lspci &> /dev/null; then 268 lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \ 269 '{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"' 270 elif hash pciconf &> /dev/null; then 271 local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \ 272 | cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' ')) 273 echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}" 274 elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then 275 : 276 else 277 echo "Missing PCI enumeration utility" >&2 278 exit 1 279 fi 280} 281 282function iter_pci_dev_id() { 283 local bdf="" 284 285 for bdf in $(iter_all_pci_dev_id "$@"); do 286 if pci_can_use "$bdf"; then 287 echo "$bdf" 288 fi 289 done 290} 291 292# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED 293# See function pci_can_use() 294function iter_pci_class_code() { 295 local bdf="" 296 297 for bdf in $(iter_all_pci_class_code "$@"); do 298 if pci_can_use "$bdf"; then 299 echo "$bdf" 300 fi 301 done 302} 303 304function nvme_in_userspace() { 305 # Check used drivers. If it's not vfio-pci or uio-pci-generic 306 # then most likely PCI_ALLOWED option was used for setup.sh 307 # and we do not want to use that disk. 308 309 local bdf bdfs 310 local nvmes 311 312 if [[ -n ${pci_bus_cache["0x010802"]} ]]; then 313 nvmes=(${pci_bus_cache["0x010802"]}) 314 else 315 nvmes=($(iter_pci_class_code 01 08 02)) 316 fi 317 318 for bdf in "${nvmes[@]}"; do 319 if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \ 320 || [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then 321 continue 322 fi 323 bdfs+=("$bdf") 324 done 325 ((${#bdfs[@]})) || return 1 326 printf '%s\n' "${bdfs[@]}" 327} 328 329cmp_versions() { 330 local ver1 ver1_l 331 local ver2 ver2_l 332 333 IFS=".-:" read -ra ver1 <<< "$1" 334 IFS=".-:" read -ra ver2 <<< "$3" 335 local op=$2 336 337 ver1_l=${#ver1[@]} 338 ver2_l=${#ver2[@]} 339 340 local lt=0 gt=0 eq=0 v 341 case "$op" in 342 "<") : $((eq = gt = 1)) ;; 343 ">") : $((eq = lt = 1)) ;; 344 "<=") : $((gt = 1)) ;; 345 ">=") : $((lt = 1)) ;; 346 "==") : $((lt = gt = 1)) ;; 347 esac 348 349 decimal() ( 350 local d=${1,,} 351 if [[ $d =~ ^[0-9]+$ ]]; then 352 echo $((10#$d)) 353 elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then 354 d=${d/0x/} 355 echo $((0x$d)) 356 else 357 echo 0 358 fi 359 ) 360 361 for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do 362 ver1[v]=$(decimal "${ver1[v]}") 363 ver2[v]=$(decimal "${ver2[v]}") 364 ((ver1[v] > ver2[v])) && return "$gt" 365 ((ver1[v] < ver2[v])) && return "$lt" 366 done 367 [[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq" 368} 369 370lt() { cmp_versions "$1" "<" "$2"; } 371gt() { cmp_versions "$1" ">" "$2"; } 372le() { cmp_versions "$1" "<=" "$2"; } 373ge() { cmp_versions "$1" ">=" "$2"; } 374eq() { cmp_versions "$1" "==" "$2"; } 375neq() { ! eq "$1" "$2"; } 376 377block_in_use() { 378 local block=$1 pt 379 # Skip devices that are in use - simple blkid it to see if 380 # there's any metadata (pt, fs, etc.) present on the drive. 381 # FIXME: Special case to ignore atari as a potential false 382 # positive: 383 # https://github.com/spdk/spdk/issues/2079 384 # Devices with SPDK's GPT part type are not considered to 385 # be in use. 386 387 if "$rootdir/scripts/spdk-gpt.py" "$block"; then 388 return 1 389 fi 390 391 if ! pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}"); then 392 return 1 393 elif [[ $pt == atari ]]; then 394 return 1 395 fi 396 397 # Devices used in SPDK tests always create GPT partitions 398 # with label containing SPDK_TEST string. Such devices were 399 # part of the tests before, so are not considered in use. 400 if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then 401 return 1 402 fi 403 404 return 0 405} 406 407get_spdk_gpt_old() { 408 local spdk_guid 409 410 [[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1 411 412 GPT_H="$rootdir/module/bdev/gpt/gpt.h" 413 IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID_OLD "$GPT_H") 414 spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/} 415 416 echo "$spdk_guid" 417} 418 419get_spdk_gpt() { 420 local spdk_guid 421 422 [[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1 423 424 GPT_H="$rootdir/module/bdev/gpt/gpt.h" 425 IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID "$GPT_H") 426 spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/} 427 428 echo "$spdk_guid" 429} 430 431map_supported_devices() { 432 local type=${1^^} 433 local ids dev_types dev_type dev_id bdf bdfs vmd _vmd 434 435 local -gA nvme_d 436 local -gA ioat_d dsa_d iaa_d 437 local -gA virtio_d 438 local -gA vmd_d nvme_vmd_d vmd_nvme_d vmd_nvme_count 439 local -gA all_devices_d types_d all_devices_type_d 440 441 ids+="PCI_DEVICE_ID_INTEL_IOAT" dev_types+="IOAT" 442 ids+="|PCI_DEVICE_ID_INTEL_DSA" dev_types+="|DSA" 443 ids+="|PCI_DEVICE_ID_INTEL_IAA" dev_types+="|IAA" 444 ids+="|PCI_DEVICE_ID_VIRTIO" dev_types+="|VIRTIO" 445 ids+="|PCI_DEVICE_ID_INTEL_VMD" dev_types+="|VMD" 446 ids+="|SPDK_PCI_CLASS_NVME" dev_types+="|NVME" 447 448 [[ -e $rootdir/include/spdk/pci_ids.h ]] || return 1 449 450 ((${#pci_bus_cache[@]} == 0)) && cache_pci_bus 451 452 while read -r _ dev_type dev_id; do 453 [[ $dev_type == *$type* ]] || continue 454 bdfs=(${pci_bus_cache["0x8086:$dev_id"]}) 455 [[ $dev_type == *NVME* ]] && bdfs=(${pci_bus_cache["$dev_id"]}) 456 [[ $dev_type == *VIRT* ]] && bdfs=(${pci_bus_cache["0x1af4:$dev_id"]}) 457 [[ $dev_type =~ ($dev_types) ]] && dev_type=${BASH_REMATCH[1],,} 458 types_d["$dev_type"]=1 459 for bdf in "${bdfs[@]}"; do 460 eval "${dev_type}_d[$bdf]=0" 461 all_devices_d["$bdf"]=0 462 all_devices_type_d["$bdf"]=$dev_type 463 done 464 done < <(grep -E "$ids" "$rootdir/include/spdk/pci_ids.h") 465 466 # Rebuild vmd refs from the very cratch to not have duplicates in case we were called 467 # multiple times. 468 unset -v "${!_vmd_@}" 469 470 for bdf in "${!nvme_d[@]}"; do 471 vmd=$(is_nvme_behind_vmd "$bdf") && _vmd=${vmd//[:.]/_} || continue 472 nvme_vmd_d["$bdf"]=$vmd 473 vmd_nvme_d["$vmd"]="_vmd_${_vmd}_nvmes[@]" 474 ((++vmd_nvme_count["$vmd"])) 475 eval "_vmd_${_vmd}_nvmes+=($bdf)" 476 done 477} 478 479is_nvme_behind_vmd() { 480 local nvme_bdf=$1 dev_path 481 482 IFS="/" read -ra dev_path < <(readlink -f "/sys/bus/pci/devices/$nvme_bdf") 483 484 for dev in "${dev_path[@]}"; do 485 [[ -n $dev && -n ${vmd_d["$dev"]} ]] && echo $dev && return 0 486 done 487 return 1 488} 489 490is_nvme_iommu_shared_with_vmd() { 491 local nvme_bdf=$1 vmd 492 493 # This use-case is quite specific to vfio-pci|iommu setup 494 is_iommu_enabled || return 1 495 496 [[ -n ${nvme_vmd_d["$nvme_bdf"]} ]] || return 1 497 # nvme is behind VMD ... 498 ((pci_iommu_groups["$nvme_bdf"] == pci_iommu_groups["${nvme_vmd_d["$nvme_bdf"]}"])) || return 1 499 # ... and it shares iommu_group with it 500} 501 502kmsg() { 503 ((UID == 0)) || return 0 504 [[ -w /dev/kmsg && $(< /proc/sys/kernel/printk_devkmsg) == on ]] || return 0 505 echo "$*" > /dev/kmsg 506} 507 508if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then 509 # Adjust uname to report the operating system as WSL, Msys or Cygwin 510 # and the kernel name as Windows. Define kill() to invoke the SIGTERM 511 # handler before causing a hard stop with TerminateProcess. 512 source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" 513fi 514 515# Make sure we have access to proper binaries installed in pkgdep/common.sh 516if [[ -e /etc/opt/spdk-pkgdep/paths/export.sh ]]; then 517 source /etc/opt/spdk-pkgdep/paths/export.sh 518fi > /dev/null 519