1803 lines
70 KiB
Bash
Executable File
1803 lines
70 KiB
Bash
Executable File
#!/bin/sh
|
||
|
||
# Copyright © 2022-2025 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||
#
|
||
#
|
||
# 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||
# no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||
# Software, provided always that the following conditions are met:
|
||
# 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||
# or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||
# product, solution or offering. The Software is not for use as a standalone product without any
|
||
# reference to Audinate's products;
|
||
# 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||
# or expectation of performance, compatibility, support, updates or security; and
|
||
# 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||
# of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||
# solely in the form of machine-executable object code generated by the source language processor.
|
||
#
|
||
# 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||
# PURPOSE AND NONINFRINGEMENT.
|
||
#
|
||
# 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||
# (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||
# LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||
# EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||
# DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||
# LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||
# IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||
# AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||
#
|
||
# 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||
# OR IMPOSES OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||
# EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||
# GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||
# AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE'S OPTION) TO ONE OF
|
||
# FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||
# 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||
# 4.2. THE REPAIR OF THE SOFTWARE;
|
||
# 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||
# SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||
#
|
||
# 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||
# the trade names, or product names of Audinate.
|
||
#
|
||
# 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||
# indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||
# your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||
# from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||
# liability.
|
||
#
|
||
|
||
# shellcheck disable=SC1091
|
||
if [ -f "$(pwd)"/dep_check_platform.sh ]; then
|
||
. "$(pwd)"/dep_check_platform.sh
|
||
elif [ -f ./development/dep_check_platform.sh ]; then
|
||
. ./development/dep_check_platform.sh
|
||
fi
|
||
|
||
RED_COLOR="\e[01;31m"
|
||
GREEN_COLOR="\e[01;32m"
|
||
YELLOW_COLOR="\e[01;33m"
|
||
BLUE_COLOR="\e[01;34m"
|
||
END_COLOR="\e[0m"
|
||
|
||
red() { printf '%b %s %b' "$RED_COLOR" "$*" "$END_COLOR"; }
|
||
green() { printf '%b %s %b' "$GREEN_COLOR" "$*" "$END_COLOR"; }
|
||
blue() { printf '%b %s %b' "$BLUE_COLOR" "$*" "$END_COLOR"; }
|
||
yellow() { printf '%b %s %b' "$YELLOW_COLOR" "$*" "$END_COLOR"; }
|
||
|
||
logerr() { echo "[ $(red ERROR)] $1"; }
|
||
logwarn() { echo "[$(yellow WARNING)] $1"; }
|
||
loginfo() { echo "[ $(blue INFO)] $1"; }
|
||
logok() { echo "[ $(green OK)] $1"; }
|
||
|
||
USER=$(whoami)
|
||
DEP_PATH="$1"
|
||
PATH_TO_BUNDLE_DIR="${DEP_PATH}"/bundle
|
||
DANTE_JSON="${DEP_PATH}"/dante_data/capability/dante.json
|
||
CONFIG_JSON="${DEP_PATH}"/dante_data/capability/config.json
|
||
DEFER_COA_CHECK=0
|
||
TAB=" "
|
||
|
||
# To use host-provided jq, set JQ to the absolute path of the jq binary.
|
||
JQ="${DEP_PATH}"/development/tools/jq
|
||
|
||
# If a different container runtime is used, its absolute path must be set here.
|
||
# When the container runtime is changed to something other than crun, please disable
|
||
# check_container_runtime_glibc() from running as it targets the specific binary
|
||
# provided by Audinate.
|
||
CONTAINER_RUNTIME=$DEP_PATH/crun
|
||
|
||
PTP_TEST="${DEP_PATH}"/development/tools/ptp_timestamping_test
|
||
PTP_TEST_README="${DEP_PATH}"/development/tools/README_ptp.txt
|
||
|
||
# 2.28 was obtained using:
|
||
#
|
||
# $ objdump -T /usr/bin/crun | grep GLIBC_ | sed 's/.*GLIBC_\([.0-9]*\).*/\1/g' | sort -Vu | tail -n1
|
||
#
|
||
# the general rule is that the system's glibc version must be equal to or greater than the
|
||
# version required by crun, otherwise crun will not be able to start
|
||
REQUIRED_GLIBC_CRUN="2.28"
|
||
REQUIRED_GLIBC_PTP_TEST="2.34"
|
||
|
||
### functions
|
||
|
||
fail() { exit 1; }
|
||
|
||
cmd_exists() { command -v -- "$1" >/dev/null 2>&1; }
|
||
|
||
# Argument validation for JSON utility functions.
|
||
validate_json_args() {
|
||
jsonData="$1"
|
||
field="$2"
|
||
|
||
if [ -z "$jsonData" ] || [ -z "$field" ]; then
|
||
logerr "invalid use of validate_json_args (or calling function): missing arguments"
|
||
fail
|
||
fi
|
||
}
|
||
|
||
# Extracts a field from JSON data using jq.
|
||
#
|
||
# Arguments:
|
||
# $1 - JSON data as a string
|
||
# $2 - absolute path of the field to extract (e.g., ".field.subfield")
|
||
#
|
||
# If the field does not exist or is not valid JSON, it returns "null".
|
||
# While such return value represents an obvious limitation (e.g. it cannot
|
||
# distinguish between a missing field and a field with the value "null"), it
|
||
# is the best we can do given the constraints of the JSON format.
|
||
get_json_field() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -erc "$2" 2>/dev/null
|
||
}
|
||
|
||
get_dante_json_field() {
|
||
field="$1"
|
||
|
||
if [ -z "$DANTE_JSON_DATA" ]; then
|
||
logerr "get_dante_json_field: DANTE_JSON_DATA is empty, this function must be called after load_config_files()"
|
||
fail
|
||
fi
|
||
|
||
get_json_field "$DANTE_JSON_DATA" "$field"
|
||
}
|
||
|
||
get_config_json_field() {
|
||
field="$1"
|
||
|
||
if [ -z "$CONFIG_JSON_DATA" ]; then
|
||
logerr "get_config_json_field: CONFIG_JSON_DATA is empty, this function must be called after load_config_files()"
|
||
fail
|
||
fi
|
||
|
||
get_json_field "$CONFIG_JSON_DATA" "$field"
|
||
}
|
||
|
||
# NOTE: checking whether the field exists and is an array is left to the caller
|
||
get_json_array_length() {
|
||
validate_json_args "$1" "$2"
|
||
|
||
# output the length of the array
|
||
echo "$1" | "$JQ" "$2 | length"
|
||
}
|
||
|
||
# Extract array elements from JSON data as space-separated values.
|
||
get_json_array_elements() {
|
||
validate_json_args "$1" "$2"
|
||
|
||
# check if the field exists and is an array
|
||
if is_json_field_array "$1" "$2"; then
|
||
# output each array element as a compact JSON string, space-separated
|
||
echo "$1" | "$JQ" -cr "$2[]? // empty" | tr '\n' ' ' | sed 's/ $//'
|
||
else
|
||
# field doesn't exist, isn't an array, or is null
|
||
echo ""
|
||
fi
|
||
}
|
||
|
||
get_dante_json_array_elements() {
|
||
field="$1"
|
||
|
||
if [ -z "$DANTE_JSON_DATA" ]; then
|
||
logerr "get_dante_json_array_elements: DANTE_JSON_DATA is empty, this function must be called after load_config_files()"
|
||
fail
|
||
fi
|
||
|
||
get_json_array_elements "$DANTE_JSON_DATA" "$field"
|
||
}
|
||
|
||
get_config_json_array_elements() {
|
||
field="$1"
|
||
|
||
if [ -z "$CONFIG_JSON_DATA" ]; then
|
||
logerr "get_config_json_array_elements: CONFIG_JSON_DATA is empty, this function must be called after load_config_files()"
|
||
fail
|
||
fi
|
||
|
||
get_json_array_elements "$CONFIG_JSON_DATA" "$field"
|
||
}
|
||
|
||
is_json_field_array() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2 | type == \"array\"" >/dev/null 2>&1
|
||
}
|
||
|
||
is_json_field_object() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2 | type == \"object\"" >/dev/null 2>&1
|
||
}
|
||
|
||
is_json_field_string() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2 | type == \"string\"" >/dev/null 2>&1
|
||
}
|
||
|
||
is_json_field_number() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2 | type == \"number\"" >/dev/null 2>&1
|
||
}
|
||
|
||
is_json_field_bool() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2 | type == \"boolean\"" >/dev/null 2>&1
|
||
}
|
||
|
||
is_json_field_null() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2 | type == \"null\"" >/dev/null 2>&1
|
||
}
|
||
|
||
json_field_exists() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -e "$2" >/dev/null 2>&1
|
||
}
|
||
|
||
get_json_field_type() {
|
||
validate_json_args "$1" "$2"
|
||
echo "$1" | "$JQ" -r "$2 | type" 2>/dev/null
|
||
}
|
||
|
||
kernel_info() {
|
||
configFile=""
|
||
isGzipped=0
|
||
|
||
# find kernel config file in order of most common to least common locations
|
||
if [ -f "/proc/config.gz" ]; then
|
||
configFile="/proc/config.gz"
|
||
isGzipped=1
|
||
elif [ -f "/boot/config-$(uname -r)" ]; then
|
||
configFile="/boot/config-$(uname -r)"
|
||
elif [ -f "/boot/config" ]; then
|
||
configFile="/boot/config"
|
||
elif [ -f "/lib/modules/$(uname -r)/build/.config" ]; then
|
||
configFile="/lib/modules/$(uname -r)/build/.config"
|
||
fi
|
||
|
||
# no config file found
|
||
if [ -z "$configFile" ]; then
|
||
echo ""
|
||
return
|
||
fi
|
||
|
||
if [ "$isGzipped" -eq 1 ]; then
|
||
if cmd_exists gunzip; then
|
||
gunzip -c "$configFile" 2>/dev/null
|
||
else
|
||
echo ""
|
||
fi
|
||
else
|
||
cat "$configFile"
|
||
fi
|
||
}
|
||
|
||
check_iface_coalesce() {
|
||
iface="$1"
|
||
coalesceErr=0
|
||
|
||
if ! cmd_exists ethtool; then
|
||
logwarn "'ethtool' not found, skipping coalescing check for $iface"
|
||
return
|
||
fi
|
||
|
||
if ! ethtool -c "$iface" >/dev/null 2>&1; then
|
||
logwarn "network coalescing cannot be set/read on $iface"
|
||
return
|
||
fi
|
||
|
||
for dir in rx tx; do
|
||
val=$(ethtool -c "$iface" 2>/dev/null | awk "/${dir}-usecs:/ {print \$2}")
|
||
if [ "$val" != 1 ] && [ "$val" != "n/a" ]; then
|
||
logwarn "$iface has ${dir}-usecs=$val, set to 1 for best performance."
|
||
echo "${TAB}This can be set with 'ethtool -C $iface $dir-usecs 1' before starting DEP."
|
||
echo "${TAB}Some drivers might not support this setting or only allow values significantly higher than 1 usecs."
|
||
echo "${TAB}Please contact your network driver vendor for more information."
|
||
coalesceErr=1
|
||
fi
|
||
done
|
||
|
||
[ "$coalesceErr" -eq 0 ] && logok "$iface coalescing settings ok"
|
||
}
|
||
|
||
# NOTE: on some systems 'id' might not be available, hence we check manually
|
||
check_uid() {
|
||
if [ "$(grep -E '^Uid:' /proc/self/status | awk '{print $2}')" -ne "0" ]; then
|
||
logerr "this script must be executed with root privileges"
|
||
fail
|
||
fi
|
||
}
|
||
|
||
check_arg() {
|
||
if [ -z "$1" ]; then
|
||
logerr "missing argument: DEP installation path"
|
||
loginfo "usage:"
|
||
loginfo " sudo ./dep_check.sh <DEP installation path>"
|
||
fail
|
||
fi
|
||
|
||
if [ "${1#/}" = "$1" ]; then
|
||
logerr "$1 is not an absolute path"
|
||
fail
|
||
fi
|
||
|
||
if [ ! -d "$1" ]; then
|
||
logerr "$1: directory not found"
|
||
fail
|
||
fi
|
||
}
|
||
|
||
check_runtime_deps() {
|
||
if [ ! -f "$JQ" ]; then
|
||
logerr "$JQ not found. This script requires jq to parse JSON files."
|
||
echo "${TAB}Please install jq or set JQ to the absolute path of the jq binary."
|
||
fail
|
||
fi
|
||
|
||
# determine installed glibc version
|
||
if cmd_exists ldd; then
|
||
installedGlibc=$(ldd --version 2>/dev/null | awk 'NR==1{print $NF}')
|
||
elif [ -x /lib/libc.so.6 ]; then
|
||
installedGlibc=$(/lib/libc.so.6 2>/dev/null | awk 'NR==1{print $NF}')
|
||
elif [ -x /lib64/libc.so.6 ]; then
|
||
installedGlibc=$(/lib64/libc.so.6 2>/dev/null | awk 'NR==1{print $NF}')
|
||
else
|
||
logwarn "could not determine installed glibc version:"
|
||
return
|
||
fi
|
||
|
||
# remove trailing dot if present
|
||
installedGlibc="${installedGlibc%.}"
|
||
|
||
is_glibc_version_host_compatible() {
|
||
requiredVersion="$1"
|
||
|
||
requiredMajor=$(echo "$requiredVersion" | cut -d'.' -f1 | tr -dc '0-9')
|
||
requiredMinor=$(echo "$requiredVersion" | cut -d'.' -f2 | tr -dc '0-9')
|
||
|
||
installedMajor=$(echo "$installedGlibc" | cut -d'.' -f1 | tr -dc '0-9')
|
||
installedMinor=$(echo "$installedGlibc" | cut -d'.' -f2 | tr -dc '0-9')
|
||
|
||
if [ "$installedMajor" -lt "$requiredMajor" ] || { [ "$installedMajor" -eq "$requiredMajor" ] && [ "$installedMinor" -lt "$requiredMinor" ]; }; then
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
if ! is_glibc_version_host_compatible $REQUIRED_GLIBC_CRUN; then
|
||
logok "host-provided glibc ($installedGlibc) is compatible with container crun (requires >= $REQUIRED_GLIBC_CRUN)"
|
||
else
|
||
logwarn "host-provided glibc ($installedGlibc) is older than required ($REQUIRED_GLIBC_CRUN) for container crun"
|
||
echo "${TAB}if unable to start DEP due to crun issues, consider compiling crun"
|
||
echo "${TAB}(or an alternative OCI-compliant container runtime) using a compatible"
|
||
echo "${TAB}toolchain for this system"
|
||
fi
|
||
|
||
PTP_TEST_COMPATIBLE=0
|
||
is_glibc_version_host_compatible "$REQUIRED_GLIBC_PTP_TEST" || PTP_TEST_COMPATIBLE=1
|
||
}
|
||
|
||
check_architecture() {
|
||
if ! cmd_exists hexdump; then
|
||
logwarn "host architecture check skipped: 'hexdump' not found"
|
||
return
|
||
fi
|
||
|
||
if [ ! -f "$CONTAINER_RUNTIME" ]; then
|
||
logerr "container runtime binary not found: $CONTAINER_RUNTIME"
|
||
echo "${TAB}If you have changed the default container runtime shipped with DEP, please"
|
||
echo "${TAB}set CONTAINER_RUNTIME to the absolute path of the new binary or just ignore this message."
|
||
return
|
||
fi
|
||
|
||
HOST_BINARY=$(which uname)
|
||
ELF_ISA_OFFSET=0x12
|
||
ELF_ISA_SIZE=2
|
||
ELF_ISA_ARM_32=28
|
||
ELF_ISA_X86_64=3e
|
||
ELF_ISA_ARM_64=b7
|
||
# get the hexdecimal value of the architecture in the ELF header section
|
||
HOST_ARCH=$(hexdump -e '"%x"' -s $ELF_ISA_OFFSET -n $ELF_ISA_SIZE "$HOST_BINARY")
|
||
DEP_ARCH=$(hexdump -e '"%x"' -s $ELF_ISA_OFFSET -n $ELF_ISA_SIZE "$CONTAINER_RUNTIME")
|
||
|
||
DEP_ARCH_NAME=
|
||
case $DEP_ARCH in
|
||
"$ELF_ISA_ARM_32" )
|
||
DEP_ARCH_NAME="arm32";;
|
||
"$ELF_ISA_X86_64" )
|
||
DEP_ARCH_NAME="x86_64";;
|
||
"$ELF_ISA_ARM_64" )
|
||
DEP_ARCH_NAME="arm64";;
|
||
* )
|
||
DEP_ARCH_NAME="ELF_ISA: $DEP_ARCH";;
|
||
esac
|
||
|
||
HOST_ARCH_NAME=
|
||
case $HOST_ARCH in
|
||
"$ELF_ISA_ARM_32" )
|
||
HOST_ARCH_NAME="arm32";;
|
||
"$ELF_ISA_X86_64" )
|
||
HOST_ARCH_NAME="x86_64";;
|
||
"$ELF_ISA_ARM_64" )
|
||
HOST_ARCH_NAME="arm64";;
|
||
* )
|
||
HOST_ARCH_NAME="ELF_ISA: $HOST_ARCH";;
|
||
esac
|
||
|
||
if [ "$HOST_ARCH" = "$DEP_ARCH" ]; then
|
||
logok "DEP and host architecture matched"
|
||
return
|
||
else
|
||
if [ "$HOST_ARCH" = "$ELF_ISA_ARM_64" ] && [ "$DEP_ARCH" = "$ELF_ISA_ARM_32" ]; then
|
||
# execute the container runtime without arguments to check whether the host is
|
||
# available for multiple architecture supports suppressing both output and error
|
||
# messages of the container runtime
|
||
# shellcheck disable=SC2086
|
||
./${CONTAINER_RUNTIME} > /dev/null 2>&1
|
||
CHECK_MULTI_ARCH_SUPPORT=$?
|
||
if [ "$CHECK_MULTI_ARCH_SUPPORT" -eq 0 ]; then
|
||
logerr "it appears you are running DEP built for arm32 on an arm64 host. Please obtain DEP built for arm64 instead"
|
||
else
|
||
logerr "system incompatibility detected: host CPU is $HOST_ARCH_NAME while DEP was built for $DEP_ARCH_NAME"
|
||
fi
|
||
else
|
||
logerr "system incompatibility detected: host CPU is $HOST_ARCH_NAME while DEP was built for $DEP_ARCH_NAME"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
check_kernel_config() {
|
||
kernelConfig=$(kernel_info)
|
||
if [ -z "$kernelConfig" ]; then
|
||
logwarn "kernel config not found, skipping kernel config checks"
|
||
return
|
||
fi
|
||
|
||
# Check if a kernel config option is enabled, built as module, or disabled
|
||
# Usage: check_config_option CONFIG_NAME [required|disabled]
|
||
check_config_option() {
|
||
configName="$1"
|
||
requirement="${2:-optional}"
|
||
|
||
enabledCount=$(echo "$kernelConfig" | grep -cw "${configName}=y")
|
||
moduleCount=$(echo "$kernelConfig" | grep -cw "${configName}=m")
|
||
|
||
# handle options that should be disabled
|
||
if [ "$requirement" = "disabled" ]; then
|
||
if [ "$enabledCount" -ne 0 ] || [ "$moduleCount" -ne 0 ]; then
|
||
logwarn "$configName needs to be disabled in kernel config"
|
||
fi
|
||
return 1
|
||
fi
|
||
|
||
# handle enabled/module options
|
||
if [ "$enabledCount" -eq 1 ]; then
|
||
return 0
|
||
fi
|
||
|
||
if [ "$moduleCount" -eq 1 ]; then
|
||
return 0
|
||
fi
|
||
|
||
# option not found
|
||
if [ "$requirement" = "required" ]; then
|
||
logerr "$configName not found in kernel config"
|
||
else
|
||
logwarn "$configName not found in kernel config"
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# Define required kernel configuration options
|
||
requiredConfigs="CGROUPS CGROUP_DEVICE CPUSETS CGROUP_SCHED MEMCG NAMESPACES UTS_NS IPC_NS USER_NS PID_NS NET_NS SQUASHFS SQUASHFS_ZLIB BLK_DEV_LOOP IP_MULTICAST"
|
||
|
||
# check required kernel configs
|
||
for option in $requiredConfigs; do
|
||
check_config_option "CONFIG_$option" "required"
|
||
done
|
||
|
||
# check recommended kernel configs
|
||
check_config_option "CONFIG_PREEMPT" "optional"
|
||
check_config_option "CONFIG_CGROUP_FREEZER" "optional"
|
||
|
||
# DEP doesn't support fine-grained real-time scheduling per cgroup yet.
|
||
# Hence, CONFIG_RT_GROUP_SCHED needs to be disabled to start DEP
|
||
# TODO: is this actually true?
|
||
check_config_option "CONFIG_RT_GROUP_SCHED" "disabled"
|
||
}
|
||
|
||
check_cgroups() {
|
||
cgroupWarningMsg="one or more cgroup mounts were not found and DEP is configured to run with
|
||
numDepCores = 0 in dante.json: if this is intended, please ignore this warning."
|
||
cgroupMountErr=0
|
||
|
||
display_notfound() { logwarn "cgroup mount '$1' not found"; }
|
||
display_found() { logok "cgroup mount '$1' found"; }
|
||
|
||
check_mount() {
|
||
path=$1
|
||
expectedMountType=$2
|
||
|
||
while IFS= read -r line; do
|
||
# get the second field (mount point) and the third field (type)
|
||
mountPoint=$(echo "$line" | awk '{print $2}')
|
||
mountType=$(echo "$line" | awk '{print $3}')
|
||
|
||
# if the mountPoint doesn't start with /sys/fs/cgroup, skip it
|
||
case "$mountPoint" in
|
||
/sys/fs/cgroup*) ;;
|
||
*) continue ;;
|
||
esac
|
||
|
||
# if mount point and type exactly match the expected values, we're good
|
||
if [ "$mountPoint" = "$path" ] && [ "$mountType" = "$expectedMountType" ]; then
|
||
display_found "$path ($expectedMountType)"
|
||
return
|
||
fi
|
||
|
||
# There is a chance multiple controllers are mounted on the same path,
|
||
# for instance we might be looking for /sys/fs/cgroup/cpu and have
|
||
#
|
||
# /sys/fs/cgroup/cpu,cpuacct cgroup etc..
|
||
#
|
||
# mounted instead.
|
||
|
||
# because we skip entries that do not start with /sys/fs/cgroup at
|
||
# the beginning of the loop, we know getting the substring after
|
||
# /sys/fs/cgroup at this point will yield an empty string at worst
|
||
cgroupSubstring=${mountPoint#/sys/fs/cgroup/}
|
||
|
||
# do the same with $path
|
||
cgroupPathSubstring=${path#/sys/fs/cgroup/}
|
||
|
||
# check if cgroupPathSubstring is part of cgroupSubstring
|
||
# eg this would successfully match 'cpu' against both 'cpuacct,cpu' and 'cpu,cpuacct'
|
||
if echo "$cgroupSubstring" | grep -qw "$cgroupPathSubstring"; then
|
||
if echo "$mountType" | grep -qw "$expectedMountType"; then
|
||
display_found "$path ($expectedMountType)"
|
||
return
|
||
fi
|
||
fi
|
||
done < /proc/mounts
|
||
|
||
display_notfound "$path (expected type: $expectedMountType)"
|
||
cgroupMountErr=1
|
||
}
|
||
|
||
# hybrid cgroup mode detection
|
||
v1Found=$(awk '$3 == "cgroup" { found=1 } END { print found+0 }' /proc/mounts)
|
||
v2Found=$(awk '$3 == "cgroup2" { found=1 } END { print found+0 }' /proc/mounts)
|
||
|
||
if [ "$v1Found" -eq 1 ] && [ "$v2Found" -eq 1 ]; then
|
||
logwarn "Hybrid cgroup mode detected: both cgroup v1 and v2 are mounted."
|
||
echo "${TAB}The default crun runtime shipped with DEP does not support hybrid cgroup mode."
|
||
echo "${TAB}Please ensure only one cgroup version (v1 or v2) is in use on your system."
|
||
echo "${TAB}If DEP fails to start, refer to the DEP manual for more information on supported cgroup configurations."
|
||
return
|
||
fi
|
||
|
||
if ! json_field_exists "$DANTE_JSON_DATA" ".platform.cgroupVersion"; then
|
||
logwarn "'cgroupVersion' not set in dante.json, assuming cgroup v1 (legacy)"
|
||
cgroupVersion="1"
|
||
else
|
||
cgroupVersion=$(get_dante_json_field ".platform.cgroupVersion")
|
||
fi
|
||
|
||
if [ "$cgroupVersion" = "1" ]; then
|
||
selectedVersion="v1"
|
||
check_mount "/sys/fs/cgroup" "tmpfs"
|
||
check_mount "/sys/fs/cgroup/cpu" "cgroup"
|
||
check_mount "/sys/fs/cgroup/cpuset" "cgroup"
|
||
check_mount "/sys/fs/cgroup/memory" "cgroup"
|
||
check_mount "/sys/fs/cgroup/devices" "cgroup"
|
||
elif [ "$cgroupVersion" = "2" ]; then
|
||
selectedVersion="v2"
|
||
check_mount "/sys/fs/cgroup" "cgroup2"
|
||
else
|
||
logerr "unsupported cgroupVersion value ($cgroupVersion) in $DANTE_JSON"
|
||
return
|
||
fi
|
||
|
||
if [ "$cgroupMountErr" -eq 1 ]; then
|
||
if is_json_field_number "$DANTE_JSON_DATA" ".audio.numDepCores"; then
|
||
depCores=$(get_dante_json_field ".audio.numDepCores")
|
||
else
|
||
# If numDepCores is not a number, assume it's a non-zero configuration
|
||
# (e.g., an array of specific cores). We only need to distinguish
|
||
# between 0 (disabled) and non-zero (enabled) for cgroup error handling.
|
||
depCores=1
|
||
fi
|
||
|
||
if [ "$depCores" -ne 0 ]; then
|
||
logerr "one or more required cgroup mounts were not found"
|
||
else
|
||
logwarn "$cgroupWarningMsg"
|
||
fi
|
||
else
|
||
logok "all required cgroup ${selectedVersion} mounts found"
|
||
fi
|
||
}
|
||
|
||
check_cpu_config() {
|
||
configuredCores=""
|
||
checkFailed=0
|
||
depProcesses="dep_manager apec DepApe conmon_server mdnsd ptp hwclkcfg DepClockBridge"
|
||
|
||
loginfo "validating CPU configuration and runtime affinity settings"
|
||
|
||
# utility to check that given a string that string is found in depProcesses
|
||
is_dep_process() {
|
||
for depProc in $depProcesses; do
|
||
if [ "$1" = "$depProc" ]; then
|
||
return 0
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
if json_field_exists "$DANTE_JSON_DATA" ".audio.percentCpuShare"; then
|
||
logwarn "the percentCpuShare option in $DANTE_JSON was deprecated in DEP 1.5 and can be removed"
|
||
fi
|
||
|
||
if is_json_field_number "$DANTE_JSON_DATA" ".audio.numDepCores"; then
|
||
numCores=$(get_dante_json_field ".audio.numDepCores")
|
||
|
||
if [ "$numCores" -eq 0 ]; then
|
||
loginfo "numDepCores is set to 0, DEP won't perform CPU affinity allocation - skipping check"
|
||
return
|
||
fi
|
||
|
||
# generate space-separated core IDs from 0 to numCores-1
|
||
i=0
|
||
while [ "$i" -lt "$numCores" ]; do
|
||
if [ -z "$configuredCores" ]; then
|
||
configuredCores="$i"
|
||
else
|
||
configuredCores="$configuredCores $i"
|
||
fi
|
||
i=$((i + 1))
|
||
done
|
||
|
||
elif is_json_field_array "$DANTE_JSON_DATA" ".audio.numDepCores"; then
|
||
configuredCores=$(get_dante_json_array_elements ".audio.numDepCores")
|
||
if [ -z "$configuredCores" ]; then
|
||
logerr "numDepCores array is empty in $DANTE_JSON"
|
||
return
|
||
fi
|
||
|
||
else
|
||
logerr "numDepCores is not a number or an array in $DANTE_JSON"
|
||
return
|
||
fi
|
||
|
||
# check that each CPU ID specified in configuredCores actually exists on the system
|
||
for core in $configuredCores; do
|
||
# check CPU ID validity
|
||
if ! printf '%s\n' "$core" | grep -Eq '^[0-9]+$'; then
|
||
logerr "invalid core ID '$core' in numDepCores - must be a non-negative integer"
|
||
checkFailed=1
|
||
continue
|
||
fi
|
||
|
||
# check if the core is online (exists in /sys/devices/system/cpu/cpu$core)
|
||
if [ ! -d "/sys/devices/system/cpu/cpu$core" ]; then
|
||
logerr "core ID '$core' in numDepCores does not exist on the system"
|
||
checkFailed=1
|
||
fi
|
||
done
|
||
|
||
# before we start checking processes that might interfere with DEP, we need
|
||
# to do some self-examination and collect all the PIDs from 1 (init) to our own PID
|
||
currentPid=$$
|
||
currentPidTree=""
|
||
while [ "$currentPid" -ne 1 ] && [ -n "$currentPid" ]; do
|
||
currentPidTree="$currentPid $currentPidTree"
|
||
# try to get the parent PID, but if the process no longer exists, break the loop
|
||
if ! currentPid=$(awk '/^PPid:/ { print $2 }' /proc/"$currentPid"/status 2>/dev/null); then
|
||
break
|
||
fi
|
||
done
|
||
|
||
# list all user-space processes (excluding kernel tasks) and warn the user for each
|
||
# non-dep process running on any of the cores in configuredCores
|
||
|
||
matchedPids=""
|
||
|
||
# shellcheck disable=SC2010
|
||
for pid in /proc/[0-9]*; do
|
||
pid=${pid##*/}
|
||
|
||
# skip if not all digits
|
||
case "$pid" in
|
||
''|*[!0-9]*) continue ;;
|
||
esac
|
||
|
||
# check if /proc/$pid still there
|
||
[ -d "/proc/$pid" ] || continue
|
||
|
||
# skip kernel tasks, read all files in one shot
|
||
#
|
||
# NOTE: on Linux, /proc/$pid/cmdline is a null-separated string of
|
||
# command-line arguments for the given process.
|
||
# POSIX shells do not support null bytes inside variables, so to avoid triggering errors like:
|
||
#
|
||
# warning: command substitution: ignored null byte in input
|
||
#
|
||
# we read the cmdline file and replace null bytes with spaces.
|
||
{
|
||
cmdline=$( { tr '\0' ' ' < "/proc/$pid/cmdline"; } 2>/dev/null ) || continue
|
||
[ -n "$cmdline" ] || continue
|
||
|
||
procName=$(cat "/proc/$pid/comm") || continue
|
||
procName=$(printf "%s" "$procName" | sed 's/ //g' | tr -d '\r\n') # remove whitespaces and trim newline
|
||
|
||
[ -r "/proc/$pid/stat" ] || continue
|
||
cpuCore=$(awk '{print $39}' "/proc/$pid/stat" 2>/dev/null) || continue
|
||
} || continue
|
||
|
||
# skip processes that are not running on any of the configured cores
|
||
if ! echo "$configuredCores" | grep -qw "$cpuCore"; then
|
||
continue
|
||
fi
|
||
|
||
# skip DEP processes
|
||
if is_dep_process "$procName"; then
|
||
# this is a DEP process, skip it
|
||
continue
|
||
fi
|
||
|
||
if [ -z "$matchedPids" ]; then
|
||
matchedPids="name:$procName,PID:$pid,core:$cpuCore"
|
||
else
|
||
matchedPids="$matchedPids name:$procName,PID:$pid,core:$cpuCore"
|
||
fi
|
||
done
|
||
|
||
if [ -n "$matchedPids" ]; then
|
||
logwarn "the following processes/threads are running on one or multiple CPU cores which are also configured for DEP:"
|
||
for p in $matchedPids; do
|
||
# extract name, PID and core id from the formatted string (name:procName,PID:pid,core:cpuCore)
|
||
procName=$(echo "$p" | cut -d',' -f1 | cut -d':' -f2)
|
||
procPid=$(echo "$p" | cut -d',' -f2 | cut -d':' -f2)
|
||
procCore=$(echo "$p" | cut -d',' -f3 | cut -d':' -f2)
|
||
echo "${TAB}name: $procName, PID: $procPid, core: $procCore"
|
||
done
|
||
logwarn "Please ensure that these processes are not running on the same cores as DEP."
|
||
echo "${TAB}DEP to work at its best requires good CPU isolation, which prevents its processing"
|
||
echo "${TAB}threads from being interrupted from other unrelated userspace or kernelspace tasks."
|
||
echo "${TAB}Consult the DEP manual for more information on how to achieve optimal CPU isolation."
|
||
checkFailed=1
|
||
fi
|
||
|
||
if [ "$checkFailed" -eq 0 ]; then
|
||
logok "DEP CPU configuration is valid - configured cores: $configuredCores"
|
||
fi
|
||
|
||
# check the scaling governor for each configured core
|
||
for core in $configuredCores; do
|
||
path="/sys/devices/system/cpu/cpu$core/cpufreq/scaling_governor"
|
||
|
||
if [ -r "$path" ]; then
|
||
governor=$(cat "$path" 2>/dev/null)
|
||
if [ "$governor" = "performance" ]; then
|
||
logok "CPU $core scaling governor set to performance"
|
||
else
|
||
logwarn "CPU $core scaling governor set to '$governor' - recommended to set to 'performance' if kernel supports it"
|
||
fi
|
||
else
|
||
loginfo "CPU $core scaling governor not found or unreadable, skipping check"
|
||
fi
|
||
done
|
||
}
|
||
|
||
check_rng_speed() {
|
||
if ! cmd_exists /usr/bin/time; then
|
||
logwarn "'/usr/bin/time' not found, skipping /dev/random speed test"
|
||
return
|
||
fi
|
||
|
||
if ! cmd_exists dd; then
|
||
logwarn "'dd' not found, skipping /dev/random speed test"
|
||
return
|
||
fi
|
||
|
||
# try to use more basic system utilities such as ps and sleep rather than timeout
|
||
dd if=/dev/random of=/dev/null bs=1024 count=1 iflag=fullblock >/tmp/dep_check_out 2>&1 &
|
||
PID=$!
|
||
sleep 1
|
||
kill $PID > /dev/null 2> /dev/null
|
||
wait $PID > /dev/null 2> /dev/null
|
||
RETVAL=$?
|
||
OUTPUT=$(cat /tmp/dep_check_out)
|
||
if [ $RETVAL -ne 0 ]; then # dd did not complete successfully
|
||
if [ -n "$OUTPUT" ]; then # some output was produced, dd probably had an error
|
||
logerr "$OUTPUT"
|
||
else # dd did not produce output
|
||
logerr "dd was killed before producing any output. /dev/random is probably too slow"
|
||
fi
|
||
return
|
||
fi # dd did not time out
|
||
|
||
SPEED_TEST_OUTPUT=$({ /usr/bin/time -p dd if=/dev/random of=/dev/null bs=1024 count=1 iflag=fullblock; } 2>&1 | grep real)
|
||
# At this stage, SPEED_TEST_OUTPUT will have a form like "real 0.00". Note that we use the GNU time here which only
|
||
# contains two decimal point precision. This does not mean "0.00" took 0 seconds to finish but rather was less than 10 ms.
|
||
# The following code converts this output to a millisecond value.
|
||
|
||
# remove the "real" part of the output
|
||
TIME=${SPEED_TEST_OUTPUT#real }
|
||
SECS=${TIME%%.*} # grab the digits before the decimal point
|
||
SUB_SECOND=${TIME#*.}
|
||
SUB_SECOND=${SUB_SECOND%[0-9]*} # grab the first digit after the decimal point
|
||
|
||
if [ "$SECS" -gt "0" ] || [ "$SUB_SECOND" -gt "0" ]; then
|
||
logwarn "/dev/random is too slow (took $TIME seconds or more)"
|
||
else
|
||
logok "/dev/random is sufficiently fast (took less than 100 milliseconds)"
|
||
fi
|
||
|
||
# cleanup
|
||
rm -rf /tmp/dep_check_out
|
||
}
|
||
|
||
load_config_files() {
|
||
jsonSyntaxError=0
|
||
if [ ! -e "$DANTE_JSON" ]; then
|
||
logerr "$DANTE_JSON not found"
|
||
fail
|
||
fi
|
||
|
||
if [ -L "$CONFIG_JSON" ]; then
|
||
# check where the symlink points to
|
||
target="$PATH_TO_BUNDLE_DIR/$(readlink "$PATH_TO_BUNDLE_DIR/config.json")"
|
||
if [ -e "$target" ]; then
|
||
target=$(realpath "$target")
|
||
else
|
||
logerr "$target does not exist at the symlink target pointed by '$PATH_TO_BUNDLE_DIR/config.json'"
|
||
fail
|
||
fi
|
||
fi
|
||
|
||
if [ ! -e "$CONFIG_JSON" ]; then
|
||
logerr "$CONFIG_JSON not found"
|
||
fail
|
||
fi
|
||
|
||
if ! "$JQ" empty "$DANTE_JSON" 2>jq_err; then
|
||
logerr "syntax error detected in '$DANTE_JSON': $(cat jq_err)"
|
||
jsonSyntaxError=1
|
||
fi
|
||
|
||
if ! "$JQ" empty "$CONFIG_JSON" 2>jq_err; then
|
||
logerr "syntax error detected in '$CONFIG_JSON': $(cat jq_err)"
|
||
jsonSyntaxError=1
|
||
fi
|
||
|
||
rm -f jq_err
|
||
|
||
if [ "$jsonSyntaxError" -eq 1 ]; then
|
||
fail
|
||
fi
|
||
|
||
# load once instead of parsing multiple times when needed
|
||
logok "JSON configuration files loaded"
|
||
DANTE_JSON_DATA=$(cat "$DANTE_JSON")
|
||
CONFIG_JSON_DATA=$(cat "$CONFIG_JSON")
|
||
}
|
||
|
||
check_network_interfaces() {
|
||
# first, check that all network interfaces specified in $DANTE_JSON actually exist
|
||
interfaces=$(get_dante_json_array_elements ".network.interfaces")
|
||
for iface in $interfaces; do
|
||
if ! ip a show dev "$iface" >/dev/null 2>&1; then
|
||
logerr "network interface $iface specified in $DANTE_JSON not found on the system"
|
||
else
|
||
logok "network interface $iface found"
|
||
fi
|
||
done
|
||
|
||
# check that interfaceMode is set to a valid value, and if it is set to "Direct",
|
||
# check network coalescing configuration
|
||
if interfaceMode=$(get_dante_json_field ".network.interfaceMode"); then
|
||
case $interfaceMode in
|
||
Direct | Switched)
|
||
loginfo "interfaceMode set to '$interfaceMode'"
|
||
;;
|
||
*)
|
||
logerr "invalid interfaceMode in dante.json: '$interfaceMode'. Possible values are: Direct, Switched"
|
||
;;
|
||
esac
|
||
else
|
||
# default: Direct
|
||
interfaceMode="Direct"
|
||
fi
|
||
|
||
# if interfaceMode is set to "Switched", checking network coalescing is deferred
|
||
# to the check_clock_hardware_interfaces() function, which will check the interfaces
|
||
# specified in the "hardwareInterfaces"
|
||
if [ "$interfaceMode" = "Direct" ]; then
|
||
for iface in $interfaces; do
|
||
check_iface_coalesce "$iface"
|
||
done
|
||
else
|
||
DEFER_COA_CHECK=1
|
||
fi
|
||
|
||
if ! json_field_exists "$DANTE_JSON_DATA" ".network.preferredLinkSpeed"; then
|
||
return
|
||
fi
|
||
|
||
# check that preferredLinkSpeed is set to a valid value
|
||
configuredSpeedValue=$(get_dante_json_field ".network.preferredLinkSpeed")
|
||
|
||
case $configuredSpeedValue in
|
||
LINK_SPEED_100M) ;;
|
||
LINK_SPEED_1G) ;;
|
||
LINK_SPEED_10G) ;;
|
||
*)
|
||
logwarn "preferredLinkSpeed '$configuredSpeedValue' in $DANTE_JSON is not supported."
|
||
echo "${TAB}Possible values are: LINK_SPEED_100M, LINK_SPEED_1G, LINK_SPEED_10G"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
check_clock_hardware_interfaces() {
|
||
hwTimestampingEnabled=0
|
||
hwTsField=$(get_dante_json_field ".clock.enableHwTimestamping")
|
||
hwTsDescription=""
|
||
case "$hwTsField" in
|
||
true)
|
||
hwTimestampingEnabled=1
|
||
hwTsDescription="hardware"
|
||
;;
|
||
"v1")
|
||
hwTimestampingEnabled=1
|
||
hwTsDescription="PTPv1-only hardware"
|
||
;;
|
||
false|"") hwTimestampingEnabled=0 ;;
|
||
*) logerr "invalid enableHwTimestamping in dante.json: '$hwTsField'. Possible values are: true, false, \"v1\"" ;;
|
||
esac
|
||
|
||
dsaTagField=""
|
||
if json_field_exists "$DANTE_JSON_DATA" ".clock.dsaTaggedPackets"; then
|
||
if ! is_json_field_bool "$DANTE_JSON_DATA" ".clock.dsaTaggedPackets"; then
|
||
logerr "'clock.dsaTaggedPackets' in dante.json is not a boolean value. Please set to either true or false"
|
||
return
|
||
fi
|
||
|
||
dsaTagField=$(get_dante_json_field ".clock.dsaTaggedPackets")
|
||
if [ "$dsaTagField" = true ]; then
|
||
loginfo "DSA tagging in use"
|
||
fi
|
||
fi
|
||
|
||
if [ "$hwTimestampingEnabled" -eq 0 ]; then
|
||
loginfo "hardware timestamping is disabled in $DANTE_JSON"
|
||
return
|
||
fi
|
||
|
||
logok "$hwTsDescription timestamping is enabled"
|
||
|
||
# check that, if defined, hardwareInterfaces is an array and not empty
|
||
hwIfacesCount=0
|
||
hardwareInterfaces=""
|
||
networkInterfacesCount=0
|
||
|
||
if is_json_field_array "$DANTE_JSON_DATA" ".clock.hardwareInterfaces"; then
|
||
hwIfacesCount=$(get_json_array_length "$DANTE_JSON_DATA" ".clock.hardwareInterfaces")
|
||
if [ "$hwIfacesCount" -eq 0 ]; then
|
||
logerr "'clock.hardwareInterfaces' in $DANTE_JSON is an empty array"
|
||
else
|
||
hardwareInterfaces=$(get_dante_json_array_elements ".clock.hardwareInterfaces")
|
||
|
||
# make sure number of interfaces specified in network.interfaces and clock.hardwareInterfaces matches
|
||
networkInterfacesCount=$(get_json_array_length "$DANTE_JSON_DATA" ".network.interfaces")
|
||
fi
|
||
elif json_field_exists "$DANTE_JSON_DATA" ".clock.hardwareInterfaces"; then
|
||
logerr "'clock.hardwareInterfaces' in $DANTE_JSON is not an array"
|
||
fi
|
||
|
||
if [ "$dsaTagField" = true ] && [ "$networkInterfacesCount" -ne "$hwIfacesCount" ]; then
|
||
logerr "number of interfaces in 'clock.hardwareInterfaces' ($hwIfacesCount) does not match number of interfaces in 'network.interfaces' ($networkInterfacesCount)"
|
||
echo "${TAB}When using DSA, the virtual interfaces cannot directly make use of the IEEE1588 packet timestamping available"
|
||
echo "${TAB}through the FEC. DEP supports using an alternative 'hardware' interface for timestamping when virtual interfaces"
|
||
echo "${TAB}are used. Similar to the 'interfaces' field of dante.json, the 'hardwareInterfaces' field takes an array"
|
||
echo "${TAB}of interface identifiers (names or indices). Each value in hardwareInterfaces must correspond to the same"
|
||
echo "${TAB}positioned value in the 'interfaces' array."
|
||
fi
|
||
|
||
# from here, all checks are done only if hardwareInterfaces is populated
|
||
if [ "$hwIfacesCount" -eq 0 ]; then
|
||
return
|
||
fi
|
||
|
||
# check all interfaces specified in hardwareInterfaces actually exist
|
||
previousInterface=""
|
||
for iface in $hardwareInterfaces; do
|
||
if [ -n "$previousInterface" ] && [ "$iface" = "$previousInterface" ]; then
|
||
# already checked, skip it
|
||
continue
|
||
fi
|
||
previousInterface="$iface"
|
||
|
||
if ! ip a show dev "$iface" >/dev/null 2>&1; then
|
||
logerr "network interface $iface specified in hardwareInterfaces in $DANTE_JSON not found on the system"
|
||
else
|
||
logok "network interface $iface found"
|
||
# this is a result of the check_network_interfaces() function letting us know that
|
||
# the interfaceMode is set to "Switched" and the 'real' network interfaces on which network
|
||
# coalescing should be checked are those in hardwareInterfaces, not in interfaces
|
||
if [ "$DEFER_COA_CHECK" -eq 1 ]; then
|
||
check_iface_coalesce "$iface"
|
||
fi
|
||
fi
|
||
done
|
||
}
|
||
|
||
# This function checks whether a device node is configured correctly
|
||
# in an OCI-compliant containter runtime configuration file (e.g., config.json).
|
||
#
|
||
# It relies on cgroupVersion being set in dante.json for its functionality: this is
|
||
# because different cgroup versions require different ways to configure device nodes
|
||
# in the configuration file.
|
||
is_device_node_configured() {
|
||
deviceNode="$1"
|
||
|
||
fileMode=384 # 0600 in octal
|
||
gid=0
|
||
uid=0
|
||
allow="true"
|
||
access="rw"
|
||
|
||
# shellcheck disable=SC2012
|
||
deviceType=$(ls -l "$deviceNode" | cut -c1)
|
||
if [ "$deviceType" = "c" ] || [ "$deviceType" = "b" ]; then
|
||
:
|
||
else
|
||
logerr "parsing device node '$deviceNode' type failed, it's not a character or block device"
|
||
fi
|
||
|
||
# shellcheck disable=SC2012
|
||
major=$(ls -l "$deviceNode" | awk '{print $5}' | sed 's/,//')
|
||
# shellcheck disable=SC2012
|
||
minor=$(ls -l "$deviceNode" | awk '{print $6}')
|
||
|
||
if ! json_field_exists "$DANTE_JSON_DATA" ".platform.cgroupVersion"; then
|
||
# assume cgroup v1
|
||
logwarn "assuming cgroup v1 - please set 'cgroupVersion' in dante.json to allow this check to work correctly"
|
||
cgroupVersion="1"
|
||
else
|
||
cgroupVersion=$(get_dante_json_field ".platform.cgroupVersion")
|
||
fi
|
||
|
||
# for cgroup v1, device node needs to be also specified in linux.resources.devices[]
|
||
foundInLinuxResources=0
|
||
if [ "$cgroupVersion" = "1" ]; then
|
||
# entries in linux.resources.devices[] are of the form:
|
||
# {
|
||
# "allow": true,
|
||
# "type": "c",
|
||
# "major": 189,
|
||
# "minor": 1,
|
||
# "access": "rw"
|
||
# }
|
||
# so it's not easy to identify whether a device node is configured or not.
|
||
# What we do is check whether an entry with the same allow, type, major, minor and access fields exists,
|
||
# and if it doesn't, print out what it should look like.
|
||
|
||
linuxResourcesDevices=$(get_config_json_array_elements ".linux.resources.devices")
|
||
for entry in $linuxResourcesDevices; do
|
||
if [ "$(get_json_field "$entry" ".allow")" = "$allow" ] &&
|
||
[ "$(get_json_field "$entry" ".type")" = "$deviceType" ] &&
|
||
[ "$(get_json_field "$entry" ".major")" = "$major" ] &&
|
||
[ "$(get_json_field "$entry" ".minor")" = "$minor" ] &&
|
||
[ "$(get_json_field "$entry" ".access")" = "$access" ]; then
|
||
foundInLinuxResources=1
|
||
fi
|
||
done
|
||
|
||
if [ "$foundInLinuxResources" -eq 0 ]; then
|
||
# shellcheck disable=SC2089
|
||
expectedEntry="{\"allow\": $allow,\"type\": \"$deviceType\",\"major\": $major,\"minor\": $minor,\"access\": \"$access\"}"
|
||
logerr "device node '$deviceNode' not found in linux.resources.devices[], check that an"
|
||
echo "${TAB}identical entry like the following one exists in $CONFIG_JSON:"
|
||
# shellcheck disable=SC2090
|
||
# shellcheck disable=SC2086
|
||
echo $expectedEntry | $JQ | sed 's/^/ /'
|
||
fi
|
||
else
|
||
foundInLinuxResources=1
|
||
fi
|
||
|
||
foundInLinuxDevices=0
|
||
misconfiguredEntryFound=0
|
||
linuxDevices=$(get_config_json_array_elements ".linux.devices")
|
||
linuxDeviceEntryFields=".type:$deviceType .major:$major .minor:$minor .fileMode:$fileMode .gid:$gid .uid:$uid"
|
||
|
||
for entry in $linuxDevices; do
|
||
if [ "$(get_json_field "$entry" ".path")" = "$deviceNode" ]; then
|
||
foundInLinuxDevices=1
|
||
|
||
for f in $linuxDeviceEntryFields; do
|
||
field=$(echo "$f" | cut -d':' -f1)
|
||
expectedValue=$(echo "$f" | cut -d':' -f2)
|
||
|
||
# check if the entry has the correct field and value
|
||
configuredValue=$(get_json_field "$entry" "$field")
|
||
|
||
if [ "$configuredValue" != "$expectedValue" ]; then
|
||
logerr "device node '$deviceNode' has incorrect $field in $CONFIG_JSON: expected '$expectedValue', got '$configuredValue' in linux.devices[]"
|
||
misconfiguredEntryFound=1
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
|
||
if [ $foundInLinuxDevices -eq 0 ]; then
|
||
logerr "device node '$deviceNode' not found in $CONFIG_JSON: expected entry with path '$deviceNode' in linux.devices[]"
|
||
echo "${TAB}Please add the device node to the 'linux.devices[]' array in $CONFIG_JSON."
|
||
return 0
|
||
fi
|
||
|
||
if [ $foundInLinuxDevices -eq 1 ] && [ $foundInLinuxResources -eq 1 ] && [ $misconfiguredEntryFound -eq 0 ]; then
|
||
logok "'$deviceNode' configured correctly"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# For both the primary and secondary (if being used) network interfaces, perform a
|
||
# preliminary check of all timestamping modes using the PTP test tool. For each mode,
|
||
# the tool will attempt to create a network socket confgured with that mode. This is
|
||
# therefore a much more definitive check than relying solely on capability reports
|
||
# provided by ethtool (which may not be 100% accurate).
|
||
#
|
||
# If DSA tagging is set and software timestamping has been configured in dante.json,
|
||
# the preliminary checks for hardware timestamping are simply skipped as the hardware
|
||
# interface(s) are deliberately ignored (even if defined).
|
||
#
|
||
# The return value is an array of strings, where each string is itself a collection
|
||
# of the following substrings separated by a '@' character:
|
||
#
|
||
# - Timestamping interface
|
||
# - The timestamping mode description (hardware/software/PTPv1-only hardware)
|
||
# - A 1 or 0 indicating if this mode is the one in dante.json
|
||
# - The return value from the PTP test tool (0 = success, 1 = failure)
|
||
# - The complete command line needed to run either a full timestamping test (if the
|
||
# check succeeded) or to run the preliminary check again (if it failed)
|
||
#
|
||
# NOTE: to allow the caller to iterate through the array using for(), the substrings
|
||
# cannot have any spaces at all. For this reason, a string will have all its spaces
|
||
# substituted with '+' characters. The caller, when looking at an array entry, is
|
||
# expected to replace the '+'s with spaces again and to simply use cut -d'@' to get
|
||
# at each substring.
|
||
timestamping_preliminary_checks() {
|
||
interfaceName=$1
|
||
danteJsonEnableHwTs=$2
|
||
dsaTaggedPackets=$3
|
||
hwInterfaceName=$4
|
||
|
||
# Create an array of modes to test, where the mode configured in dante.json is the
|
||
# first entry. This is done so that the caller, when reporting the check results,
|
||
# will show that mode first.
|
||
if [ "$danteJsonEnableHwTs" = true ]; then
|
||
configuredTsMode="-hw"
|
||
tsModesToTest="-hw -hwv1 -sw"
|
||
elif [ "$danteJsonEnableHwTs" = "v1" ]; then
|
||
configuredTsMode="-hwv1"
|
||
tsModesToTest="-hwv1 -hw -sw"
|
||
else
|
||
configuredTsMode="-sw"
|
||
if [ "$dsaTaggedPackets" = true ]; then
|
||
tsModesToTest="-sw"
|
||
else
|
||
tsModesToTest="-sw -hw -hwv1"
|
||
fi
|
||
fi
|
||
|
||
for tsMode in $tsModesToTest; do
|
||
if [ "$tsMode" = "-hw" ]; then
|
||
tsModeDescription="hardware"
|
||
elif [ "$tsMode" = "-hwv1" ]; then
|
||
tsModeDescription="PTPv1-only hardware"
|
||
else
|
||
tsModeDescription="software"
|
||
fi
|
||
|
||
timestampingInterface=$interfaceName
|
||
dsaArgs=""
|
||
|
||
# Any DSA arguments will be appended to the end of the standard PTP test tool command line
|
||
# arguments. We don't want any trailing spaces for the command lines we show the user, and
|
||
# so the DSA arguments will have a leading space.
|
||
if [ "$dsaTaggedPackets" = true ]; then
|
||
if [ "$tsMode" = "-hw" ] || [ "$tsMode" = "-hwv1" ]; then
|
||
dsaArgs=" -dsa -hwi $hwInterfaceName"
|
||
timestampingInterface=$hwInterfaceName
|
||
else
|
||
dsaArgs=" -dsa"
|
||
fi
|
||
fi
|
||
|
||
ptpTestToolCmdLine="${PTP_TEST} -i $interfaceName $tsMode$dsaArgs" # No space between $tsMode and $dsaArgs
|
||
|
||
# shellcheck disable=SC2086
|
||
${ptpTestToolCmdLine} -c > /dev/null 2>&1
|
||
retval=$?
|
||
if [ "$tsMode" = "$configuredTsMode" ]; then
|
||
isConfiguredTsMode=1
|
||
else
|
||
isConfiguredTsMode=0
|
||
fi
|
||
|
||
# If the check failed, the command line we pass back will be to run a preliminary
|
||
# rather than a full check.
|
||
if [ $retval -eq 1 ]; then
|
||
ptpTestToolCmdLine="$ptpTestToolCmdLine -c"
|
||
fi
|
||
|
||
# Substitute all spaces in the output string with the '+' character
|
||
echo "$timestampingInterface@$tsModeDescription@$isConfiguredTsMode@$retval@$ptpTestToolCmdLine" | sed 's/ /+/g'
|
||
done
|
||
}
|
||
|
||
check_timestamping_config() {
|
||
hwTsField=$(get_dante_json_field ".clock.enableHwTimestamping")
|
||
networkInterfaces=$(get_dante_json_array_elements ".network.interfaces")
|
||
|
||
if json_field_exists "$DANTE_JSON_DATA" ".clock.dsaTaggedPackets"; then
|
||
if ! is_json_field_bool "$DANTE_JSON_DATA" ".clock.dsaTaggedPackets"; then
|
||
logwarn "'clock.dsaTaggedPackets' is invalid - skipping timestamping configuration check"
|
||
return
|
||
fi
|
||
dsaTaggedPackets=$(get_dante_json_field ".clock.dsaTaggedPackets")
|
||
else
|
||
dsaTaggedPackets=false
|
||
fi
|
||
|
||
clockHwInterfaces=""
|
||
if [ "$hwTsField" = true ] || [ "$hwTsField" = "v1" ]; then
|
||
# Check if AES67 is being supported (if so, PTPv1 hardware timestamping cannot be used)
|
||
aes67Supported=$(get_dante_json_field ".audio.aes67Supported")
|
||
|
||
if [ "$aes67Supported" = true ] && [ "$hwTsField" = "v1" ]; then
|
||
logerr "AES67 cannot be supported with PTPv1 timestamping."
|
||
echo "${TAB}To enable AES67, enableHwTimestamping must be set to either true or false."
|
||
return
|
||
fi
|
||
|
||
# Hardware timestamping is in use, so check PHCs.
|
||
#
|
||
# If dsaTaggedPackets is false, the NICs whose PHCs need to be checked are those in
|
||
# .network.interfaces.
|
||
if [ "$dsaTaggedPackets" = true ]; then
|
||
clockHwInterfaces=$(get_dante_json_array_elements ".clock.hardwareInterfaces")
|
||
|
||
# $clockHwInterfaces will be empty if it is either undefined or invalid.
|
||
# Error and return in this case.
|
||
if [ -z "$clockHwInterfaces" ]; then
|
||
logerr "'clock.hardwareInterfaces' is undefined or invalid - unable to check PHC(s) or run preliminary hardware timestamping checks"
|
||
return
|
||
fi
|
||
|
||
ifacesToCheck=$clockHwInterfaces
|
||
else
|
||
ifacesToCheck=$networkInterfaces
|
||
fi
|
||
|
||
previousPHC=""
|
||
for iface in $ifacesToCheck; do
|
||
# $iface needs to be associated with a PHC device for hardware timestamping to work
|
||
phcName=""
|
||
|
||
ptpPath="/sys/class/net/$iface/device/ptp"
|
||
if [ -d "$ptpPath" ]; then
|
||
for d in "$ptpPath"/*; do
|
||
if [ -d "$d" ] && [ -f "$d/clock_name" ]; then
|
||
phcName=$(basename "$d")
|
||
deviceNode="/dev/$phcName"
|
||
fi
|
||
done
|
||
elif cmd_exists ethtool; then
|
||
phc=$(ethtool -T "$iface" 2>/dev/null | awk "/PTP Hardware Clock:/ {print \$4}")
|
||
if [ -n "$phc" ] && [ "$phc" != "none" ]; then
|
||
phcName="ptp$phc"
|
||
deviceNode="/dev/$phcName"
|
||
fi
|
||
fi
|
||
|
||
if [ -z "$phcName" ]; then
|
||
logerr "no PTP device found for $iface."
|
||
echo "${TAB}hardware timestamping cannot be enabled when using this interface."
|
||
return
|
||
fi
|
||
|
||
if [ -n "$previousPHC" ] && [ "$previousPHC" = "$phcName" ]; then
|
||
# already checked this node, skip it
|
||
continue
|
||
fi
|
||
previousPHC="$phcName"
|
||
|
||
loginfo "$iface: PHC device found ($phcName)"
|
||
|
||
if [ ! -e "$deviceNode" ]; then
|
||
logerr "corresponding PTP device node '$deviceNode' not found for $iface interface"
|
||
continue
|
||
fi
|
||
|
||
# we have a PHC node, has the user configured it in config.json?
|
||
is_device_node_configured "$deviceNode"
|
||
done
|
||
fi
|
||
|
||
# Perform preliminary timestamping checks using the PTP timestamping test tool
|
||
if [ "$PTP_TEST_COMPATIBLE" -ne 1 ]; then
|
||
# Do a test run of the tool to see if the host's glibc really is incompatible.
|
||
# If it is, the checks are skipped.
|
||
${PTP_TEST} -h > /dev/null 2>&1
|
||
if [ $? -ne 0 ]; then
|
||
logwarn "host-provided glibc ($installedGlibc) is older than required ($REQUIRED_GLIBC_PTP_TEST) for ${PTP_TEST}"
|
||
echo "${TAB}Skipping preliminary timestamping checks"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
# Initially, we assume that all timestamping modes will pass their preliminary checks.
|
||
# If EITHER the primary or secondary interface has a failed or skipped check for a
|
||
# particular timestamping mode, that mode has its check marked as having failed.
|
||
hwTsPrelimCheckOk=1
|
||
hwV1TsPrelimCheckOk=1
|
||
swTsPrelimCheckOk=1
|
||
|
||
if [ "$dsaTaggedPackets" = true ] && [ "$hwTsField" = false ]; then
|
||
loginfo "DSA tagging in use along with software timestamping - skipping preliminary hardware timestamping checks."
|
||
echo "${TAB}To perform these checks, please rerun this script after turning on hardware timestamping and ensuring"
|
||
echo "${TAB}that 'clock.hardwareInterfaces' contains valid entries."
|
||
hwTsPrelimCheckOk=0
|
||
hwV1TsPrelimCheckOk=0
|
||
fi
|
||
|
||
primaryCheckCmdsAndResults=""
|
||
secondaryCheckCmdsAndResults=""
|
||
|
||
ifaceIndex=0
|
||
hwIface=""
|
||
for iface in $networkInterfaces; do
|
||
if [ -n "$clockHwInterfaces" ]; then
|
||
hwIface=$(echo "$DANTE_JSON_DATA" | "$JQ" -cr ".clock.hardwareInterfaces[$ifaceIndex]")
|
||
if [ "$hwIface" = null ]; then
|
||
# We'll hit this if clock.hardwareInterfaces has fewer entries than
|
||
# network.interfaces. This error will already have been reported in
|
||
# an earlier check, but it will also interfere with proper timestamping
|
||
# checking and so log another error here and exit this function.
|
||
logerr "Unable to perform preliminary timestamping check for interface $iface, as associated 'clock.hardwareInterfaces' entry is missing"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
# NOTE: if we're now checking the secondary interface, it's possible that
|
||
# $hwIface is common to both the primary and secondary. The preliminary checks
|
||
# below will end up checking hardware timestamping on $hwIface a second time -
|
||
# even though this is arguably redundant, we still want to do this in order to
|
||
# be able to show the user the full timestamping test command lines (which will
|
||
# feature the primary and secondary interfaces respectively).
|
||
checkCmdsAndResults=$(timestamping_preliminary_checks "$iface" "$hwTsField" "$dsaTaggedPackets" "$hwIface")
|
||
|
||
if [ "$ifaceIndex" -eq 0 ]; then
|
||
primaryCheckCmdsAndResults="$checkCmdsAndResults"
|
||
else
|
||
secondaryCheckCmdsAndResults="$checkCmdsAndResults"
|
||
fi
|
||
|
||
ifaceIndex=$((ifaceIndex+1))
|
||
done
|
||
|
||
# Keep track of the PTP test tool commands that can be used to do full tests for all
|
||
# interfaces and each timestamping mode. Depending on which modes is later deemed as
|
||
# recommended, the appropriate set of commands will be shown to the user.
|
||
hwTsFullCheckCmds=""
|
||
hwV1TsFullCheckCmds=""
|
||
swTsFullCheckCmds=""
|
||
|
||
# We need to report on the success/failure of the check of the timestamping mode in
|
||
# dante.json
|
||
danteJsonHwTsDescription=""
|
||
danteJsonHwTsFullCheckCmds=""
|
||
danteJsonFailedPrelimCheckCmds=""
|
||
|
||
# Display all preliminary check results first
|
||
for check in $primaryCheckCmdsAndResults $secondaryCheckCmdsAndResults; do
|
||
iface=$(echo "$check" | cut -d'@' -f1)
|
||
hwTsDescription=$(echo "$check" | cut -d'@' -f2 | sed 's/+/ /g') # Restore any spaces
|
||
isDanteJsonSetting=$(echo "$check" | cut -d'@' -f3)
|
||
checkRetval=$(echo "$check" | cut -d'@' -f4)
|
||
|
||
# Don't restore spaces in the command line for now, as it will be added to another
|
||
# array. Spaces will only be restored if the command line ends up being displayed.
|
||
ptpTestToolCmdLine=$(echo "$check" | cut -d'@' -f5)
|
||
|
||
logMsgPrefix="Preliminary check of $hwTsDescription timestamping on interface $iface"
|
||
|
||
if [ "$checkRetval" -eq 0 ]; then
|
||
if [ "$isDanteJsonSetting" -eq 1 ]; then
|
||
logok "$logMsgPrefix passed"
|
||
|
||
danteJsonHwTsDescription="$hwTsDescription"
|
||
danteJsonHwTsFullCheckCmds="$danteJsonHwTsFullCheckCmds $ptpTestToolCmdLine"
|
||
else
|
||
loginfo "$logMsgPrefix passed"
|
||
fi
|
||
|
||
# Add the command line to the ones for the current timestamping mode
|
||
case "$hwTsDescription" in
|
||
"hardware")
|
||
hwTsFullCheckCmds="$hwTsFullCheckCmds $ptpTestToolCmdLine"
|
||
;;
|
||
"software")
|
||
swTsFullCheckCmds="$swTsFullCheckCmds $ptpTestToolCmdLine"
|
||
;;
|
||
*)
|
||
# PTPv1 hardware
|
||
hwV1TsFullCheckCmds="$hwV1TsFullCheckCmds $ptpTestToolCmdLine"
|
||
;;
|
||
esac
|
||
else
|
||
if [ "$isDanteJsonSetting" -eq 1 ]; then
|
||
logerr "$logMsgPrefix failed"
|
||
|
||
danteJsonHwTsDescription="$hwTsDescription"
|
||
danteJsonFailedPrelimCheckCmds="$danteJsonFailedPrelimCheckCmds $ptpTestToolCmdLine"
|
||
else
|
||
logwarn "$logMsgPrefix failed"
|
||
fi
|
||
|
||
case "$hwTsDescription" in
|
||
"hardware")
|
||
hwTsPrelimCheckOk=0
|
||
;;
|
||
"software")
|
||
swTsPrelimCheckOk=0
|
||
;;
|
||
*)
|
||
# PTPv1 hardware
|
||
hwV1TsPrelimCheckOk=0
|
||
;;
|
||
esac
|
||
fi
|
||
done
|
||
|
||
# If no timestamping modes passed their preliminary check, log an error and return
|
||
if [ "$hwTsPrelimCheckOk" -eq 0 ] && [ "$hwV1TsPrelimCheckOk" -eq 0 ] && [ "$swTsPrelimCheckOk" -eq 0 ]; then
|
||
if [ -z "$secondaryCheckCmdsAndResults" ]; then # No secondary
|
||
logerr "Primary interface does not support any of the required timestamping modes and cannot be used."
|
||
echo "${TAB}Please specify an alternative interface in dante.json."
|
||
else
|
||
logerr "Primary and secondary interfaces do not support any of the required timestamping modes in common."
|
||
echo "${TAB}Please remove the secondary interface from dante.json and/or change the interfaces."
|
||
fi
|
||
return
|
||
fi
|
||
|
||
# Internal function for displaying an array of PTP test tool commands. The array will
|
||
# have two entries if using a primary and secondary interface, one otherwise. These
|
||
# commands, obtained from timestamping_preliminary_checks(), need to have their
|
||
# placeholder '+' characters changed back to spaces.
|
||
display_ptp_test_tool_cmds() {
|
||
for cmd in $1; do
|
||
restoredCmd=$(echo "$cmd" | sed 's/+/ /g')
|
||
echo "${TAB}$restoredCmd"
|
||
done
|
||
}
|
||
|
||
display_full_timestamping_test_advice() {
|
||
hwTsDescription=$1
|
||
hwTsFullCheckCmds=$2
|
||
referToReadme=$3
|
||
|
||
echo "${TAB}Please be sure to run the following command(s) to perform a full $hwTsDescription timestamping test:"
|
||
display_ptp_test_tool_cmds "$hwTsFullCheckCmds"
|
||
if [ "$referToReadme" -eq 1 ]; then
|
||
echo "${TAB}Please see $PTP_TEST_README for advice on running a full test."
|
||
fi
|
||
}
|
||
|
||
# Determine the recommended timestamping setting
|
||
recommendedHwTsDescription=""
|
||
recommendedHwTsFullCheckCmds=""
|
||
|
||
if [ "$hwTsPrelimCheckOk" -eq 1 ]; then
|
||
recommendedHwTsDescription="hardware"
|
||
recommendedHwTsFullCheckCmds="$hwTsFullCheckCmds"
|
||
elif [ "$swTsPrelimCheckOk" -eq 1 ]; then
|
||
if [ "$hwV1TsPrelimCheckOk" -eq 1 ]; then
|
||
recommendedHwTsDescription="either software or PTPv1-only hardware"
|
||
recommendedHwTsFullCheckCmds="$swTsFullCheckCmds $hwV1TsFullCheckCmds"
|
||
else
|
||
recommendedHwTsDescription="software"
|
||
recommendedHwTsFullCheckCmds="$swTsFullCheckCmds"
|
||
fi
|
||
else
|
||
recommendedHwTsDescription="PTPv1-only hardware"
|
||
recommendedHwTsFullCheckCmds="$hwV1TsFullCheckCmds"
|
||
fi
|
||
|
||
referToReadme=1
|
||
|
||
# Check whether the timestamping setting in dante.json passed or not
|
||
if [ -z "$danteJsonFailedPrelimCheckCmds" ]; then
|
||
logok "All preliminary checks passed for $danteJsonHwTsDescription timestamping."
|
||
if [ "$recommendedHwTsDescription" = "$danteJsonHwTsDescription" ]; then
|
||
echo "${TAB}Based on the checks above, $danteJsonHwTsDescription timestamping is the recommended setting."
|
||
fi
|
||
display_full_timestamping_test_advice "$danteJsonHwTsDescription" "$danteJsonHwTsFullCheckCmds" "$referToReadme"
|
||
|
||
# If the dante.json setting is the recommended one, there's nothing more to do
|
||
if [ "$recommendedHwTsDescription" = "$danteJsonHwTsDescription" ]; then
|
||
return
|
||
fi
|
||
|
||
# Prepare to offer advice on an alternative, recommended setting
|
||
however=" however,"
|
||
referToReadme=0 # We've displayed it already, so don't do it again
|
||
else
|
||
logwarn "Preliminary check(s) failed for $danteJsonHwTsDescription timestamping."
|
||
echo "${TAB}Therefore, $danteJsonHwTsDescription timestamping cannot be used with the interface(s) configured in dante.json."
|
||
echo "${TAB}For more details on the failure(s), the following command(s) can be run:"
|
||
display_ptp_test_tool_cmds "$danteJsonFailedPrelimCheckCmds"
|
||
|
||
# Suggest the recommended setting(s)
|
||
however=""
|
||
fi
|
||
|
||
loginfo "Based on the checks above,$however the recommended setting is $recommendedHwTsDescription timestamping."
|
||
if [ "$recommendedHwTsDescription" = "hardware" ]; then
|
||
if [ "$danteJsonHwTsDescription" = "PTPv1-only hardware" ]; then
|
||
echo "${TAB}This will enable use of PTPv2 features such as AES67 and site/domain unicast clocking."
|
||
else
|
||
echo "${TAB}This will provide better clocking stability."
|
||
fi
|
||
elif [ "$recommendedHwTsDescription" = "either software or PTPv1-only hardware" ]; then
|
||
# Explain the tradeoffs for each mode, however only display the full test advice
|
||
# if it has not already been displayed.
|
||
echo "${TAB}With software, clocking is not as accurate as hardware but PTPv2 features such as"
|
||
echo "${TAB}AES67 and site/domain unicast clocking can be used."
|
||
if [ "$danteJsonHwTsDescription" != "software" ]; then
|
||
display_full_timestamping_test_advice "software" "$swTsFullCheckCmds" "$referToReadme"
|
||
referToReadme=0
|
||
fi
|
||
echo "${TAB}With PTPv1-only hardware, clocking will be more stable but PTPv2 features such as"
|
||
echo "${TAB}AES67 and site/domain unicast clocking cannot be used."
|
||
if [ "$danteJsonHwTsDescription" != "PTPv1-only hardware" ]; then
|
||
display_full_timestamping_test_advice "PTPv1-only hardware" "$hwV1TsFullCheckCmds" "$referToReadme"
|
||
fi
|
||
|
||
# Advice displayed, so nothing more to do
|
||
return
|
||
elif [ "$recommendedHwTsDescription" = "software" ]; then
|
||
# If v1 was configured but is not supported, explain that PTPv2 can now be used
|
||
if [ -n "$danteJsonFailedPrelimCheckCmds" ] && [ "$danteJsonHwTsDescription" = "PTPv1-only hardware" ]; then
|
||
echo "${TAB}Clocking is not as accurate as hardware, but PTPv2 features such as AES67 and"
|
||
echo "${TAB}site/domain unicast clocking can be used."
|
||
fi
|
||
else # $recommendedHwTs = "v1"
|
||
# If software was configured but is not supported, explain that PTPv2 can't be used
|
||
if [ -n "$danteJsonFailedPrelimCheckCmds" ] && [ "$danteJsonHwTsDescription" = "software" ]; then
|
||
echo "${TAB}This will provide better clocking stability, but PTPv2 features such as AES67 and"
|
||
echo "${TAB}site/domain unicast clocking cannot be used."
|
||
fi
|
||
fi
|
||
|
||
display_full_timestamping_test_advice "$recommendedHwTsDescription" "$recommendedHwTsFullCheckCmds" "$referToReadme"
|
||
}
|
||
|
||
check_hw_clock_config() {
|
||
if ! json_field_exists "$DANTE_JSON_DATA" ".hardwareClock.useHwClock"; then
|
||
return
|
||
fi
|
||
|
||
if ! is_json_field_bool "$DANTE_JSON_DATA" ".hardwareClock.useHwClock"; then
|
||
logerr "'hardwareClock.useHwClock' in dante.json is not a boolean value. Please set to either true or false"
|
||
return
|
||
fi
|
||
|
||
useHwClockField=$(get_dante_json_field ".hardwareClock.useHwClock")
|
||
|
||
if [ "$useHwClockField" = false ]; then
|
||
return
|
||
fi
|
||
|
||
loginfo "hardware clock enabled in $DANTE_JSON"
|
||
|
||
circuitNameField=$(get_dante_json_field ".hardwareClock.circuitName")
|
||
if [ -z "$circuitNameField" ]; then
|
||
logerr "'hardwareClock.circuitName' is not set in $DANTE_JSON."
|
||
echo "${TAB}Please specify the circuit name for the hardware clock."
|
||
fi
|
||
|
||
i2cBusField=$(get_dante_json_field ".hardwareClock.i2cBus")
|
||
if [ "$i2cBusField" = null ]; then
|
||
i2cBusField="/dev/i2c-0" # default value
|
||
fi
|
||
|
||
if [ ! -e "$i2cBusField" ]; then
|
||
logerr "i2c bus device node '$i2cBusField' not found on the system."
|
||
echo "${TAB}Please check the 'hardwareClock.i2cBus' field in $DANTE_JSON."
|
||
else
|
||
is_device_node_configured "$i2cBusField"
|
||
fi
|
||
|
||
extClockInputDevField=$(get_dante_json_field ".hardwareClock.extClockInputDev")
|
||
if [ "$extClockInputDevField" = null ]; then
|
||
extClockInputDevField="/dev/extclkin" # default value
|
||
fi
|
||
|
||
if [ ! -e "$extClockInputDevField" ]; then
|
||
logerr "external clock input device node '$extClockInputDevField' not found on the system."
|
||
echo "${TAB}Please check the 'hardwareClock.extClockInputDev' field in $DANTE_JSON."
|
||
else
|
||
is_device_node_configured "$extClockInputDevField"
|
||
fi
|
||
}
|
||
|
||
# Check if an encoding value is valid.
|
||
# Returns:
|
||
# - 1 if the encoding is a standard PCM encoding (PCM16, PCM24, PCM32)
|
||
# - 2 if the encoding is a custom value (between 0x1300 and 0x13FF inclusive)
|
||
# - 0 otherwise (invalid encoding)
|
||
is_encoding_valid() {
|
||
encoding="$1"
|
||
|
||
encodingNumeric=$(printf "%d" "$encoding" 2>/dev/null)
|
||
# shellcheck disable=SC2181
|
||
if [ $? -eq 0 ]; then
|
||
# check if encoding is in 0x1300–0x13FF range
|
||
case "$encoding" in
|
||
0x13[0-9A-Fa-f][0-9A-Fa-f])
|
||
if [ "$encodingNumeric" -ge 4864 ] && [ "$encodingNumeric" -le 5119 ]; then
|
||
echo 2
|
||
return
|
||
fi
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
# standard PCM encodings
|
||
case "$encoding" in
|
||
PCM16|PCM24|PCM32)
|
||
echo 1
|
||
;;
|
||
*)
|
||
echo 0
|
||
;;
|
||
esac
|
||
}
|
||
|
||
check_encodings()
|
||
{
|
||
defaultEncoding=""
|
||
|
||
if json_field_exists "$DANTE_JSON_DATA" "audio.defaultEncoding"; then
|
||
defaultEncoding=$(get_dante_json_field ".audio.defaultEncoding")
|
||
encodingType=$(is_encoding_valid "$defaultEncoding")
|
||
if [ "$encodingType" -eq 0 ]; then
|
||
logerr "defaultEncoding $defaultEncoding is invalid in $DANTE_JSON"
|
||
elif [ "$encodingType" -eq 2 ]; then
|
||
defaultEncoding=$(echo "$defaultEncoding" | tr '[a-f]' '[A-F]')
|
||
fi
|
||
logwarn "the 'defaultEncoding' field should no longer be used and will be removed in future versions of DEP."
|
||
echo "${TAB}The default should instead be specified as the first entry in 'supportedEncodings'"
|
||
fi
|
||
|
||
if [ -z "$defaultEncoding" ] && ! json_field_exists "$DANTE_JSON_DATA" ".audio.supportedEncodings"; then
|
||
loginfo "neither 'supportedEncodings' nor 'defaultEncoding' specified - PCM24 will be used by default"
|
||
return
|
||
fi
|
||
|
||
if ! is_json_field_array "$DANTE_JSON_DATA" ".audio.supportedEncodings"; then
|
||
logerr "'supportedEncodings' in $DANTE_JSON is not an array"
|
||
return
|
||
fi
|
||
|
||
supportedEncodingsCount=$(get_json_array_length "$DANTE_JSON_DATA" ".audio.supportedEncodings")
|
||
if [ "$supportedEncodingsCount" -eq 0 ]; then
|
||
logerr "'supportedEncodings' cannot be specified as empty in $DANTE_JSON"
|
||
return
|
||
fi
|
||
|
||
supportedEncodings=$(get_dante_json_array_elements ".audio.supportedEncodings")
|
||
|
||
# check for duplicates in supportedEncodings
|
||
if echo "$DANTE_JSON_DATA" | "$JQ" -e '.audio.supportedEncodings | sort | group_by(.) | any(length > 1)' > /dev/null; then
|
||
logerr "'supportedEncodings' contains duplicate entries in $DANTE_JSON"
|
||
fi
|
||
|
||
customEncodingCount=0
|
||
loopIndex=0
|
||
|
||
for encoding in $supportedEncodings; do
|
||
loopIndex=$((loopIndex+1))
|
||
encodingType=$(is_encoding_valid "$encoding")
|
||
|
||
if [ "$encodingType" -eq 0 ]; then
|
||
logerr "'supportedEncodings' contains the invalid encoding $encoding in $DANTE_JSON"
|
||
elif [ "$encodingType" -eq 2 ]; then
|
||
customEncodingCount=$((customEncodingCount+1))
|
||
if [ $customEncodingCount -gt 4 ]; then
|
||
logerr "'supportedEncodings' cannot contain more than 4 custom encodings in $DANTE_JSON"
|
||
fi
|
||
# convert encoding string to uppercase
|
||
encoding=$(echo "$encoding" | tr '[a-f]' '[A-F]')
|
||
fi
|
||
|
||
# if defaultEncoding is specified and we are looping over supportedEncodings,
|
||
# check that it matches the first entry in supportedEncodings
|
||
if [ -n "$defaultEncoding" ] && [ "$loopIndex" -eq 1 ]; then
|
||
if [ "$encoding" != "$defaultEncoding" ]; then
|
||
logerr "'supportedEncodings' does not match the specified 'defaultEncoding' in $DANTE_JSON"
|
||
fi
|
||
fi
|
||
done
|
||
}
|
||
|
||
check_old_files() {
|
||
mainPath=${DEP_PATH}"/"
|
||
|
||
oldRootfsFound=0
|
||
if [ -f "$mainPath/dante_data/images/1/rootfs_squash" ]; then
|
||
oldRootfsFound=1
|
||
fi
|
||
|
||
# any old file around?
|
||
if ls -1 "$mainPath" | grep -qE '^(runc|fixer|dep_util.sh|dep.service|select_image.sh)$' || [ $oldRootfsFound -eq 1 ]; then
|
||
loginfo "These files can be removed to reduce disk usage:"
|
||
else
|
||
return
|
||
fi
|
||
|
||
if [ -f "$mainPath/runc" ]; then
|
||
echo "${TAB}runc (old container runtime)"
|
||
fi
|
||
if [ -f "$mainPath/fixer" ]; then
|
||
echo "${TAB}fixer (deprecated and no longer used)"
|
||
fi
|
||
if [ -f "$mainPath/dep_util.sh" ]; then
|
||
echo "${TAB}dep_util.sh (deprecated and no longer used)"
|
||
fi
|
||
if [ -f "$mainPath/dep.service" ]; then
|
||
echo "${TAB}dep.service (old systemd service file, a new updated version is available in the development subdirectory)"
|
||
fi
|
||
if [ -f "$mainPath/select_image.sh" ]; then
|
||
echo "${TAB}select_image.sh (not used anymore)"
|
||
fi
|
||
if [ "$oldRootfsFound" -eq 1 ]; then
|
||
echo "${TAB}dante_data/images/1/rootfs_squash (second rootfs image deprecated and no longer used)"
|
||
fi
|
||
}
|
||
|
||
### main logic
|
||
|
||
# check we are running as root
|
||
check_uid
|
||
|
||
# make sure the first arg is a valid path
|
||
check_arg "$1"
|
||
|
||
check_runtime_deps
|
||
|
||
load_config_files
|
||
|
||
check_architecture
|
||
check_kernel_config
|
||
check_cgroups
|
||
check_cpu_config
|
||
check_rng_speed
|
||
check_network_interfaces
|
||
check_clock_hardware_interfaces
|
||
check_timestamping_config
|
||
check_hw_clock_config
|
||
check_encodings
|
||
check_old_files
|
||
|
||
#
|
||
# Copyright © 2022-2025 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||
#
|