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 14 15shopt -s extglob 16 17function pci_can_use() { 18 local i 19 20 # The '\ ' part is important 21 if [[ " $PCI_BLOCKED " =~ \ $1\ ]]; then 22 return 1 23 fi 24 25 if [[ -z "$PCI_ALLOWED" ]]; then 26 #no allow list specified, bind all devices 27 return 0 28 fi 29 30 for i in $PCI_ALLOWED; do 31 if [ "$i" == "$1" ]; then 32 return 0 33 fi 34 done 35 36 return 1 37} 38 39resolve_mod() { 40 local mod=$1 aliases=() 41 42 if aliases=($(modprobe -R "$mod")); then 43 echo "${aliases[0]}" 44 else 45 echo "unknown" 46 fi 2> /dev/null 47} 48 49cache_pci_init() { 50 local -gA pci_bus_cache 51 local -gA pci_ids_vendor 52 local -gA pci_ids_device 53 local -gA pci_bus_driver 54 local -gA pci_mod_driver 55 local -gA pci_mod_resolved 56 local -gA pci_iommu_groups 57 local -ga iommu_groups 58 59 [[ -z ${pci_bus_cache[*]} || $CMD == reset ]] || return 1 60 61 pci_bus_cache=() 62 pci_bus_ids_vendor=() 63 pci_bus_ids_device=() 64 pci_bus_driver=() 65 pci_mod_driver=() 66 pci_mod_resolved=() 67 pci_iommu_groups=() 68 iommu_groups=() 69} 70 71cache_pci() { 72 local pci=$1 class=$2 vendor=$3 device=$4 driver=$5 mod=$6 73 74 if [[ -n $class ]]; then 75 class=0x${class/0x/} 76 pci_bus_cache["$class"]="${pci_bus_cache["$class"]:+${pci_bus_cache["$class"]} }$pci" 77 fi 78 if [[ -n $vendor && -n $device ]]; then 79 vendor=0x${vendor/0x/} device=0x${device/0x/} 80 pci_bus_cache["$vendor:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci" 81 82 pci_ids_vendor["$pci"]=$vendor 83 pci_ids_device["$pci"]=$device 84 fi 85 if [[ -n $driver ]]; then 86 pci_bus_driver["$pci"]=$driver 87 fi 88 if [[ -n $mod ]]; then 89 pci_mod_driver["$pci"]=$mod 90 pci_mod_resolved["$pci"]=$(resolve_mod "$mod") 91 fi 92 93 cache_pci_iommu_group "$pci" 94} 95 96cache_iommu_group() { 97 local iommu_group=$1 pcis=() pci 98 99 [[ -e /sys/kernel/iommu_groups/$iommu_group/type ]] || return 0 100 101 local -n _iommu_group_ref=_iommu_group_$iommu_group 102 103 iommu_groups[iommu_group]="_iommu_group_${iommu_group}[@]" 104 105 for pci in "/sys/kernel/iommu_groups/$iommu_group/devices/"*; do 106 pci=${pci##*/} 107 [[ -n ${pci_iommu_groups["$pci"]} ]] && continue 108 pci_iommu_groups["$pci"]=$iommu_group 109 _iommu_group_ref+=("$pci") 110 done 111 112} 113 114cache_pci_iommu_group() { 115 local pci=$1 iommu_group 116 117 [[ -e /sys/bus/pci/devices/$pci/iommu_group ]] || return 0 118 119 iommu_group=$(readlink -f "/sys/bus/pci/devices/$pci/iommu_group") 120 iommu_group=${iommu_group##*/} 121 122 cache_iommu_group "$iommu_group" 123} 124 125is_iommu_enabled() { 126 [[ -e /sys/kernel/iommu_groups/0 ]] && return 0 127 [[ -e /sys/module/vfio/parameters/enable_unsafe_noiommu_mode ]] || return 1 128 [[ $(< /sys/module/vfio/parameters/enable_unsafe_noiommu_mode) == Y ]] 129} 130 131cache_pci_bus_sysfs() { 132 [[ -e /sys/bus/pci/devices ]] || return 1 133 134 cache_pci_init || return 0 135 136 local pci 137 local class vendor device driver mod 138 139 for pci in /sys/bus/pci/devices/*; do 140 class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device") driver="" mod="" 141 driver=$(get_pci_driver_sysfs "${pci##*/}") 142 if [[ -e $pci/modalias ]]; then 143 mod=$(< "$pci/modalias") 144 fi 145 cache_pci "${pci##*/}" "$class" "$vendor" "$device" "$driver" "$mod" 146 done 147} 148 149cache_pci_bus_lspci() { 150 hash lspci 2> /dev/null || return 1 151 152 cache_pci_init || return 0 153 154 local dev 155 while read -ra dev; do 156 dev=("${dev[@]//\"/}") 157 # lspci splits ls byte of the class (prog. interface) into a separate 158 # field if it's != 0. Look for it and normalize the value to fit with 159 # what kernel exposes under sysfs. 160 if [[ ${dev[*]} =~ -p([0-9]+) ]]; then 161 dev[1]+=${BASH_REMATCH[1]} 162 else 163 dev[1]+=00 164 fi 165 # pci class vendor device driver 166 # lspci supports driver listing only under Linux, however, it's not 167 # included when specific display mode (i.e. -mm) is in use, even if 168 # extra -k is slapped on the cmdline. So with that in mind, just 169 # get that info from sysfs. 170 cache_pci "${dev[@]::4}" "$(get_pci_driver_sysfs "${dev[0]}")" 171 done < <(lspci -Dnmm) 172} 173 174cache_pci_bus_pciconf() { 175 hash pciconf 2> /dev/null || return 1 176 177 cache_pci_init || return 0 178 179 local class vendor device 180 local pci pci_info 181 local chip driver 182 183 while read -r pci pci_info; do 184 driver=${pci%@*} 185 pci=${pci#*:} pci=${pci%:} # E.g.: nvme0@pci0:0:16:0: -> 0:16:0 186 source <(echo "$pci_info") 187 # pciconf under FreeBSD 13.1 provides vendor and device IDs in its 188 # output under separate, dedicated fields. For 12.x they need to 189 # be extracted from the chip field. 190 if [[ -n $chip ]]; then 191 vendor=$(printf '0x%04x' $((chip & 0xffff))) 192 device=$(printf '0x%04x' $(((chip >> 16) & 0xffff))) 193 fi 194 cache_pci "$pci" "$class" "$vendor" "$device" "$driver" 195 done < <(pciconf -l) 196} 197 198get_pci_driver_sysfs() { 199 local pci=/sys/bus/pci/devices/$1 driver 200 201 if [[ -e $pci/driver ]]; then 202 driver=$(readlink -f "$pci/driver") driver=${driver##*/} 203 fi 204 echo "$driver" 205} 206 207cache_pci_bus() { 208 case "$(uname -s)" in 209 Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;; 210 FreeBSD) cache_pci_bus_pciconf ;; 211 esac 212} 213 214iter_all_pci_sysfs() { 215 cache_pci_bus_sysfs || return 1 216 217 # default to class of the nvme devices 218 local find=${1:-0x010802} findx=$2 219 local pci pcis 220 221 [[ -n ${pci_bus_cache["$find"]} ]] || return 0 222 read -ra pcis <<< "${pci_bus_cache["$find"]}" 223 224 if ((findx)); then 225 printf '%s\n' "${pcis[@]::findx}" 226 else 227 printf '%s\n' "${pcis[@]}" 228 fi 229} 230 231# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED 232function iter_all_pci_class_code() { 233 local class 234 local subclass 235 local progif 236 class="$(printf %02x $((0x$1)))" 237 subclass="$(printf %02x $((0x$2)))" 238 progif="$(printf %02x $((0x$3)))" 239 240 if hash lspci &> /dev/null; then 241 if [ "$progif" != "00" ]; then 242 lspci -mm -n -D \ 243 | grep -i -- "-p${progif}" \ 244 | awk -v cc="\"${class}${subclass}\"" -F " " \ 245 '{if (cc ~ $2) print $1}' | tr -d '"' 246 else 247 lspci -mm -n -D \ 248 | awk -v cc="\"${class}${subclass}\"" -F " " \ 249 '{if (cc ~ $2) print $1}' | tr -d '"' 250 fi 251 elif hash pciconf &> /dev/null; then 252 local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \ 253 | cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' ')) 254 echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}" 255 elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then 256 : 257 else 258 echo "Missing PCI enumeration utility" >&2 259 exit 1 260 fi 261} 262 263# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED 264function iter_all_pci_dev_id() { 265 local ven_id 266 local dev_id 267 ven_id="$(printf %04x $((0x$1)))" 268 dev_id="$(printf %04x $((0x$2)))" 269 270 if hash lspci &> /dev/null; then 271 lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \ 272 '{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"' 273 elif hash pciconf &> /dev/null; then 274 local addr=($(pciconf -l | grep -iE "chip=0x${dev_id}${ven_id}|vendor=0x$ven_id device=0x$dev_id" \ 275 | cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' ')) 276 echo "${addr[0]}:${addr[1]}:${addr[2]}:${addr[3]}" 277 elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then 278 : 279 else 280 echo "Missing PCI enumeration utility" >&2 281 exit 1 282 fi 283} 284 285function iter_pci_dev_id() { 286 local bdf="" 287 288 for bdf in $(iter_all_pci_dev_id "$@"); do 289 if pci_can_use "$bdf"; then 290 echo "$bdf" 291 fi 292 done 293} 294 295# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED 296# See function pci_can_use() 297function iter_pci_class_code() { 298 local bdf="" 299 300 for bdf in $(iter_all_pci_class_code "$@"); do 301 if pci_can_use "$bdf"; then 302 echo "$bdf" 303 fi 304 done 305} 306 307function nvme_in_userspace() { 308 # Check used drivers. If it's not vfio-pci or uio-pci-generic 309 # then most likely PCI_ALLOWED option was used for setup.sh 310 # and we do not want to use that disk. 311 312 local bdf bdfs 313 local nvmes 314 315 if [[ -n ${pci_bus_cache["0x010802"]} ]]; then 316 nvmes=(${pci_bus_cache["0x010802"]}) 317 else 318 nvmes=($(iter_pci_class_code 01 08 02)) 319 fi 320 321 for bdf in "${nvmes[@]}"; do 322 if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \ 323 || [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then 324 continue 325 fi 326 bdfs+=("$bdf") 327 done 328 ((${#bdfs[@]})) || return 1 329 printf '%s\n' "${bdfs[@]}" 330} 331 332cmp_versions() { 333 local ver1 ver1_l 334 local ver2 ver2_l 335 336 IFS=".-:" read -ra ver1 <<< "$1" 337 IFS=".-:" read -ra ver2 <<< "$3" 338 local op=$2 339 340 ver1_l=${#ver1[@]} 341 ver2_l=${#ver2[@]} 342 343 local lt=0 gt=0 eq=0 v 344 case "$op" in 345 "<") : $((eq = gt = 1)) ;; 346 ">") : $((eq = lt = 1)) ;; 347 "<=") : $((gt = 1)) ;; 348 ">=") : $((lt = 1)) ;; 349 "==") : $((lt = gt = 1)) ;; 350 esac 351 352 decimal() ( 353 local d=${1,,} 354 if [[ $d =~ ^[0-9]+$ ]]; then 355 echo $((10#$d)) 356 elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then 357 d=${d/0x/} 358 echo $((0x$d)) 359 else 360 echo 0 361 fi 362 ) 363 364 for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do 365 ver1[v]=$(decimal "${ver1[v]}") 366 ver2[v]=$(decimal "${ver2[v]}") 367 ((ver1[v] > ver2[v])) && return "$gt" 368 ((ver1[v] < ver2[v])) && return "$lt" 369 done 370 [[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq" 371} 372 373lt() { cmp_versions "$1" "<" "$2"; } 374gt() { cmp_versions "$1" ">" "$2"; } 375le() { cmp_versions "$1" "<=" "$2"; } 376ge() { cmp_versions "$1" ">=" "$2"; } 377eq() { cmp_versions "$1" "==" "$2"; } 378neq() { ! eq "$1" "$2"; } 379 380block_in_use() { 381 local block=$1 pt 382 # Skip devices that are in use - simple blkid it to see if 383 # there's any metadata (pt, fs, etc.) present on the drive. 384 # FIXME: Special case to ignore atari as a potential false 385 # positive: 386 # https://github.com/spdk/spdk/issues/2079 387 # Devices with SPDK's GPT part type are not considered to 388 # be in use. 389 390 if "$rootdir/scripts/spdk-gpt.py" "$block"; then 391 return 1 392 fi 393 394 if ! pt=$(blkid -s PTTYPE -o value "/dev/${block##*/}"); then 395 return 1 396 elif [[ $pt == atari ]]; then 397 return 1 398 fi 399 400 # Devices used in SPDK tests always create GPT partitions 401 # with label containing SPDK_TEST string. Such devices were 402 # part of the tests before, so are not considered in use. 403 if [[ $pt == gpt ]] && parted "/dev/${block##*/}" -ms print | grep -q "SPDK_TEST"; then 404 return 1 405 fi 406 407 return 0 408} 409 410get_spdk_gpt_old() { 411 local spdk_guid 412 413 [[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1 414 415 GPT_H="$rootdir/module/bdev/gpt/gpt.h" 416 IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID_OLD "$GPT_H") 417 spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/} 418 419 echo "$spdk_guid" 420} 421 422get_spdk_gpt() { 423 local spdk_guid 424 425 [[ -e $rootdir/module/bdev/gpt/gpt.h ]] || return 1 426 427 GPT_H="$rootdir/module/bdev/gpt/gpt.h" 428 IFS="()" read -r _ spdk_guid _ < <(grep -w SPDK_GPT_PART_TYPE_GUID "$GPT_H") 429 spdk_guid=${spdk_guid//, /-} spdk_guid=${spdk_guid//0x/} 430 431 echo "$spdk_guid" 432} 433 434map_supported_devices() { 435 local type=${1^^} 436 local ids dev_types dev_type dev_id bdf bdfs vmd _vmd 437 438 local -gA nvme_d 439 local -gA ioat_d dsa_d iaa_d 440 local -gA virtio_d 441 local -gA vmd_d nvme_vmd_d vmd_nvme_d vmd_nvme_count 442 local -gA all_devices_d types_d all_devices_type_d 443 444 ids+="PCI_DEVICE_ID_INTEL_IOAT" dev_types+="IOAT" 445 ids+="|PCI_DEVICE_ID_INTEL_DSA" dev_types+="|DSA" 446 ids+="|PCI_DEVICE_ID_INTEL_IAA" dev_types+="|IAA" 447 ids+="|PCI_DEVICE_ID_VIRTIO" dev_types+="|VIRTIO" 448 ids+="|PCI_DEVICE_ID_INTEL_VMD" dev_types+="|VMD" 449 ids+="|SPDK_PCI_CLASS_NVME" dev_types+="|NVME" 450 451 [[ -e $rootdir/include/spdk/pci_ids.h ]] || return 1 452 453 ((${#pci_bus_cache[@]} == 0)) && cache_pci_bus 454 455 while read -r _ dev_type dev_id; do 456 [[ $dev_type == *$type* ]] || continue 457 bdfs=(${pci_bus_cache["0x8086:$dev_id"]}) 458 [[ $dev_type == *NVME* ]] && bdfs=(${pci_bus_cache["$dev_id"]}) 459 [[ $dev_type == *VIRT* ]] && bdfs=(${pci_bus_cache["0x1af4:$dev_id"]}) 460 [[ $dev_type =~ ($dev_types) ]] && dev_type=${BASH_REMATCH[1],,} 461 types_d["$dev_type"]=1 462 for bdf in "${bdfs[@]}"; do 463 eval "${dev_type}_d[$bdf]=0" 464 all_devices_d["$bdf"]=0 465 all_devices_type_d["$bdf"]=$dev_type 466 done 467 done < <(grep -E "$ids" "$rootdir/include/spdk/pci_ids.h") 468 469 # Rebuild vmd refs from the very cratch to not have duplicates in case we were called 470 # multiple times. 471 unset -v "${!_vmd_@}" 472 473 for bdf in "${!nvme_d[@]}"; do 474 vmd=$(is_nvme_behind_vmd "$bdf") && _vmd=${vmd//[:.]/_} || continue 475 nvme_vmd_d["$bdf"]=$vmd 476 vmd_nvme_d["$vmd"]="_vmd_${_vmd}_nvmes[@]" 477 ((++vmd_nvme_count["$vmd"])) 478 eval "_vmd_${_vmd}_nvmes+=($bdf)" 479 done 480} 481 482is_nvme_behind_vmd() { 483 local nvme_bdf=$1 dev_path 484 485 IFS="/" read -ra dev_path < <(readlink -f "/sys/bus/pci/devices/$nvme_bdf") 486 487 for dev in "${dev_path[@]}"; do 488 [[ -n $dev && -n ${vmd_d["$dev"]} ]] && echo $dev && return 0 489 done 490 return 1 491} 492 493is_nvme_iommu_shared_with_vmd() { 494 local nvme_bdf=$1 vmd 495 496 # This use-case is quite specific to vfio-pci|iommu setup 497 is_iommu_enabled || return 1 498 499 [[ -n ${nvme_vmd_d["$nvme_bdf"]} ]] || return 1 500 # nvme is behind VMD ... 501 ((pci_iommu_groups["$nvme_bdf"] == pci_iommu_groups["${nvme_vmd_d["$nvme_bdf"]}"])) || return 1 502 # ... and it shares iommu_group with it 503} 504 505kmsg() { 506 ((UID == 0)) || return 0 507 [[ -w /dev/kmsg && $(< /proc/sys/kernel/printk_devkmsg) == on ]] || return 0 508 echo "$*" > /dev/kmsg 509} 510 511function get_block_dev_from_bdf() { 512 local bdf=$1 513 local block blocks=() ctrl sub 514 515 for block in /sys/block/!(nvme*); do 516 if [[ $(readlink -f "$block/device") == *"/$bdf/"* ]]; then 517 blocks+=("${block##*/}") 518 fi 519 done 520 521 blocks+=($(get_block_dev_from_nvme "$bdf")) 522 523 printf '%s\n' "${blocks[@]}" 524} 525 526function get_block_dev_from_nvme() { 527 local bdf=$1 block ctrl sub 528 529 for ctrl in /sys/class/nvme/nvme*; do 530 [[ -e $ctrl/address && $(< "$ctrl/address") == "$bdf" ]] || continue 531 sub=$(< "$ctrl/subsysnqn") && break 532 done 533 534 [[ -n $sub ]] || return 0 535 536 for block in /sys/block/nvme*; do 537 [[ -e $block/hidden && $(< "$block/hidden") == 1 ]] && continue 538 if [[ -e $block/device/subsysnqn && $(< "$block/device/subsysnqn") == "$sub" ]]; then 539 echo "${block##*/}" 540 fi 541 done 542} 543 544if [[ -e "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" ]]; then 545 # Adjust uname to report the operating system as WSL, Msys or Cygwin 546 # and the kernel name as Windows. Define kill() to invoke the SIGTERM 547 # handler before causing a hard stop with TerminateProcess. 548 source "$CONFIG_WPDK_DIR/bin/wpdk_common.sh" 549fi 550 551# Make sure we have access to proper binaries installed in pkgdep/common.sh 552if [[ -e /etc/opt/spdk-pkgdep/paths/export.sh ]]; then 553 source /etc/opt/spdk-pkgdep/paths/export.sh 554fi > /dev/null 555