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