From df331cbb77e29900873a6a019addbc697d311b34 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 31 Oct 2026 11:05:57 +0100 Subject: [PATCH 1/7] Add scripts for quick setup of network and kernel --- scripts/realtime-config-ethernet | 137 ++++++++++++++++++ scripts/realtime-config-kernel | 238 +++++++++++++++++++++++++++++++ 2 files changed, 375 insertions(+) create mode 100755 scripts/realtime-config-ethernet create mode 100755 scripts/realtime-config-kernel diff --git a/scripts/realtime-config-ethernet b/scripts/realtime-config-ethernet new file mode 100755 index 00000000000..7e12f8c5f2a --- /dev/null +++ b/scripts/realtime-config-ethernet @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# realtime-config-ethernet: Configure a network card for realtime +# +# This sets all network card parameters known to improve latency +# with ethtool. +# +# Additionally, the IRQ and IQR handler process is assigned to +# the given CPU which should be the one linuxcnc runs on. +# +# Realtime priority is assigned to the IRQ handler process. +# Only PREEMPT_RT has a dedicated IRQ handler process and some +# network drivers (r8169 for example) use ksoftirqd on the same +# CPU the IRQ is assigned to. So realtime priority is also set +# for ksoftirqd/$CPU. +# +# Warning: These settings are not persistent, so they have +# to be set after each boot. +# +# Can be integrated in /etc/network/interfaces the following way +# assuming your network card is enp2s0f0 and your realtime +# CPU is CPU3: +# +#auto enp2s0f0 +#iface enp2s0f0 inet static +# address 192.168.1.120/31 +# post-up realtime-config-ethernet enp2s0f0 3 +# +#Copyright: 2026 Hannes Diethelm +#License: GPL Version 2 + +set -euo pipefail +trap 'echo "ERROR: line $LINENO: $BASH_COMMAND" >&2' ERR + +if [[ "$#" -ne 2 ]]; then + echo "Usage: realtime-config-ethernet nic cpu" >&2 + exit 1 +fi + +if [[ $EUID -ne 0 ]]; then + echo "Error: run this script as root (sudo)" >&2 + exit 1 +fi + +NIC=$1 +CPU=$2 + +if [[ ! -d "/sys/class/net/$NIC" ]]; then + echo "Interface $NIC does not exist" >&2 + exit 1 +fi + +if [[ ! "$CPU" =~ ^[0-9]+$ ]]; then + echo "Error: cpu must be a positive integer, got $CPU" >&2 + exit 1; +fi + +#Priority 90 should be fine for most cases +#It should be lower than lowest linuxcnc task +RT_PRIO_IRQ=90 +RT_PRIO_KSOFTIRQD=80 + +#Build mask for taskset and smp_affinity +if [ "$CPU" -gt 63 ]; then + echo "No support for 64 CPU's yet" + exit 1 +fi +MASK=$(printf '%x\n' "$((2**CPU))") +if (( ${#MASK} > 8 )); then + MASKIRQ="${MASK:0:${#MASK}-8},${MASK: -8}" +else + MASKIRQ=$MASK +fi + +echo Setting network card config---------------------- +echo These fail if not supported by card +echo +echo Setting coalesce... +ethtool -C "$NIC" rx-usecs 0 || true +ethtool -C "$NIC" rx-frames 1 || true +ethtool -C "$NIC" adaptive-rx off || true +ethtool -C "$NIC" tx-usecs 0 || true +ethtool -C "$NIC" tx-frames 1 || true +ethtool -C "$NIC" adaptive-tx off || true +echo +echo Coalesce config: +ethtool -c "$NIC" || true +echo + +echo Setting channels... +ethtool -L "$NIC" rx 1 || true +ethtool -L "$NIC" tx 1 || true +ethtool -L "$NIC" combined 1 || true +echo +echo Channels config: +ethtool -l "$NIC" || true +echo + +echo Setting eee... +ethtool --set-eee "$NIC" eee off || true +echo eee config: +ethtool --show-eee ens4 || true +echo + +echo Setting process and IRQ config------------------- +echo +echo "Setting affinity for $NIC to use CPU Mask $MASK..." + +IRQS=$(grep "$NIC" /proc/interrupts | awk -F: '{print $1}' | tr -d ' ') || true +if [ -z "$IRQS" ]; then + echo "Error: No IRQ's found for $NIC" + exit 1 +fi + +for IRQ in $IRQS; do + PID=$(pgrep "irq/$IRQ-$NIC" || true) + if [ -z "$PID" ]; then + echo "No process found for IRQ $IRQ, this is no issue if you use a kernel without PREEMPT_RT patch" + else + echo "Setting pid $PID irq $IRQ process $(cat "/proc/$PID/comm") to use realtime priority $RT_PRIO_IRQ and mask $MASK" + chrt -p "$RT_PRIO_IRQ" "$PID" + taskset -p "$MASK" "$PID" + fi + echo "Setting irq $IRQ to mask $MASKIRQ" + echo "$MASKIRQ" > "/proc/irq/$IRQ/smp_affinity" +done +echo +echo "Setting affinity for ksoftirqd/$CPU..." +PID=$(pgrep -x "ksoftirqd/$CPU" || true) +if [ -z "$PID" ]; then + echo "No process found for ksoftirqd/$CPU" +else + echo "Setting pid $PID process $(cat "/proc/$PID/comm") to use realtime priority $RT_PRIO_KSOFTIRQD" + chrt -p "$RT_PRIO_KSOFTIRQD" "$PID" +fi + +echo +echo "done" diff --git a/scripts/realtime-config-kernel b/scripts/realtime-config-kernel new file mode 100755 index 00000000000..41e2c7b8275 --- /dev/null +++ b/scripts/realtime-config-kernel @@ -0,0 +1,238 @@ +#!/usr/bin/env bash +# realtime-config-kernel — config kernel command line and sysctl for this machine +# +# Rules +# - isolate the highest logical CPU and it's SMT sibling if SMT is active +# - irqaffinity on all remaining CPU's +# - rcu_nocbs / nohz_full on the isolated CPU +# - SMT state from smt/control: +# notsupported -> no SMT hardware, no 'nosmt', no question +# on / off -> SMT hardware exists -> ASK whether to add 'nosmt' +# - intel_idle.max_cstate=1 and processor.max_cstate=1 to work on both AMD and Intel +# +# Copyright: 2026 Hannes Diethelm +# License: GPL Version 2 + +set -euo pipefail +trap 'echo "ERROR: line $LINENO: $BASH_COMMAND" >&2' ERR + +# --- CPU detection ------------------------------------------- +N_ALL="$(nproc --all)" #All CPU's including SMT's even if SMT is off +if (( N_ALL < 2 )); then + echo "error: need at least 2 logical CPUs (got $N_ALL)" >&2 + exit 1 +fi + +# --- Kernel detection ---------------------------------------- +kernel_type=UNKNOWN +if [[ -c /dev/evl/control ]]; then + kernel_type=EVL + echo "Xenomai4 EVL kernel detected" +elif uname -v | grep -q PREEMPT_RT; then + kernel_type=PREEMPT_RT + echo "PreemptRT kernel detected" +else + echo "WARNING: Please install PreemptRT or Xenomai4 EVL Kernel" +fi + +# --- SMT detection ------------------------------------------- +# SMT hardware state from smt/active: none (notsupported) / on / off +smtcontrol="$(cat "/sys/devices/system/cpu/smt/control" 2>/dev/null || echo "")" +if [[ -z "$smtcontrol" ]]; then + echo "Error, could not determine SMT state, assuming on" + smtstate=on +else + case "$smtcontrol" in + notsupported) smtstate=none ;; + off|forceoff) smtstate=off ;; + on) smtstate=on ;; + *) echo "Error, could not determine SMT state, assuming on"; smtstate=on ;; + esac +fi + +nosmt="" +if [[ "$smtstate" != "none" ]]; then + read -r -p "SMT hardware present (currently: $smtstate). Add 'nosmt' (run without SMT)? [Y/n] " ans + ans="${ans:-y}" + case "$ans" in + y|Y) nosmt="nosmt" ;; + n|N) ;; + *) echo "y or n"; exit 1;; + esac + if [[ "$smtstate" != "on" && -z "$nosmt" ]]; then + echo "Can not determine SMT sibling, run \"echo on | sudo tee /sys/devices/system/cpu/smt/control\" to enable SMT and try again" + exit 1 + fi +fi + +if [[ "$smtstate" != "none" && -n "$nosmt" ]]; then + N=$((N_ALL / 2)) #When disabling SMT, we have halve the CPU's +else + N=$N_ALL +fi +isolated_main=$((N - 1)) +isolated=$((N - 1)) +nonisolated=0-$((N - 2)) + +sibling="" +if [[ "$smtstate" != "none" && -z "$nosmt" ]]; then + # sibling of the isolated CPU needs to be isolated too if we don't disable SMT + sibs_last="$(cat "/sys/devices/system/cpu/cpu$isolated/topology/thread_siblings_list" 2>/dev/null || echo "")" + for c in ${sibs_last//,/ }; do + if [[ "$c" != "$isolated" ]]; then sibling="$c"; break; fi + done + if [[ -z "$sibling" ]]; then + echo "ERROR: SMT sibling not detected, select yes when asked for nosmt" + fi + isolated=$sibling,$isolated + nonisolated=0-$((sibling-1)),$((sibling+1))-$((N - 2)) +fi + +# --- commandline --------------------------------------------- + +cmdline="isolcpus=managed_irq,domain,$isolated irqaffinity=$nonisolated rcu_nocbs=$isolated rcu_nocb_poll" +if [[ "$kernel_type" != "EVL" ]]; then + #evl has issues with nohz + cmdline="$cmdline nohz=on nohz_full=$isolated" +fi +cmdline="$cmdline intel_idle.max_cstate=1 processor.max_cstate=1 cpufreq.off=1 nosoftlockup nowatchdog" +if [[ -n "$nosmt" ]]; then + cmdline="$cmdline nosmt" +fi +if [[ "$kernel_type" == "EVL" ]]; then + #evl needs evl.oobcpus, so the network card tasks run on the correct CPU + cmdline="$cmdline evl.oobcpus=$isolated_main" +fi + +#Contains all set or unset parameters for merge with existing cmdline +cmdline_parameters=("isolcpus" "irqaffinity" "rcu_nocbs" "rcu_nocb_poll" + "nohz" "nohz_full" + "intel_idle.max_cstate" "processor.max_cstate" "cpufreq.off" "nosoftlockup" "nowatchdog" + "nosmt" + "evl.oobcpus") + +# --- report -------------------------------------------------- +echo "Logical CPUs : $N_ALL" +echo "Available CPUs : $N" +echo "SMT : $smtstate" +echo "Isolated CPU : $isolated_main" +if [[ -n "$sibling" ]]; then + echo "Sibling of CPU $isolated_main: $sibling (also isolated)" +fi +echo +echo Recommended kernel command line: +echo "$cmdline" +echo + +# --- add to grub --------------------------------------------- + +merge_cmdline() { + local existing="$1" + local new="$2" + local -a param_list + read -r -a param_list <<< "$3" + local token key opt + local -a result=() + + # Keep existing parameters not overridden by the new ones, + # except options in param_list. + for token in $existing; do + key=${token%%=*} + + # Skip options that should be removed unless explicitly set in new. + for opt in "${param_list[@]}"; do + [[ "$key" == "$opt" ]] && continue 2 + done + + result+=("$token") + done + + # Append the new parameters. + for token in $new; do + result+=("$token") + done + + printf '%s' "${result[*]}" +} + +grub_confg="/etc/default/grub" + +change_grub=false +if [[ ! -f "$grub_confg" ]]; then + echo "$grub_confg not found, edit your bootloader configuration by hand" +else + tmpfile=$(mktemp /tmp/grub.XXXXXX) + source <(grep '^[[:space:]]*GRUB_CMDLINE_LINUX_DEFAULT=' $grub_confg) + cp "$grub_confg" "$tmpfile" + existing=$GRUB_CMDLINE_LINUX_DEFAULT + if [[ "$cmdline" = "$existing" ]]; then + echo "Configured kernel command line is equal to recommended: nothing to do" + elif [[ -n "$existing" ]]; then + merged=$(merge_cmdline "$existing" "$cmdline" "${cmdline_parameters[*]}") + echo "Existing command line:" + echo "$existing" + echo + echo "Merged command line:" + echo "$merged" + echo + read -r -p "Use recommended, merged or abort? [r,m,A] " ans + ans="${ans:-a}" + case "$ans" in + r|R) change_grub=true;; + m|M) cmdline=$merged; change_grub=true ;; + a|A) echo "Edit /etc/default/grub manually";; + *) echo "r, m or a"; exit 1;; + esac + else + change_grub=true + fi +fi + +if [[ "$change_grub" == "true" ]]; then + sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=\"$cmdline\"|" "$tmpfile" + + echo Diff existing vs. new $grub_confg: + diff -u "$grub_confg" "$tmpfile" || true + + read -r -p "Apply changes and update grub? [y/N] " ans + ans="${ans:-n}" + case "$ans" in + y|Y) sudo cp "$tmpfile" "$grub_confg" ; sudo update-grub;; + n|N) ;; + *) echo "y or n"; exit 1;; + esac +fi +rm "$tmpfile" +echo + +# --- sysctl -------------------------------------------------- + +sysctl="/etc/sysctl.d/50-linuxcnc-rt.conf" +tmpfile=$(mktemp /tmp/50-linuxcnc-rt.XXXXXX) +chmod go+r "$tmpfile" + +cat > "$tmpfile" <<'EOF' +kernel.sched_rt_runtime_us=-1 +kernel.timer_migration=0 +EOF + +if cmp -s "$sysctl" "$tmpfile" ; then + echo "$sysctl is ok: nothing to do" +else + if [[ ! -f "$sysctl" ]]; then + echo "$sysctl does not exist. Recommended:" + cat "$tmpfile" + read -r -p "Create this file? [y/N] " ans + else + echo "$sysctl does not contain recommended content:" + diff -u "$sysctl" "$tmpfile" || true + read -r -p "Replace this file? [y/N] " ans + fi + ans="${ans:-n}" + case "$ans" in + y|Y) sudo cp "$tmpfile" "$sysctl" ;; + n|N) ;; + *) echo "y or n"; exit 1;; + esac +fi +rm "$tmpfile" From 68f0cff2dcafca40d171f8274ed96bfcde3e925b Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 31 Oct 2026 13:06:44 +0100 Subject: [PATCH 2/7] realtime-config-kernel: Add big/little support and update logic --- scripts/realtime-config-kernel | 234 ++++++++++++++++++++++++++++----- 1 file changed, 204 insertions(+), 30 deletions(-) diff --git a/scripts/realtime-config-kernel b/scripts/realtime-config-kernel index 41e2c7b8275..46f8b6474b3 100755 --- a/scripts/realtime-config-kernel +++ b/scripts/realtime-config-kernel @@ -17,10 +17,95 @@ set -euo pipefail trap 'echo "ERROR: line $LINENO: $BASH_COMMAND" >&2' ERR # --- CPU detection ------------------------------------------- -N_ALL="$(nproc --all)" #All CPU's including SMT's even if SMT is off -if (( N_ALL < 2 )); then - echo "error: need at least 2 logical CPUs (got $N_ALL)" >&2 - exit 1 + +# Convert Linux CPU lists (0-3,8,10-11) into Bash arrays. +parse_cpulist() { + local list="$1" + local part first last cpu + + CPU_LIST=() + + IFS=',' read -ra parts <<< "$list" + + for part in "${parts[@]}"; do + if [[ "$part" == *-* ]]; then + first="${part%-*}" + last="${part#*-}" + + for ((cpu=first; cpu<=last; cpu++)); do + CPU_LIST+=("$cpu") + done + elif [[ -n "$part" ]]; then + CPU_LIST+=("$part") + fi + done +} + +# Convert Bash CPU array into Linux CPU-list notation. +format_cpulist() { + local -n cpus=$1 + local cpu start previous + local result="" + + local -a sorted=() + + mapfile -t sorted < <( + printf '%s\n' "${cpus[@]}" | sort -nu + ) + + ((${#sorted[@]})) || return 0 + + start="${sorted[0]}" + previous="$start" + + for cpu in "${sorted[@]:1}"; do + if (( cpu == previous + 1 )); then + previous="$cpu" + continue + fi + + [[ -n "$result" ]] && result+="," + + if (( start == previous )); then + result+="$start" + else + result+="$start-$previous" + fi + + start="$cpu" + previous="$cpu" + done + + [[ -n "$result" ]] && result+="," + + if (( start == previous )); then + result+="$start" + else + result+="$start-$previous" + fi + + echo "$result" +} + +# --- Hybrid CPU detection ------------------------------------ + +cpu_type=uniform + +if [[ -s /sys/devices/cpu_core/cpus && + -s /sys/devices/cpu_atom/cpus ]]; then + + echo "Hybrid CPU detected" + echo "Big CPUs : $(cat /sys/devices/cpu_core/cpus)" + echo "Little CPUs: $(cat /sys/devices/cpu_atom/cpus)" + + read -r -p "Use Big or Little CPUs? [B/l] " ans + ans="${ans:-b}" + + case "$ans" in + b|B) cpu_type=big ;; + l|L) cpu_type=little ;; + *) echo "b or l"; exit 1 ;; + esac fi # --- Kernel detection ---------------------------------------- @@ -65,29 +150,125 @@ if [[ "$smtstate" != "none" ]]; then fi fi -if [[ "$smtstate" != "none" && -n "$nosmt" ]]; then - N=$((N_ALL / 2)) #When disabling SMT, we have halve the CPU's -else - N=$N_ALL +# --- CPU topology and isolation ------------------------------ + +# Available CPU IDs. +read -r online_list < /sys/devices/system/cpu/online +parse_cpulist "$online_list" +online_cpus=("${CPU_LIST[@]}") + +# Select CPU class. +case "$cpu_type" in + big) + read -r selected_list < /sys/devices/cpu_core/cpus + ;; + little) + read -r selected_list < /sys/devices/cpu_atom/cpus + ;; + *) + selected_list="$online_list" + ;; +esac + +parse_cpulist "$selected_list" +selected_cpus=() + +for cpu in "${CPU_LIST[@]}"; do + if [[ " ${online_cpus[*]} " == *" $cpu "* ]]; then + selected_cpus+=("$cpu") + fi +done + +if ((${#selected_cpus[@]} == 0)); then + echo "ERROR: No online CPUs in selected CPU class" + exit 1 fi -isolated_main=$((N - 1)) -isolated=$((N - 1)) -nonisolated=0-$((N - 2)) - -sibling="" -if [[ "$smtstate" != "none" && -z "$nosmt" ]]; then - # sibling of the isolated CPU needs to be isolated too if we don't disable SMT - sibs_last="$(cat "/sys/devices/system/cpu/cpu$isolated/topology/thread_siblings_list" 2>/dev/null || echo "")" - for c in ${sibs_last//,/ }; do - if [[ "$c" != "$isolated" ]]; then sibling="$c"; break; fi + +# Select highest-numbered CPU in selected class. +isolated_main=0 + +for cpu in "${selected_cpus[@]}"; do + (( cpu > isolated_main )) && isolated_main=$cpu +done + +siblings=() +isolated_cpus=("${isolated_main[@]}") +if [[ "$smtstate" = "on" ]]; then + # Determine SMT siblings of selected CPU. + sibling_list="$(cat "/sys/devices/system/cpu/cpu$isolated_main/topology/thread_siblings_list")" + parse_cpulist "$sibling_list" + siblings=() + for cpu in "${CPU_LIST[@]}"; do + [[ "$cpu" == "$isolated_main" ]] || siblings+=("$cpu") done - if [[ -z "$sibling" ]]; then - echo "ERROR: SMT sibling not detected, select yes when asked for nosmt" + + if [[ -z "$nosmt" ]]; then + # SMT stays active: Isolate all in the siblings list + isolated_cpus=("${CPU_LIST[@]}") + else + # SMT going to be disabled: Isolate the first CPU in the list which is the main one + # which will survive nosmt + siblings=() + isolated_main="${CPU_LIST[0]}" + isolated_cpus=("${isolated_main[@]}") fi - isolated=$sibling,$isolated - nonisolated=0-$((sibling-1)),$((sibling+1))-$((N - 2)) fi +# Build isolated CPU lookup. +declare -A isolated_map=() + +for cpu in "${isolated_cpus[@]}"; do + isolated_map[$cpu]=1 +done + +# When using nosmt, exclude all siblings of the isolated CPU +# from IRQ affinity as well. +if [[ -n "$nosmt" ]]; then + for cpu in "${siblings[@]}"; do + isolated_map[$cpu]=1 + done +fi + +# Build nonisolated CPU list. +nonisolated_cpus=() + +for cpu in "${online_cpus[@]}"; do + if [[ -n "${isolated_map[$cpu]:-}" ]]; then + continue + fi + + if [[ "$smtstate" == "on" && -n "$nosmt" ]]; then + sibling_list="$(cat "/sys/devices/system/cpu/cpu$cpu/topology/thread_siblings_list")" + parse_cpulist "$sibling_list" + + # Only the first CPU in the sibling list survives nosmt. + if [[ "$cpu" == "${CPU_LIST[0]}" ]]; then + nonisolated_cpus+=("$cpu") + fi + else + nonisolated_cpus+=("$cpu") + fi +done + +isolated="$(format_cpulist isolated_cpus)" +nonisolated="$(format_cpulist nonisolated_cpus)" + +if [[ -z "$nonisolated" ]]; then + echo "ERROR: No CPUs available for IRQ affinity" + exit 1 +fi + +# --- report cpu ---------------------------------------------- +echo "Online CPUs : $online_list" +echo "CPU type : $cpu_type" +echo "SMT : $smtstate" +echo "Isolated CPU : $isolated_main" +if ((${#siblings[@]} > 0)); then + echo "Sibling of CPU $isolated_main: ${siblings[*]} (also isolated)" +fi +echo "IRQ affinity : $nonisolated" + + # --- commandline --------------------------------------------- cmdline="isolcpus=managed_irq,domain,$isolated irqaffinity=$nonisolated rcu_nocbs=$isolated rcu_nocb_poll" @@ -111,14 +292,7 @@ cmdline_parameters=("isolcpus" "irqaffinity" "rcu_nocbs" "rcu_nocb_poll" "nosmt" "evl.oobcpus") -# --- report -------------------------------------------------- -echo "Logical CPUs : $N_ALL" -echo "Available CPUs : $N" -echo "SMT : $smtstate" -echo "Isolated CPU : $isolated_main" -if [[ -n "$sibling" ]]; then - echo "Sibling of CPU $isolated_main: $sibling (also isolated)" -fi +# --- report cmdline ------------------------------------------ echo echo Recommended kernel command line: echo "$cmdline" From c630f64211a1cb6c45051f5c45c6a92d286759a9 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Thu, 5 Nov 2026 22:59:48 +0100 Subject: [PATCH 3/7] Create latency-ping --- scripts/latency-ping | 93 +++++++++ src/hal/components/ping.comp | 357 +++++++++++++++++++++++++++++++++++ 2 files changed, 450 insertions(+) create mode 100755 scripts/latency-ping create mode 100644 src/hal/components/ping.comp diff --git a/scripts/latency-ping b/scripts/latency-ping new file mode 100755 index 00000000000..474cc80ab71 --- /dev/null +++ b/scripts/latency-ping @@ -0,0 +1,93 @@ +#!/bin/bash +SCRIPT_LOCATION=$(dirname "$(readlink -f "$0")"); +if [ -f "$SCRIPT_LOCATION/rip-environment" ] && [ -z "$EMC2_HOME" ]; then + . "$SCRIPT_LOCATION/rip-environment" +fi + +T=$(mktemp -d) +trap 'cd / && [ -d "$T" ] && rm -rf "$T"' SIGINT SIGTERM EXIT +cd "$T" || { echo "E: Cannot change directory to '$T'"; exit 1; } + +calc() { awk "BEGIN { print ($1); }" < /dev/null; } +icalc() { awk "BEGIN { printf \"%.0f\n\", ($1); }" < /dev/null; } + +parse_time () { + case $1 in + -) echo "0" ;; + *ns) icalc "${1%ns}" ;; + *us|*µs) icalc "1000*${1%us}" ;; + *ms) icalc "1000*1000*${1%ms}" ;; + *s) icalc "1000*1000*1000*${1%s}" ;; + *) if [ "$1" -lt 1000 ]; then icalc "1000*$1"; else icalc "$1"; fi ;; + esac +} + +human_time () { + if [ "$1" -eq 0 ]; then echo "-" + elif [ "$1" -ge 1000000000 ]; then echo "$(calc "$1"/1000/1000/1000)s" + elif [ "$1" -ge 1000000 ]; then echo "$(calc "$1"/1000/1000)ms" + elif [ "$1" -ge 1000 ]; then echo "$(calc "$1"/1000)µs" + else echo "$1ns" + fi +} + +usage () { + echo "Usage:" + echo " latency-ping [-t timeout] [-p period] [-n nbins ] [-b binsize ] ip_address [if_name]" + echo "" + echo "Example:" + echo " latency-ping 192.168.1.121 enp1s0" + echo "" + echo "Defaults: timeout=${TIMEOUT}nS period=${PERIOD}nS nbins=$NBINS binsize=period/2/nbins" + echo "Equivalently: timeout=$(human_time "$TIMEOUT") period=$(human_time "$PERIOD") nbins=$NBINS binsize=2500" + echo "" + echo "Times may be specified with suffix \"s\", \"ms\", \"us\" \"µs\", or \"ns\"" + echo "Times without a suffix and less than 1000 are taken to be in us;" + echo "other times without a suffix are taken to be in ns" +} + +TIMEOUT=$(parse_time 800us) +PERIOD=$(parse_time 1ms) +NBINS=200 + +while getopts t:p:n:b:h opt; do + case "$opt" in + t) TIMEOUT=$(parse_time "$OPTARG");; + p) PERIOD=$(parse_time "$OPTARG");; + n) NBINS="$OPTARG";; + b) BINSIZE="$OPTARG";; + h|?) usage; exit 0 ;; + *) usage; exit 1 ;; + esac +done +shift $((OPTIND-1)) + +case $# in +1) IP="$1" ;; +2) IP="$1" IF="$2" ;; +*) usage; exit 1 ;; +esac + +if [ -z "$BINSIZE" ]; then + BINSIZE=$((PERIOD/2/NBINS)) +fi + +if [ -z "$IF" ]; then +cat > lat.hal < lat.hal < +#include +#include +#include +#include +#include +#include +#include +#include + +static char *ip_address = "127.0.0.1"; +RTAPI_MP_STRING(ip_address, "IP address to ping"); + +static char *if_name = NULL; +RTAPI_MP_STRING(if_name, "Interface name to ping"); + +static int timeout = 800000; +RTAPI_MP_INT(timeout, "Timeout in ns"); + +#define PING_PKT_SIZE 64 + +typedef struct{ + char *ip_address; + int sockfd; + struct sockaddr_in addr; + struct arpreq arp_request; + long long int time_start; + long long int time_end; + uint16_t tx_seq; + uint16_t tx_id; + bool send; + unsigned char tbuffer[PING_PKT_SIZE]; + unsigned char rbuffer[2*PING_PKT_SIZE]; +}ping_data; + +static ping_data data={}; + +#undef max +#define max(a,b) ((a)>(b)?(a):(b)) + +#if BYTE_ORDER == LITTLE_ENDIAN +# define ODDBYTE(v) (v) +#elif BYTE_ORDER == BIG_ENDIAN +# define ODDBYTE(v) ((unsigned short)(v) << 8) +#else +# define ODDBYTE(v) htons((unsigned short)(v) << 8) +#endif + +//in_cksum from: https://github.com/iputils/iputils/blob/master/ping/ping.c +//Licence: BSD-3-Clause +static unsigned short +in_cksum(const unsigned short *addr, int len, unsigned short csum) +{ + int nleft = len; + const unsigned short *w = addr; + unsigned short answer; + int sum = csum; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) + sum += ODDBYTE(*(unsigned char *)w); /* le16toh() may be unavailable on old systems */ + + /* + * add back carry outs from top 16 bits to low 16 bits + */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return (answer); +} + +bool setup_ping(ping_data *data){ + int sockfd; + data->addr.sin_family = AF_INET; + data->addr.sin_port = htons(0); + data->addr.sin_addr.s_addr = inet_addr(data->ip_address); + + // Create socket + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (sockfd < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: socket %m"); + return false; + } + + int ttl_val = 64; + // Set socket options at IP to TTL 64 + if(setsockopt(sockfd, SOL_IP, IP_TTL, &ttl_val, sizeof(ttl_val)) != 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: setsockopt IP_TTL %m\n"); + close(sockfd); + return false; + } + + struct timeval tv; + tv.tv_sec = timeout/1000000000; + tv.tv_usec = (timeout/1000)%1000000; + + // Setting timeout + if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv) != 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: setsockopt SO_RCVTIMEO %m\n"); + close(sockfd); + return false; + } + data->tx_id=getpid(); + data->sockfd=sockfd; + data->send=true; + return true; +} + +bool setup_arp(ping_data *data, char* if_name) { + int ret = 0; + memset(&data->arp_request, 0, sizeof(data->arp_request)); + ((struct sockaddr_in *)&data->arp_request.arp_pa)->sin_family = AF_INET; + ((struct sockaddr_in *)&data->arp_request.arp_pa)->sin_addr = data->addr.sin_addr; + strncpy(data->arp_request.arp_dev, if_name, sizeof(data->arp_request.arp_dev)-1); + ret = ioctl(data->sockfd, SIOCGARP, &data->arp_request); + if(ret < 0){ + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: ioctl SIOCGARP %m\n"); + return false; + } + if(!(data->arp_request.arp_flags | ATF_COM)) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: arp request failed\n"); + return false; + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: ip=%s mac=%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", + data->ip_address, + (uint8_t)data->arp_request.arp_ha.sa_data[0], + (uint8_t)data->arp_request.arp_ha.sa_data[1], + (uint8_t)data->arp_request.arp_ha.sa_data[2], + (uint8_t)data->arp_request.arp_ha.sa_data[3], + (uint8_t)data->arp_request.arp_ha.sa_data[4], + (uint8_t)data->arp_request.arp_ha.sa_data[5]); + } + + data->arp_request.arp_flags |= ATF_PERM; + ret = ioctl(data->sockfd, SIOCSARP, &data->arp_request); + if(ret < 0){ + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: ioctl SIOCSARP %m\n"); + return false; + } + + return true; +} + +bool cleanup_arp(ping_data *data){ + int ret = ioctl(data->sockfd, SIOCDARP, &data->arp_request); + if(ret < 0){ + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: ioctl SIOCSARP %m\n"); + return false; + } + return true; +} + +bool send_ping(ping_data *data) { + struct icmphdr *hdr = (struct icmphdr *)(data->tbuffer); + unsigned char *msg = data->tbuffer + sizeof(struct icmphdr); + + // Fill the packet + bzero(&data->tbuffer, sizeof(data->tbuffer)); + hdr->type = ICMP_ECHO; + hdr->un.echo.id = htons(data->tx_id); + + for (size_t i = 0; i < PING_PKT_SIZE - sizeof(struct icmphdr); i++) + msg[i] = i + '0'; + + hdr->un.echo.sequence = htons(data->tx_seq); + hdr->checksum = in_cksum((unsigned short *)data->tbuffer, sizeof(data->tbuffer), 0); + + // Send packet + data->time_start = rtapi_get_time(); + ssize_t ret = sendto(data->sockfd, data->tbuffer, sizeof(data->tbuffer), 0, (struct sockaddr*)&data->addr, sizeof(data->addr)); + if (ret <= 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: sendto %m\n"); + return false; + }else{ + return true; + } +} + +enum{ + ERR_TIMEOUT = -1, + ERR_SKIP = -2, + ERR_CORRUPT = -3 +}; + +rtapi_s32 recv_ping(ping_data *data) { + struct sockaddr_in r_addr; + rtapi_s32 rtt_nsec = 0; + + // Receive packet + socklen_t addr_len = sizeof(r_addr); + ssize_t ret = recvfrom(data->sockfd, data->rbuffer, sizeof(data->rbuffer), 0, (struct sockaddr*)&r_addr, &addr_len); + if (ret <= 0) { + if( errno == EAGAIN){ + return ERR_TIMEOUT; + }else{ + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: recvfrom failed: %m\n"); + return ERR_CORRUPT; + } + } else { + data->time_end = rtapi_get_time(); + + struct icmphdr *recv_hdr = (struct icmphdr *)(data->rbuffer); + unsigned short checksum = in_cksum((unsigned short *)data->rbuffer, ret, 0); + uint16_t rx_seq=ntohs(recv_hdr->un.echo.sequence); + if (checksum != 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: received checksum wrong\n"); + return ERR_CORRUPT; + } else if (recv_hdr->type != 0 || recv_hdr->code != 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: package corrupt: received size %li with ICMP type %d code %d tx_seq %d rx_seq %d\n", ret, recv_hdr->type, recv_hdr->code, data->tx_seq, rx_seq); + return ERR_CORRUPT; + } else if (data->tx_seq > rx_seq) { + //Probably did skiped a package due to timeout, don't send on next round, so we get to the correct sequence again + return ERR_SKIP; + } else if (data->tx_seq < rx_seq) { + //The sequence of the received package should never be > than the sent one + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: sequence wrong: received size %li with ICMP type %d code %d tx_seq %d rx_seq %d\n", ret, recv_hdr->type, recv_hdr->code, data->tx_seq, rx_seq); + return ERR_CORRUPT; + } else { + rtt_nsec = data->time_end - data->time_start; + //rtapi_print_msg(RTAPI_MSG_ERR, "%li bytes from %s msg_seq = %d rtt = %.3f us.\n", ret, data->ip_address, rx_seq, (double)rtt_nsec/1000); + } + data->tx_seq ++; + } + + return rtt_nsec; +} + +// Note: the extra setup is run _before_ the pins are created +EXTRA_SETUP() { + (void)prefix; + (void)extra_arg; + (void)__comp_inst; + data.ip_address=ip_address; + if(!setup_ping(&data)){ + return -1; + } + + if(if_name == NULL){ + rtapi_print_msg(RTAPI_MSG_ERR, "ping: warning: if_name not given, can not set\n" + " static arp. Might increase latency.\n"); + } else { + //Ping once to resolve arp + if(send_ping(&data)){ + recv_ping(&data); + } + + if(!setup_arp(&data, if_name)){ + return -1; + } + } + + return 0; +} + +EXTRA_CLEANUP() { + if(if_name != NULL){ + cleanup_arp(&data); + } +} + +FUNCTION(_) { + (void)period; + bool ret = true; + if(data.send){ + ret = send_ping(&data); + } + if(ret){ + rtapi_s32 rtt = recv_ping(&data); + data.send = true; + if(rtt == ERR_TIMEOUT){ + ping_timeout_set(ping_timeout + 1); + }else if(rtt == ERR_CORRUPT){ + ping_err_set(ping_err + 1); + }else if(rtt == ERR_SKIP){ + data.send = false; + }else if(rtt >= 0){ + ping_rtt_set(rtt); + ping_rtt_max_set(max(ping_rtt_max, rtt)); + }else{ + rtapi_print_msg(RTAPI_MSG_ERR, "ping: error: bug"); + } + } + if(reset) { + ping_timeout_set(0); + ping_err_set(0); + ping_rtt_max_set(0); + } +} +#endif From 47f11eb4aa056ddb444329fd79d53222e3f86392 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 9 Nov 2026 23:09:08 +0100 Subject: [PATCH 4/7] Add new realtime tools to Debian package --- debian/linuxcnc.install.in | 3 +++ src/Makefile | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/debian/linuxcnc.install.in b/debian/linuxcnc.install.in index a71302665a0..ef175c797bf 100644 --- a/debian/linuxcnc.install.in +++ b/debian/linuxcnc.install.in @@ -40,6 +40,7 @@ usr/bin/kinslimits usr/bin/latency-histogram usr/bin/latency-plot usr/bin/latency-test +usr/bin/latency-ping usr/bin/lineardelta usr/bin/linuxcnc usr/bin/linuxcnc_check_ini @@ -84,6 +85,8 @@ usr/bin/qtvcp usr/bin/rotarydelta usr/bin/rs274 usr/bin/rtapi_app +usr/bin/realtime-config-kernel +usr/sbin/realtime-config-ethernet usr/bin/scaragui usr/bin/schedrmt usr/bin/scorbot-er-3 diff --git a/src/Makefile b/src/Makefile index 05715dd7985..210cc551f3c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -664,6 +664,9 @@ endif $(EXE) ../scripts/latency-test $(DESTDIR)$(bindir) $(EXE) ../scripts/latency-plot $(DESTDIR)$(bindir) $(EXE) ../scripts/latency-histogram $(DESTDIR)$(bindir) + $(EXE) ../scripts/latency-ping $(DESTDIR)$(bindir) + $(EXE) ../scripts/realtime-config-ethernet $(DESTDIR)$(sbindir) + $(EXE) ../scripts/realtime-config-kernel $(DESTDIR)$(bindir) $(EXE) ../scripts/moveoff_gui $(DESTDIR)$(bindir) $(EXE) ../scripts/hal-histogram $(DESTDIR)$(bindir) $(EXE) ../scripts/xhc-hb04-accels $(DESTDIR)$(bindir) @@ -810,6 +813,7 @@ endif install-kernel-dep: $(DIR) $(DESTDIR)$(moduledir)/linuxcnc \ $(DESTDIR)$(bindir) \ + $(DESTDIR)$(sbindir) \ $(DESTDIR)$(sysconfdir)/linuxcnc $(FILE) ../rtlib/*$(MODULE_EXT) $(DESTDIR)$(EMC2_RTLIB_DIR) $(SETUID) ../bin/linuxcnc_module_helper $(DESTDIR)$(bindir) From 2007291359ef2a80735dab3500d64f3081757cc4 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 9 Nov 2026 21:59:04 +0100 Subject: [PATCH 5/7] rtapi_app: Show selected CPU and reorder code Now only calls find_rt_cpu_number() once in constructor and not anymore for each created task. --- src/rtapi/uspace_posix.cc | 40 ++++++++++++++++++--------------- src/rtapi/uspace_rtai.cc | 9 +++++--- src/rtapi/uspace_rtapi_app.cc | 4 +--- src/rtapi/uspace_xenomai.cc | 28 ++++++++++++----------- src/rtapi/uspace_xenomai_evl.cc | 28 ++++++++++++----------- 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/rtapi/uspace_posix.cc b/src/rtapi/uspace_posix.cc index e7d0b88bb30..c5120ffdcf5 100644 --- a/src/rtapi/uspace_posix.cc +++ b/src/rtapi/uspace_posix.cc @@ -36,6 +36,19 @@ struct PosixTask : RtapiTask { struct PosixApp : RtapiApp { PosixApp(int policy = SCHED_FIFO) : RtapiApp(policy), do_thread_lock(policy != SCHED_FIFO) { + + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + rt_cpu_number = -1; + if (nprocs > 1) { + rt_cpu_number = find_rt_cpu_number(); + } + + if (policy == SCHED_OTHER) { + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime on CPU %i\n", rt_cpu_number); + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX realtime on CPU %i\n", rt_cpu_number); + } + pthread_once(&key_once, init_key); if (do_thread_lock) { pthread_once(&lock_once, init_lock); @@ -73,8 +86,6 @@ struct PosixApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - pthread_attr_t attr; int ret; if ((ret = pthread_attr_init(&attr)) != 0) @@ -87,20 +98,16 @@ struct PosixApp : RtapiApp { return -ret; if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if (nprocs > 1) { - const static int rt_cpu_number = find_rt_cpu_number(); - rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if (rt_cpu_number != -1) { + if (rt_cpu_number >= 0) { #ifdef __FreeBSD__ - cpuset_t cpuset; + cpuset_t cpuset; #else - cpu_set_t cpuset; + cpu_set_t cpuset; #endif - CPU_ZERO(&cpuset); - CPU_SET(rt_cpu_number, &cpuset); - if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) - return -ret; - } + CPU_ZERO(&cpuset); + CPU_SET(rt_cpu_number, &cpuset); + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + return -ret; } if (do_thread_lock) pthread_mutex_lock(&thread_lock); @@ -211,6 +218,8 @@ struct PosixApp : RtapiApp { clock_gettime(CLOCK_MONOTONIC, &task->nextstart); } + int rt_cpu_number; + bool do_thread_lock; static pthread_once_t key_once; @@ -247,10 +256,5 @@ pthread_mutex_t PosixApp::thread_lock; extern "C" RtapiApp *make(int policy); RtapiApp *make(int policy) { - if (policy == SCHED_OTHER) { - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime\n"); - } else { - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX realtime\n"); - } return new PosixApp(policy); } diff --git a/src/rtapi/uspace_rtai.cc b/src/rtapi/uspace_rtai.cc index c6f90746848..e67d99dcf5b 100644 --- a/src/rtapi/uspace_rtai.cc +++ b/src/rtapi/uspace_rtai.cc @@ -44,6 +44,8 @@ template T *get_task(int task_id) { struct RtaiApp : RtapiApp { RtaiApp() : RtapiApp(SCHED_FIFO) { + rt_cpu_number = find_rt_cpu_number(); + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using LXRT realtime on CPU %i\n", rt_cpu_number); pthread_once(&key_once, init_key); } @@ -102,9 +104,7 @@ struct RtaiApp : RtapiApp { int nprocs = sysconf(_SC_NPROCESSORS_ONLN); int cpus_allowed = 1 << (nprocs - 1); //Use last CPU as default - const static int rt_cpu_number = find_rt_cpu_number(); if (rt_cpu_number != -1) { - rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); cpus_allowed = 1 << rt_cpu_number; } task->rt_task = rt_task_init_schmod(task->id, task->prio, 0, 0, SCHED_FIFO, cpus_allowed); @@ -192,6 +192,8 @@ struct RtaiApp : RtapiApp { } } + static int rt_cpu_number; + static pthread_once_t key_once; static pthread_key_t key; static void init_key(void) { @@ -215,6 +217,8 @@ struct RtaiApp : RtapiApp { } }; +int RtaiApp::rt_cpu_number=-1; + pthread_once_t RtaiApp::key_once; pthread_key_t RtaiApp::key; } // namespace @@ -225,6 +229,5 @@ RtapiApp *make(int policy) { if (policy != SCHED_FIFO) { throw std::invalid_argument("Only SCHED_FIFO allowed"); } - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using LXRT realtime\n"); return app = new RtaiApp(); } diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 8bfbedec1d2..7f4477d3453 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -274,9 +274,7 @@ int RtapiApp::find_rt_cpu_number() { } } rtapi_print_msg( - RTAPI_MSG_ERR, "No isolated CPU's found, expect some latency or set RTAPI_CPU_NUMBER to select CPU.\n" - " CPU %i selected as fallback.\n", top - ); + RTAPI_MSG_ERR, "Warning: No isolated CPU's found, expect some latency or set RTAPI_CPU_NUMBER to select CPU.\n"); } return top; #else diff --git a/src/rtapi/uspace_xenomai.cc b/src/rtapi/uspace_xenomai.cc index bd6813ea9bb..a53c8513526 100644 --- a/src/rtapi/uspace_xenomai.cc +++ b/src/rtapi/uspace_xenomai.cc @@ -37,6 +37,13 @@ struct XenomaiTask : RtapiTask { struct XenomaiApp : RtapiApp { XenomaiApp() : RtapiApp(SCHED_FIFO) { + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + rt_cpu_number = -1; + if (nprocs > 1) { + rt_cpu_number = find_rt_cpu_number(); + } + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI (posix-skin) realtime on CPU %i\n", rt_cpu_number); + pthread_once(&key_once, init_key); } @@ -71,8 +78,6 @@ struct XenomaiApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - pthread_attr_t attr; int ret; if ((ret = pthread_attr_init(&attr)) != 0) @@ -85,16 +90,12 @@ struct XenomaiApp : RtapiApp { return -ret; if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if (nprocs > 1) { - const static int rt_cpu_number = find_rt_cpu_number(); - rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if (rt_cpu_number != -1) { - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - CPU_SET(rt_cpu_number, &cpuset); - if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) - return -ret; - } + if (rt_cpu_number >= 0) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(rt_cpu_number, &cpuset); + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + return -ret; } if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; @@ -205,6 +206,8 @@ struct XenomaiApp : RtapiApp { clock_gettime(CLOCK_MONOTONIC, &task->nextstart); } + int rt_cpu_number; + static pthread_once_t key_once; static pthread_key_t key; static void init_key(void) { @@ -233,6 +236,5 @@ RtapiApp *make(int policy) { if (policy != SCHED_FIFO) { throw std::invalid_argument("Only SCHED_FIFO allowed"); } - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI (posix-skin) realtime\n"); return new XenomaiApp(); } diff --git a/src/rtapi/uspace_xenomai_evl.cc b/src/rtapi/uspace_xenomai_evl.cc index 520f33f4fec..28b3e96c223 100644 --- a/src/rtapi/uspace_xenomai_evl.cc +++ b/src/rtapi/uspace_xenomai_evl.cc @@ -48,6 +48,13 @@ struct EvlTask : RtapiTask { struct EvlApp : RtapiApp { EvlApp() : RtapiApp(SCHED_FIFO) { + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + rt_cpu_number = -1; + if (nprocs > 1) { + rt_cpu_number = find_rt_cpu_number(); + } + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI4 EVL realtime on CPU %i\n", rt_cpu_number); + pthread_once(&key_once, init_key); WithRoot r; /* Attach to the EVL core for rtapi_app, so modules can use EVL core @@ -92,8 +99,6 @@ struct EvlApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - pthread_attr_t attr; int ret; if ((ret = pthread_attr_init(&attr)) != 0) @@ -106,16 +111,12 @@ struct EvlApp : RtapiApp { return -ret; if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if (nprocs > 1) { - const static int rt_cpu_number = find_rt_cpu_number(); - rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if (rt_cpu_number != -1) { - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - CPU_SET(rt_cpu_number, &cpuset); - if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) - return -ret; - } + if (rt_cpu_number >= 0) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(rt_cpu_number, &cpuset); + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + return -ret; } if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; @@ -236,6 +237,8 @@ struct EvlApp : RtapiApp { evl_read_clock(EVL_CLOCK_MONOTONIC, &task->nextstart); } + int rt_cpu_number; + static pthread_once_t key_once; static pthread_key_t key; static void init_key(void) { @@ -266,6 +269,5 @@ RtapiApp *make(int policy) { if (policy != SCHED_FIFO) { throw std::invalid_argument("Only SCHED_FIFO allowed"); } - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI4 EVL realtime\n"); return new EvlApp(); } From 86cbc1cc63b988b6dcf0cf7385b662e0e8001ec8 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 9 Nov 2026 22:07:20 +0100 Subject: [PATCH 6/7] rtapi_app: Reduce duplicated code Each app except uspace_rtai checked for _SC_NPROCESSORS_ONLN. Now this is done inside find_rt_cpu_number(). This slightly changes the behaivour for uspace_rtai. --- src/rtapi/uspace_posix.cc | 7 +------ src/rtapi/uspace_rtapi_app.cc | 8 ++++++++ src/rtapi/uspace_xenomai.cc | 6 +----- src/rtapi/uspace_xenomai_evl.cc | 6 +----- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/rtapi/uspace_posix.cc b/src/rtapi/uspace_posix.cc index c5120ffdcf5..5e1940be187 100644 --- a/src/rtapi/uspace_posix.cc +++ b/src/rtapi/uspace_posix.cc @@ -37,12 +37,7 @@ struct PosixTask : RtapiTask { struct PosixApp : RtapiApp { PosixApp(int policy = SCHED_FIFO) : RtapiApp(policy), do_thread_lock(policy != SCHED_FIFO) { - int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - rt_cpu_number = -1; - if (nprocs > 1) { - rt_cpu_number = find_rt_cpu_number(); - } - + rt_cpu_number = find_rt_cpu_number(); if (policy == SCHED_OTHER) { rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime on CPU %i\n", rt_cpu_number); } else { diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 7f4477d3453..cbaedda4c95 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -237,6 +237,14 @@ int RtapiApp::find_rt_cpu_number() { if (getenv("RTAPI_CPU_NUMBER")) return atoi(getenv("RTAPI_CPU_NUMBER")); + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + if (nprocs < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "sysconf(_SC_NPROCESSORS_ONLN) failed %m\n"); + } else if (nprocs == 1) { + //On single CPU systems, no CPU assignment needed + return -1; + } + #ifdef __linux__ const char *isolated_file = "/sys/devices/system/cpu/isolated"; cpu_set_t cpuset; diff --git a/src/rtapi/uspace_xenomai.cc b/src/rtapi/uspace_xenomai.cc index a53c8513526..bb878665853 100644 --- a/src/rtapi/uspace_xenomai.cc +++ b/src/rtapi/uspace_xenomai.cc @@ -37,11 +37,7 @@ struct XenomaiTask : RtapiTask { struct XenomaiApp : RtapiApp { XenomaiApp() : RtapiApp(SCHED_FIFO) { - int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - rt_cpu_number = -1; - if (nprocs > 1) { - rt_cpu_number = find_rt_cpu_number(); - } + rt_cpu_number = find_rt_cpu_number(); rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI (posix-skin) realtime on CPU %i\n", rt_cpu_number); pthread_once(&key_once, init_key); diff --git a/src/rtapi/uspace_xenomai_evl.cc b/src/rtapi/uspace_xenomai_evl.cc index 28b3e96c223..5807f616f3a 100644 --- a/src/rtapi/uspace_xenomai_evl.cc +++ b/src/rtapi/uspace_xenomai_evl.cc @@ -48,11 +48,7 @@ struct EvlTask : RtapiTask { struct EvlApp : RtapiApp { EvlApp() : RtapiApp(SCHED_FIFO) { - int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - rt_cpu_number = -1; - if (nprocs > 1) { - rt_cpu_number = find_rt_cpu_number(); - } + rt_cpu_number = find_rt_cpu_number(); rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI4 EVL realtime on CPU %i\n", rt_cpu_number); pthread_once(&key_once, init_key); From b152c330ae557e5f5ba278a8fa93fdd26c853a65 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Thu, 5 Nov 2026 23:00:29 +0100 Subject: [PATCH 7/7] Improve doc concerning latency testing and tuning --- docs/po4a.cfg | 1 + docs/src/Master_Documentation.adoc | 2 + docs/src/Submakefile | 1 + docs/src/config/images/latency-ping.png | Bin 0 -> 21157 bytes .../getting-started/system-requirements.adoc | 39 ++- docs/src/index.tmpl | 1 + docs/src/install/latency-test.adoc | 174 +++++------ docs/src/install/latency-tuning.adoc | 290 ++++++++++++++++++ docs/src/man/man9/hm2_eth.9.adoc | 16 +- 9 files changed, 403 insertions(+), 121 deletions(-) create mode 100644 docs/src/config/images/latency-ping.png create mode 100644 docs/src/install/latency-tuning.adoc diff --git a/docs/po4a.cfg b/docs/po4a.cfg index 5ca4619aa21..baa0f44e579 100644 --- a/docs/po4a.cfg +++ b/docs/po4a.cfg @@ -137,6 +137,7 @@ [type: AsciiDoc_def] src/hal/tutorial.adoc $lang:build/adoc/$lang/hal/tutorial.adoc [type: AsciiDoc_def] src/hal/twopass.adoc $lang:build/adoc/$lang/hal/twopass.adoc [type: AsciiDoc_def] src/install/latency-test.adoc $lang:build/adoc/$lang/install/latency-test.adoc +[type: AsciiDoc_def] src/install/latency-tuning.adoc $lang:build/adoc/$lang/install/latency-tuning.adoc [type: AsciiDoc_def] src/integrator/steppers.adoc $lang:build/adoc/$lang/integrator/steppers.adoc [type: AsciiDoc_def] src/integrator/stepper-timing.adoc $lang:build/adoc/$lang/integrator/stepper-timing.adoc [type: AsciiDoc_def] src/integrator/wiring.adoc $lang:build/adoc/$lang/integrator/wiring.adoc diff --git a/docs/src/Master_Documentation.adoc b/docs/src/Master_Documentation.adoc index b1c373e7d59..3355403ac73 100644 --- a/docs/src/Master_Documentation.adoc +++ b/docs/src/Master_Documentation.adoc @@ -64,6 +64,8 @@ include::config/integrator-concepts.adoc[] include::install/latency-test.adoc[] +include::install/latency-tuning.adoc[] + include::motion/tweaking-steppers.adoc[] include::config/ini-config.adoc[] diff --git a/docs/src/Submakefile b/docs/src/Submakefile index 5bd1092d01f..6252deb2d96 100644 --- a/docs/src/Submakefile +++ b/docs/src/Submakefile @@ -238,6 +238,7 @@ DOC_SRCS_EN := \ hal/tutorial.adoc \ hal/twopass.adoc \ install/latency-test.adoc \ + install/latency-tuning.adoc \ integrator/steppers.adoc \ integrator/stepper-timing.adoc \ integrator/wiring.adoc \ diff --git a/docs/src/config/images/latency-ping.png b/docs/src/config/images/latency-ping.png new file mode 100644 index 0000000000000000000000000000000000000000..459074b76fb38d8665dd0d92760c16c4e6c8cb2b GIT binary patch literal 21157 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. -=== Xenomai with 'linuxcnc-uspace' package - -Xenomai is also supported, but you will have to find or build the kernel -and compile LinuxCNC from source to utilise it. +Prebuilt experimental kernels can be found here: https://github.com/NTULINUX/RTAI === RTAI with 'linuxcnc-uspace' package -It is also possible to run LinuxCNC with RTAI in user-space mode. +It is possible to run LinuxCNC with RTAI in user-space mode. As with Xenomai you will need to compile from source to do this. == Problematic Hardware diff --git a/docs/src/index.tmpl b/docs/src/index.tmpl index 2b0d800ae21..67c668e9723 100644 --- a/docs/src/index.tmpl +++ b/docs/src/index.tmpl @@ -67,6 +67,7 @@
  • Integrator Concepts
  • Latency Test
  • +
  • Latency Tuning
  • Stepper Tuning
  • INI Configuration
  • Homing Configuration
  • diff --git a/docs/src/install/latency-test.adoc b/docs/src/install/latency-test.adoc index 1097b21cf4a..105d1926533 100644 --- a/docs/src/install/latency-test.adoc +++ b/docs/src/install/latency-test.adoc @@ -33,6 +33,48 @@ However, software step pulses also have some disadvantages: - jitter in the generated pulses - loads the CPU +[[sec:latency-to-achive]] +== What to Achieve? +This depends on your application. If step pulses are directly generated by the PC, minimal +latency is key to achieve a hight step rate. See <> +for requirements. + +As soon as the step pulses are generated using an external hardware like Mesa cards, the latency +constraints are looser. + +If the servo-thread has period of 1 ms, latency of 100-200 us can still be used. + +However, in this case, the total thread runtime is more important. The time from where +the thread should have started to the next cycle should never exceed the thread's period. +If this happens, the following warning is shown: +---- +Unexpected realtime delay on task 0 with period 1000000 +This Message will only display once per session. +Run the Latency Test and resolve before continuing. +---- + +Especially for ethernet connected cards, the ethernet latency is often the limiting factor, +not the pure thread start latency. When using ethernet, LinuxCNC sends a request to the card +and waits for the response. This has to happen in one thread period. + +Often, bad thread latency correlates with bad ethernet latency, due to the LinuxCNC and ethernet +kernel latency adds up. + +[[sec:latency-load]] +== Perform Latency Tests +Having a system that has low latency when mostly idle but behaves badly as soon +as some load is added will result in an unreliable system. + +While the test is running, you should 'abuse' the computer. +Move windows around on the screen. Surf the web. Copy some large files +around on the disk. Play some music. +Run an OpenGL program such as glxgears. +Run a load and stress tool such as stress-ng. +The idea is to put the PC through its paces while +the latency test checks to see what the worst case numbers are. + +For quick comparisons between different settings, a test of a few minutes are fine. +However, for final testing, a several hours long test should be performed. [[sec:latency-tests]] == Latency Tests @@ -87,13 +129,6 @@ After starting a latency test you should see something like this: .HAL Latency Test image::../config/images/latency-test_en.png["HAL Latency Test",align="center"] -While the test is running, you should 'abuse' the computer. -Move windows around on the screen. Surf the web. Copy some large files -around on the disk. Play some music. -Run an OpenGL program such as glxgears. -The idea is to put the PC through its paces while -the latency test checks to see what the worst case numbers are. - The important number for software stepping is the 'max jitter' of the base thread. In the example above, that is 6693 nanoseconds (ns), or 6.693 microseconds (µs). Record this number, and enter it in StepConf when it is requested. @@ -103,7 +138,7 @@ You should run the test for at least several minutes; sometimes the worst case latency doesn't happen very often, or only happens when you do some particular action. For instance, one Intel motherboard worked pretty well most of the time, but every 64 -seconds it had a very bad 300 µs latency. Fortunately that was fixable, see <<_latency_tuning>>. +seconds it had a very bad 300 µs latency. Fortunately that was fixable, see <>. So, what do the results mean? @@ -156,7 +191,7 @@ Options: .`latency-plot` Window image::../config/images/latency-plot.png["latency-plot Window"] - +[[sec:latency-histogram]] === Latency Histogram The application latency-histogram displays a histogram of latency (jitter) for @@ -192,6 +227,36 @@ Use `--show` to show count for the off-chart [pos|neg] bin. .`latency-histogram` Window image::../config/images/latency-histogram.png["latency-histogram Window"] +[[sec:latency-ping]] +=== Latency Ping + +The application latency-ping displays a histogram of the round trip time for +a realtime thread pinging an ip address. This useful for network latency tuning where +ip_address should be a Mesa card, an EtherCAT device or any other device which +has a realtime capable ethernet interface to be used together with LinuxCNC. + +Due to ping is used, it should work with virtually any device without any setup. + +It is recommended to use the `if_name` argument to allow for static arp, so arp +renewal is disabled and does not influence the response time. + +---- +Usage: + latency-ping [-t timeout] [-p period] [-n nbins ] [-b binsize ] ip_address [if_name] + +Example: + latency-ping 192.168.1.121 enp1s0 + +Defaults: timeout=800000nS period=1000000nS nbins=200 binsize=period/2/nbins +Equivalently: timeout=800µs period=1ms nbins=200 binsize=2500 + +Times may be specified with suffix "s", "ms", "us" "µs", or "ns" +Times without a suffix and less than 1000 are taken to be in us; +other times without a suffix are taken to be in ns +---- + +.`latency-ping` Window +image::../config/images/latency-ping.png["latency-ping Window"] [[sec:latency-status-bar]] === Realtime status bar @@ -227,95 +292,4 @@ isolcpus:: for latency>>. -[[_latency_tuning]] -== Latency tuning - -LinuxCNC can run on many different hardware platforms and with many -different realtime kernels, and they all may benefit from tuning for -optimal latency. - -A primary goal in tuning the system for LinuxCNC is to reserve a CPU for the exclusive use of LinuxCNC's realtime tasks, so that other tasks (both user programs and kernel threads) do not interfere with LinuxCNC's access to that CPU. - -When specific tuning options are believed to be universally helpful -LinuxCNC does this tuning automatically at startup, but many tuning -options are machine-specific and cannot be done automatically. The person -installing LinuxCNC will need to experimentally determine the optimal -tuning for their system. - - -=== Tuning the BIOS for latency - -PC BIOSes vary wildly in their latency behavior. - -Tuning the BIOS is tedious because you have to reboot the computer, -make one small tweak in the BIOS, boot Linux, and run the latency test -(potentially for a long time) to see what effects your BIOS change had. -Then repeat for all the other BIOS settings you want to try. - -Because BIOSes are all different and non-standard, providing a detailed -BIOS tuning guide is not practical. In general, some things to try -tuning in the BIOS are: - -* Disable ACPI, APM, and any other power-saving features. This includes - anything related to power saving, suspending, CPU sleep states, CPU - frequency scaling, etc. - -* Disable CPU "turbo" mode. - -* Disable CPU hyperthreading. - -* Disable (or otherwise control) System Management Interrupt (SMI). - -* Disable any hardware you do not intend to use. - - -[[sec:latency-governor]] -=== CPU frequency governor - -Frequency scaling lets the CPU lower its clock speed to save power, which adds -latency while the CPU ramps back up to service a realtime thread. For the best -latency, set the governor to `performance` on every CPU: - ----- -echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor ----- - -This setting does not survive a reboot. To make it permanent, apply it at boot -with a small systemd service, or with a CPU frequency management tool such as -`cpupower` (Debian/Ubuntu package `linux-cpupower`) or `tuned`. - - -[[sec:tuning-preempt-rt]] -=== Tuning Preempt-RT for latency - -The Preempt-RT kernel may benefit from tuning in order to provide the -best latency for LinuxCNC. Tuning may be done via the kernel command -line, sysctl, and via files in `/proc` and `/sys`. - -Some tuning parameters to look into: - -Kernel command line:: - - Details here: - - * `isolcpus`: Prevent most non-LinuxCNC processes from using these - CPUs, leaving more CPU time available for LinuxCNC. - - * `irqaffinity`: Select which CPUs service interrupts, so that the - CPUs reserved for LinuxCNC realtime don't have to perform this task. - - * `rcu_nocbs`: Prevent RCU callbacks from running on these CPUs. - - * `rcu_nocb_poll`: Poll for RCU callbacks instead of using sleep/wake. - - * `nohz_full`: Disable clock tick on these CPUs. - -Sysctl:: - - Details here: - - * `sysctl.kernel.sched_rt_runtime_us`: Set to -1 to remove the limit - on how much time realtime tasks may use. - - // vim: set syntax=asciidoc: diff --git a/docs/src/install/latency-tuning.adoc b/docs/src/install/latency-tuning.adoc new file mode 100644 index 00000000000..bb96ef9be81 --- /dev/null +++ b/docs/src/install/latency-tuning.adoc @@ -0,0 +1,290 @@ +:lang: en +:toc: + +[[cha:latency_tuning]] += Latency Tuning + +LinuxCNC can run on many different hardware platforms and with many +different realtime kernels, and they all may benefit from tuning for +optimal latency. + +A primary goal in tuning the system for LinuxCNC is to reserve a CPU for +the exclusive use of LinuxCNC's realtime tasks, so that other tasks +(both user programs and kernel threads) do not interfere with LinuxCNC's +access to that CPU. + +When specific tuning options are believed to be universally helpful +LinuxCNC does this tuning automatically at startup, but many tuning +options are machine-specific and cannot be done automatically. The person +installing LinuxCNC will need to experimentally determine the optimal +tuning for their system. + +== Basic process + +=== For x86 PREEMPT_RT or Xenomai4 EVL + +==== Basic configuration + +1. Install realtime kernel +2. Run `realtime-config-kernel` +3. Reboot +4. Check latency: <> +5. If good, continue. If bad, try BIOS tuning and manually adjust kernel parameters. + +==== Network configuration + +This is only needed if you use an Ethernet IO card, EtherCAT or anything connected over Ethernet which the servo-thread communicates with. + +To determine the CPU LinuxCNC runs on, start any LinuxCNC app that uses realtime (`latency-histogram` for example) in the console and it will show it in the log output. + +1. Configure network card by editing `/etc/network/interfaces`. Example: + +* Replace `eth0` with the interface used for LinuxCNC + +* Replace `192.168.1.120/31` with the address/netmask + +* Replace `ISOLATED_CPU` with the CPU LinuxCNC runs on ++ +---- +auto eth0 + iface eth0 inet static + address 192.168.1.120/31 + post-up realtime-config-ethernet eth0 ISOLATED_CPU +---- + +2. If you use NetworkManager: `sudo systemctl restart NetworkManager` to reload the config. Otherwise, NetworkManager will interfere with the static config. +3. Activate interface: `sudo ifup eth0` +4. Test network latency: <> +5. If good, continue. If bad, try <> or use a different network card. + +==== Final test + +Add a timedelta component to your configuration to check the thread +jitter while LinuxCNC is running. It should be added as the first function. + +For servo-thread only: + +---- +loadrt timedelta count=1 +... +addf timedelta.0 servo-thread +---- + +For base- and servo-thread only: + +---- +loadrt timedelta count=2 +... +addf timedelta.0 servo-thread +... +addf timedelta.1 base-thread +---- + +While LinuxCNC is running, use `hal-histogram` to measure the available timing information. Example: + +`hal-histogram --minvalue 0 --binsize 0.5e-5 --nbins 200 servo-thread.time` + +Pins to watch are: + +* `servo-thread.time`: Time of servo thread. Must be < servo period. +* `timedelta.0.current-jitter`: Jitter of servo thread +* `hm2_7i96s.0.read.time`: If you are using a mesa card, time it takes to read from the card. Must be < 0.8 * servo period. +* `base-thread.time`: Time of base thread. Must be < base period. +* `timedelta.1.current-jitter`: Jitter of base thread + +=== For other hardware or kernels + +* Start with the always needed kernel and sysctl parameters and configuration +** If you are using grub, they are added to `/etc/default/grub` ++ +Example for CPU3 isolated: `GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=managed_irq,domain,3 irqaffinity=0-2"` +** sysctl parameters are set in `/etc/sysctl.d/50-linuxcnc-rt.conf` +** `sudo update-grub` updates the grub configuration, reboot +* Test latency +* Try and error until it works. Ideally, only one parameter at a time is changed. Some settings can make latency worse and if you set multiple at once, it might well be that you miss the ideal parameter due to an other one makes everything worse. + +[[sec:latency-scripts]] +== Automated scripts + +These scripts should allow to achieve good realtime performance by setting general parameters. However, due to sheer amount of existing hardware, manual adjustment might be needed. + +realtime-config-kernel:: +This scripts creates a kernel command line that should achieve good latency for most x86 PC's. It is interactive and asks for settings where multiple choices are possible. The following settings are performed: + +* Full core isolation compatible with SMT enabled (if desired) +* Any frequency scaling is disabled +* The idle states are limited to 1 +* Sets `evl.oobcpus` to the isolated CPU if a Xenomai4 EVL kernel is running + +realtime-config-ethernet:: +Usage: realtime-config-ethernet nic cpu ++ +This script configures a network card for good latency. It needs to be run after the network card is up. The following settings are performed: ++ +* Any coalesce parameters are set to zero +* All channels are set to one +* Energy efficient ethernet is disabled +* All network card threads and interrupts are assigned to the given CPU + +[[sec:bios-latency]] +== Tuning the BIOS for latency + +PC BIOSes vary wildly in their latency behavior. + +Some settings can be modified in BIOS and also using the kernel command +line. For example "turbo" mode and Hyper-Threading can also be disabled using +`cpufreq.off=1` and `nosmt`. + +Tuning the BIOS is tedious because you have to reboot the computer, +make one small tweak in the BIOS, boot Linux, and run the latency test +(potentially for a long time) to see what effects your BIOS change had. +Then repeat for all the other BIOS settings you want to try. + +Because BIOSes are all different and non-standard, providing a detailed +BIOS tuning guide is not practical. In general, some things to try +tuning in the BIOS are: + +* Disable ACPI, APM, and any other power-saving features. This includes + anything related to power saving, suspending, CPU sleep states, CPU + frequency scaling, etc. + +* Disable CPU "turbo" mode. + +* Disable CPU Hyper-Threading. + +* Disable (or otherwise control) System Management Interrupt (SMI). + +* Disable any hardware you do not intend to use. + + +[[sec:latency-governor]] +== CPU frequency governor + +Frequency scaling lets the CPU lower its clock speed to save power, which adds +latency while the CPU ramps back up to service a realtime thread. For the best +latency, set the governor to `performance` on every CPU: + +---- +echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor +---- + +This setting does not survive a reboot. To make it permanent: + +* Kernel command line: `cpufreq.default_governor=performance` + +* Apply it at boot with a small systemd service + +* Use CPU frequency management tool such as `cpupower` (Debian/Ubuntu package `linux-cpupower`) or `tuned`. + +[[sec:hyper-threading]] +== SMT or Hyper-Threading + +Hyper-Threading has a big influence on latency if not handled correctly. + +If the Hyper-Threading is enabled and the isolated core has a sibling that is not +isolated, latency will be similar to no core isolation. + +This is handled by the `realtime-config-kernel` script. Ideally, SMT is simply +disabled. If you prefer to leave SMT enabled due to performance reason, the SMT +sibling is isolated also. + +[[sec:tuning-preempt-rt]] +== Tuning Preempt-RT for latency + +The Preempt-RT kernel may benefit from tuning in order to provide the +best latency for LinuxCNC. Tuning may be done via the kernel command +line, sysctl, and via files in `/proc` and `/sys`. + +Some tuning parameters to look into: + +Kernel command line:: +Details here: ++ +Set in `/etc/default/grub` by adding to `GRUB_CMDLINE_LINUX_DEFAULT` and running `sudo update-grub` ++ +* `isolcpus`: Prevent most non-LinuxCNC processes from using these + CPUs, leaving more CPU time available for LinuxCNC. +* `irqaffinity`: Select which CPUs service interrupts, so that the + CPUs reserved for LinuxCNC realtime don't have to perform this task. +* `rcu_nocbs`: Prevent RCU callbacks from running on these CPUs. +* `rcu_nocb_poll`: Poll for RCU callbacks instead of using sleep/wake. +* `nohz=on`: Enables nohz +* `nohz_full`: Disable clock tick on these CPUs. +* `intel_idle.max_cstate=1`: Sets the maximum C-state to 1 for intel CPUs +* `processor.max_cstate=1`: Sets the maximum C-state to 1 for other CPUs using ACPI +* `intel_pstate=disable`: Disables intel power states (frequency scaling) +* `cpufreq.off=1`: Disables any frequency scaling +* `cpufreq.default_governor=performance`: Sets the frequency scaling governor to performance +* `nosoftlockup`: Disable the soft-lockup detector. +* `nowatchdog`: Disable both lockup detectors, soft-lockup and NMI watchdog +* `nosmt`: Disable symmetric multithreading (SMT) +* `mitigations=off`: Disable any CPU vulnerabilities mitigation WARNING: This makes your PC vulnerable to cyberattacks! +* `pcie_aspm=off`: Disable PCIe power saving +* `skew_tick=1`: Offset the periodic timer tick +* `mce=off`: Disable machine check interrupt +* `idle=poll`: Instead of using C-states when the CPU is idle, it does a busy wait. This will make the CPUs eat a lot more power. + +Sysctl:: ++ +Details here: ++ +Set using `sudo sysctl -w` or as a persistent setting in `/etc/sysctl.d/50-linuxcnc-rt.conf` ++ +* `sysctl.kernel.sched_rt_runtime_us=-1`: Set to -1 to remove the limit + on how much time realtime tasks may use. +* `kernel.timer_migration=0`: Prevents timers from being migrated between cores, which creates jitter + +[[sec:network-tuning]] +== Network tuning + +Most network card's default settings are optimized for throughput, not latency. Below how some parameters to investigate. Read the `ethtool` manpage for details. + +* Any coalesce parameters should be set to zero. `sudo ethtool -c eth0` + +* The channels should be set to one. Multichannel is good for throughput but increases latency. `sudo ethtool -l eth0` + +* Energy efficient ethernet should be disabled. `ethtool --show-eee eth0` + +* Hardware offloading can be an issue: `sudo ethtool -k eth0` + +* Package drops and CRC errors due to bad cabling or hardware issues can be investigated with: `sudo ethtool -S eth0` + +* For driver issues, the following command helps determining which driver is used: `sudo ethtool -i eth0` + +If the network card interrupt and threads are assigned to the same CPU that the servo-thread runs on, any additional latency can be reduced. This will not slow down the servo-thread: It waits for the data anyway. + +Use `cat /proc/interrupts` to show the interrupts. Use `echo 8 | sudo tee /proc/irq/16/smp_affinity` to set the affinity of an interrupt to a specific CPU. In this example, the mask is 8 meaning the interrupt 16 is assigned to CPU 3. Use `cat /proc/interrupts` while pinging the remote device to see if the interrupt count is increasing on the CPU configured. + +PREEMPT_RT uses a kernel task for some network card drivers. Use `ps -Af | grep irq` to show all kernel interrupt tasks. If you see an interrupt task for your network card, use `taskset` and `chrt` to assign it to the isolated core and set to realtime priority. + +All the above settings are volatile and interrupt numbers can change from boot to boot. A script is needed set them in a persistent way. + +== Known good and bad hardware + +* Dedicated NVidia GPU's are only usable with the Nouveau driver. The nvidia-driver Debian package will not build for PREEMPT_RT. Latency spikes of 100-200us are common. +* Integrated GPU's use the PC's main memory and therefore block the CPU from memory access. Wider latency distributions of 50-100us are common. +* Dedicated AMD GPU's work quite well. +* Intel Network Cards that use the e1000e driver have a high network latency of about 200us +* Intel Network Cards that use the igb driver work well with a latency of <100us +* Realtek Network Cards that use the r8169 driver can be cumbersome. Installing r8168-dkms or r8125-dkms can improve it. + +== Identifying issues + +By watching `latency-histogram` while performing certain tasks, hardware issues can be identified. + +* Start `glx-gears`: If the distribution gets wider, it is a GPU issue. +* Run `stress-ng --cpu $(nproc)` If the distribution gets wider, it is a CPU issue. +* Run `stress-ng --cpu $(nproc)` before starting `latency-histogram` and stop `stress-ng` while it is running. If the distribution gets wider without load, it might be a frequency scaling or sleep state issue. If the CPU clocks down or activates sleep states, latency gets worse. +* Play some music. It might be a sound card issue. + +If you expect a GPU issue which can even be triggered just running `latency-histogram` on the PC, try running it over SSH from another PC. + +There are also command line tools to measure latency independently: + +PREEMT_RT: `sudo cyclictest --mlockall -a ISOLATED_CPU --priority=97 -N --interval=1000` + +PREEMT_RT network testing: `sudo taskset 8 chrt 90 ping -i .001 -q 192.168.1.121` + +Xenomai4 EVL: `sudo latmus -c ISOLATED_CPU` + +// vim: set syntax=asciidoc: diff --git a/docs/src/man/man9/hm2_eth.9.adoc b/docs/src/man/man9/hm2_eth.9.adoc index 603da4e6812..f82bce859c6 100644 --- a/docs/src/man/man9/hm2_eth.9.adoc +++ b/docs/src/man/man9/hm2_eth.9.adoc @@ -16,7 +16,7 @@ IO boards, with HostMot2 firmware. As shipped, the board address is 192.168.1.121. *board_rtnet* [default: posix]:: The real time ethernet implementation of the board(s), separated by - commas. Use *posix* for PREEMT_RT. Use *evl* if you are running a + commas. Use *posix* for PREEMPT_RT. Use *evl* if you are running a Xenomai4 EVL kernel and have a supported network card. *firewall* [default: auto]:: Selects the firewall backend used to isolate the dedicated interface. @@ -58,6 +58,9 @@ hm2_eth should be used on a dedicated network interface, with only a cable between the PC and the board. Wireless and USB network interfaces are not suitable. +Good network latency of the PC is important. Read the chapters +*Latency Testing* and *Latency Tuning* in the LinuxCNC manual. + These instructions assume your dedicated network interface is "eth1", 192.168.1/24 is an unused private network, that the hostmot2 board is using the default address of 192.168.1.121, that you are using Debian 7 @@ -81,13 +84,14 @@ mitigate most accidental causes of interference. === Configure network with static address Add these lines to the file /etc/network/interfaces to configure the -ethernet interface eth1 with a static address: +ethernet interface eth1 with a static address. Replace ISOLATED_CPU +with the isolated CPU LinuxCNC runs on: ---- auto eth1 iface eth1 inet static address 192.168.1.1 - hardware-irq-coalesce-rx-usecs 0 + post-up realtime-config-ethernet eth0 ISOLATED_CPU ---- == PACKET LOSS @@ -262,12 +266,6 @@ Tear the rules down by deleting the whole private table: nft delete table inet hm2_eth ---- -"hardware-irq-coalesce-rx-usecs" decreases time waiting to receive a packet on most systems, -but on at least some Marvel-chipset NICs it is harmful. -If the line does not improve system performance, then remove it. -A reboot is required for the value to be set back to its power-on default. -This requires the ethtool package to be installed. - == BUGS Some hostmot2 functions such uart are coded in a way that causes