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 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:$device"]="${pci_bus_cache["$vendor:$device"]:+${pci_bus_cache["$vendor:$device"]} }$pci" 53 54 pci_ids_vendor["$pci"]=$vendor 55 pci_ids_device["$pci"]=$device 56 fi 57} 58 59cache_pci_bus_sysfs() { 60 [[ -e /sys/bus/pci/devices ]] || return 1 61 62 cache_pci_init || return 0 63 64 local pci 65 local class vendor device 66 67 for pci in /sys/bus/pci/devices/*; do 68 class=$(< "$pci/class") vendor=$(< "$pci/vendor") device=$(< "$pci/device") 69 cache_pci "${pci##*/}" "$class" "$vendor" "$device" 70 done 71} 72 73cache_pci_bus_lspci() { 74 hash lspci 2> /dev/null || return 1 75 76 cache_pci_init || return 0 77 78 local dev 79 while read -ra dev; do 80 dev=("${dev[@]//\"/}") 81 # lspci splits ls byte of the class (prog. interface) into a separate 82 # field if it's != 0. Look for it and normalize the value to fit with 83 # what kernel exposes under sysfs. 84 if [[ ${dev[*]} =~ -p([0-9]+) ]]; then 85 dev[1]+=${BASH_REMATCH[1]} 86 else 87 dev[1]+=00 88 fi 89 # pci class vendor device 90 cache_pci "${dev[@]::4}" 91 done < <(lspci -Dnmm) 92} 93 94cache_pci_bus_pciconf() { 95 hash pciconf 2> /dev/null || return 1 96 97 cache_pci_init || return 0 98 99 local class vd vendor device 100 local pci domain bus device function 101 102 while read -r pci class _ vd _; do 103 IFS=":" read -r domain bus device function _ <<< "${pci##*pci}" 104 pci=$(printf '%04x:%02x:%02x:%x' \ 105 "$domain" "$bus" "$device" "$function") 106 class=$(printf '0x%06x' $((class))) 107 vendor=$(printf '0x%04x' $((vd & 0xffff))) 108 device=$(printf '0x%04x' $(((vd >> 16) & 0xffff))) 109 110 cache_pci "$pci" "$class" "$vendor" "$device" 111 done < <(pciconf -l) 112} 113 114cache_pci_bus() { 115 case "$(uname -s)" in 116 Linux) cache_pci_bus_lspci || cache_pci_bus_sysfs ;; 117 FreeBSD) cache_pci_bus_pciconf ;; 118 esac 119} 120 121iter_all_pci_sysfs() { 122 cache_pci_bus_sysfs || return 1 123 124 # default to class of the nvme devices 125 local find=${1:-0x010802} findx=$2 126 local pci pcis 127 128 [[ -n ${pci_bus_cache["$find"]} ]] || return 0 129 read -ra pcis <<< "${pci_bus_cache["$find"]}" 130 131 if ((findx)); then 132 printf '%s\n' "${pcis[@]::findx}" 133 else 134 printf '%s\n' "${pcis[@]}" 135 fi 136} 137 138# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED 139function iter_all_pci_class_code() { 140 local class 141 local subclass 142 local progif 143 class="$(printf %02x $((0x$1)))" 144 subclass="$(printf %02x $((0x$2)))" 145 progif="$(printf %02x $((0x$3)))" 146 147 if hash lspci &> /dev/null; then 148 if [ "$progif" != "00" ]; then 149 lspci -mm -n -D \ 150 | grep -i -- "-p${progif}" \ 151 | awk -v cc="\"${class}${subclass}\"" -F " " \ 152 '{if (cc ~ $2) print $1}' | tr -d '"' 153 else 154 lspci -mm -n -D \ 155 | awk -v cc="\"${class}${subclass}\"" -F " " \ 156 '{if (cc ~ $2) print $1}' | tr -d '"' 157 fi 158 elif hash pciconf &> /dev/null; then 159 local addr=($(pciconf -l | grep -i "class=0x${class}${subclass}${progif}" \ 160 | cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' ')) 161 printf "%04x:%02x:%02x:%x\n" ${addr[0]} ${addr[1]} ${addr[2]} ${addr[3]} 162 elif iter_all_pci_sysfs "$(printf '0x%06x' $((0x$progif | 0x$subclass << 8 | 0x$class << 16)))"; then 163 : 164 else 165 echo "Missing PCI enumeration utility" >&2 166 exit 1 167 fi 168} 169 170# This function will ignore PCI PCI_ALLOWED and PCI_BLOCKED 171function iter_all_pci_dev_id() { 172 local ven_id 173 local dev_id 174 ven_id="$(printf %04x $((0x$1)))" 175 dev_id="$(printf %04x $((0x$2)))" 176 177 if hash lspci &> /dev/null; then 178 lspci -mm -n -D | awk -v ven="\"$ven_id\"" -v dev="\"${dev_id}\"" -F " " \ 179 '{if (ven ~ $3 && dev ~ $4) print $1}' | tr -d '"' 180 elif hash pciconf &> /dev/null; then 181 local addr=($(pciconf -l | grep -i "chip=0x${dev_id}${ven_id}" \ 182 | cut -d$'\t' -f1 | sed -e 's/^[a-zA-Z0-9_]*@pci//g' | tr ':' ' ')) 183 printf "%04x:%02x:%02x:%x\n" ${addr[0]} ${addr[1]} ${addr[2]} ${addr[3]} 184 elif iter_all_pci_sysfs "0x$ven_id:0x$dev_id"; then 185 : 186 else 187 echo "Missing PCI enumeration utility" >&2 188 exit 1 189 fi 190} 191 192function iter_pci_dev_id() { 193 local bdf="" 194 195 for bdf in $(iter_all_pci_dev_id "$@"); do 196 if pci_can_use "$bdf"; then 197 echo "$bdf" 198 fi 199 done 200} 201 202# This function will filter out PCI devices using PCI_ALLOWED and PCI_BLOCKED 203# See function pci_can_use() 204function iter_pci_class_code() { 205 local bdf="" 206 207 for bdf in $(iter_all_pci_class_code "$@"); do 208 if pci_can_use "$bdf"; then 209 echo "$bdf" 210 fi 211 done 212} 213 214function nvme_in_userspace() { 215 # Check used drivers. If it's not vfio-pci or uio-pci-generic 216 # then most likely PCI_ALLOWED option was used for setup.sh 217 # and we do not want to use that disk. 218 219 local bdf bdfs 220 local nvmes 221 222 if [[ -n ${pci_bus_cache["0x010802"]} ]]; then 223 nvmes=(${pci_bus_cache["0x010802"]}) 224 else 225 nvmes=($(iter_pci_class_code 01 08 02)) 226 fi 227 228 for bdf in "${nvmes[@]}"; do 229 if [[ -e /sys/bus/pci/drivers/nvme/$bdf ]] \ 230 || [[ $(uname -s) == FreeBSD && $(pciconf -l "pci${bdf/./:}") == nvme* ]]; then 231 continue 232 fi 233 bdfs+=("$bdf") 234 done 235 ((${#bdfs[@]})) || return 1 236 printf '%s\n' "${bdfs[@]}" 237} 238 239cmp_versions() { 240 local ver1 ver1_l 241 local ver2 ver2_l 242 243 IFS=".-:" read -ra ver1 <<< "$1" 244 IFS=".-:" read -ra ver2 <<< "$3" 245 local op=$2 246 247 ver1_l=${#ver1[@]} 248 ver2_l=${#ver2[@]} 249 250 local lt=0 gt=0 eq=0 v 251 case "$op" in 252 "<") : $((eq = gt = 1)) ;; 253 ">") : $((eq = lt = 1)) ;; 254 "<=") : $((gt = 1)) ;; 255 ">=") : $((lt = 1)) ;; 256 "==") : $((lt = gt = 1)) ;; 257 esac 258 259 decimal() ( 260 local d=${1,,} 261 if [[ $d =~ ^[0-9]+$ ]]; then 262 echo $((10#$d)) 263 elif [[ $d =~ ^0x || $d =~ ^[a-f0-9]+$ ]]; then 264 d=${d/0x/} 265 echo $((0x$d)) 266 else 267 echo 0 268 fi 269 ) 270 271 for ((v = 0; v < (ver1_l > ver2_l ? ver1_l : ver2_l); v++)); do 272 ver1[v]=$(decimal "${ver1[v]}") 273 ver2[v]=$(decimal "${ver2[v]}") 274 ((ver1[v] > ver2[v])) && return "$gt" 275 ((ver1[v] < ver2[v])) && return "$lt" 276 done 277 [[ ${ver1[*]} == "${ver2[*]}" ]] && return "$eq" 278} 279 280lt() { cmp_versions "$1" "<" "$2"; } 281gt() { cmp_versions "$1" ">" "$2"; } 282le() { cmp_versions "$1" "<=" "$2"; } 283ge() { cmp_versions "$1" ">=" "$2"; } 284eq() { cmp_versions "$1" "==" "$2"; } 285neq() { ! eq "$1" "$2"; } 286