Petr Oros 656cfc3ce0 dpll: Add dpll command
Add a new userspace tool for managing and monitoring DPLL devices via the
Linux kernel DPLL subsystem. The tool uses libmnl for netlink communication
and provides a complete interface for device and pin configuration.

The tool supports:

- Device management: enumerate devices, query capabilities (lock status,
  temperature, supported modes, clock quality levels), configure phase-offset
  monitoring and averaging

- Pin management: enumerate pins with hierarchical relationships, configure
  frequencies (including esync), phase adjustments, priorities, states, and
  directions

- Complex topologies: handle parent-device and parent-pin relationships,
  reference synchronization tracking, multi-attribute queries (frequency
  ranges, capabilities)

- ID resolution: query device/pin IDs by various attributes (module-name,
  clock-id, board-label, type)

- Monitoring: real-time display of device and pin state changes via netlink
  multicast notifications

- Output formats: both human-readable and JSON output (with pretty-print
  support)

The tool belongs in iproute2 as DPLL devices are tightly integrated with
network interfaces - modern NICs provide hardware clock synchronization
support. The DPLL subsystem uses the same netlink infrastructure as other
networking subsystems, and the tool follows established iproute2 patterns
for command structure, output formatting, and error handling.

Example usage:

  # dpll device show
  # dpll device id-get module-name ice
  # dpll device set id 0 phase-offset-monitor enable
  # dpll pin show
  # dpll pin set id 0 frequency 10000000
  # dpll pin set id 13 parent-device 0 state connected prio 10
  # dpll pin set id 0 reference-sync 1 state connected
  # dpll monitor
  # dpll -j -p device show

Testing notes:

Tested on real hardware with ice and zl3073x drivers. All commands work
(device show/set/id-get, pin show/set/id-get, monitor). JSON output was
carefully compared with cli.py - the tools are interchangeable.

v2:
- Added testing notes
- Added MAINTAINERS entry
- Removed unused -n parameter from man page
v3:
- Use shared mnlg and str_to_bool helpers from lib
- Use str_num_map for bidirectional string/enum mapping
- Remove unnecessary else after return
- Remove misleading "legacy" comments
- Simplify DPLL_PR_MULTI_ENUM_STR macro
- Convert json_output to global json variable
- Use appropriate mnl helpers with proper type casting to respect signed integer data types
- Use DPLL_PR_INT_FMT for phase-adjust-gran to respect signed integer type
- Remove dpll_link from Makefile (mistake in v2)
v4:
- Replace DPLL_PR_MULTI_ENUM_STR macro with dpll_pr_multi_enum_str() function
- Replace pr_out("\n") with print_nl() for one-line mode support
- Remove all is_json_context() code splitting, use PRINT_FP/PRINT_JSON/PRINT_ANY appropriately
- Add dpll_pr_freq_range() helper function to reduce code duplication
v5
- Fix checkpatch warnings
- Use structure initialization instead of memset
- Use sigemptyset() for proper signal mask initialization
- Remove redundant if (json) checks around JSON functions
- Use signalfd for signal handling in monitor
- Set netlink socket to non-blocking in monitor

Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Co-developed-by: Ivan Vecera <ivecera@redhat.com>
Signed-off-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Signed-off-by: David Ahern <dsahern@kernel.org>
2025-11-21 09:11:48 -07:00

317 lines
9.6 KiB
Bash

# bash completion for dpll(8) -*- shell-script -*-
# Get all the optional commands for dpll
_dpll_get_optional_commands()
{
local object=$1; shift
local filter_options=""
local options="$(dpll $object help 2>&1 \
| command sed -n -e "s/^.*dpll $object //p" \
| cut -d " " -f 1)"
# Remove duplicate options from "dpll $OBJECT help" command
local opt
for opt in $options; do
if [[ $filter_options =~ $opt ]]; then
continue
else
filter_options="$filter_options $opt"
fi
done
echo $filter_options
}
# Complete based on given word
_dpll_direct_complete()
{
local device_id pin_id value
case $1 in
device_id)
value=$(dpll -j device show 2>/dev/null \
| jq '.device[].id' 2>/dev/null)
;;
pin_id)
value=$(dpll -j pin show 2>/dev/null \
| jq '.pin[].id' 2>/dev/null)
;;
module_name)
value=$(dpll -j device show 2>/dev/null \
| jq -r '.device[]."module-name"' 2>/dev/null \
| sort -u)
;;
esac
echo $value
}
# Handle device subcommands
_dpll_device()
{
local command=$1
case $command in
show)
case $prev in
id)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete device_id)" -- "$cur" ) )
return 0
;;
*)
COMPREPLY=( $( compgen -W "id" -- "$cur" ) )
return 0
;;
esac
;;
set)
case $prev in
id)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete device_id)" -- "$cur" ) )
return 0
;;
phase-offset-monitor)
COMPREPLY=( $( compgen -W "enable disable true false 0 1" -- "$cur" ) )
return 0
;;
phase-offset-avg-factor)
# numeric value, no completion
return 0
;;
*)
COMPREPLY=( $( compgen -W "id phase-offset-monitor \
phase-offset-avg-factor" -- "$cur" ) )
return 0
;;
esac
;;
id-get)
case $prev in
module-name)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete module_name)" -- "$cur" ) )
return 0
;;
clock-id)
# numeric value, no completion
return 0
;;
type)
COMPREPLY=( $( compgen -W "pps eec" -- "$cur" ) )
return 0
;;
*)
COMPREPLY=( $( compgen -W "module-name clock-id type" \
-- "$cur" ) )
return 0
;;
esac
;;
esac
}
# Handle pin subcommands
_dpll_pin()
{
local command=$1
case $command in
show)
case $prev in
id)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete pin_id)" -- "$cur" ) )
return 0
;;
device)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete device_id)" -- "$cur" ) )
return 0
;;
*)
COMPREPLY=( $( compgen -W "id device" -- "$cur" ) )
return 0
;;
esac
;;
set)
case $prev in
id|parent-device)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete device_id)" -- "$cur" ) )
return 0
;;
parent-pin|reference-sync)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete pin_id)" -- "$cur" ) )
return 0
;;
frequency|phase-adjust|esync-frequency|prio)
# numeric value, no completion
return 0
;;
direction)
COMPREPLY=( $( compgen -W "input output" -- "$cur" ) )
return 0
;;
state)
COMPREPLY=( $( compgen -W \
"connected disconnected selectable" -- "$cur" ) )
return 0
;;
*)
# Check if we are in nested context (after parent-device/parent-pin/reference-sync)
local i nested_keyword=""
for (( i=cword-1; i>0; i-- )); do
case "${words[i]}" in
parent-device)
nested_keyword="parent-device"
break
;;
parent-pin|reference-sync)
nested_keyword="parent-pin"
break
;;
id|frequency|phase-adjust|esync-frequency)
# Hit a top-level keyword, not in nested context
break
;;
esac
done
if [[ "$nested_keyword" == "parent-device" ]]; then
COMPREPLY=( $( compgen -W "direction prio state" -- "$cur" ) )
elif [[ "$nested_keyword" == "parent-pin" ]]; then
COMPREPLY=( $( compgen -W "state" -- "$cur" ) )
else
COMPREPLY=( $( compgen -W "id frequency phase-adjust \
esync-frequency parent-device parent-pin reference-sync" \
-- "$cur" ) )
fi
return 0
;;
esac
;;
id-get)
case $prev in
module-name)
COMPREPLY=( $( compgen -W \
"$(_dpll_direct_complete module_name)" -- "$cur" ) )
return 0
;;
clock-id)
# numeric value, no completion
return 0
;;
board-label|panel-label|package-label)
# string value, no completion
return 0
;;
type)
COMPREPLY=( $( compgen -W "mux ext synce-eth-port \
int-oscillator gnss" -- "$cur" ) )
return 0
;;
*)
COMPREPLY=( $( compgen -W "module-name clock-id \
board-label panel-label package-label type" \
-- "$cur" ) )
return 0
;;
esac
;;
esac
}
# Handle monitor subcommand
_dpll_monitor()
{
# monitor has no additional arguments
return 0
}
# Complete any dpll command
_dpll()
{
local cur prev words cword
local opt='--Version --json --pretty'
local objects="device pin monitor"
_init_completion || return
# Gets the word-to-complete without considering the colon as word breaks
_get_comp_words_by_ref -n : cur prev words cword
if [[ $cword -eq 1 ]]; then
case $cur in
-*)
COMPREPLY=( $( compgen -W "$opt" -- "$cur" ) )
return 0
;;
*)
COMPREPLY=( $( compgen -W "$objects help" -- "$cur" ) )
return 0
;;
esac
fi
# Deal with options
if [[ $prev == -* ]]; then
case $prev in
-V|--Version)
return 0
;;
-j|--json)
COMPREPLY=( $( compgen -W "--pretty $objects" -- "$cur" ) )
return 0
;;
*)
COMPREPLY=( $( compgen -W "$objects" -- "$cur" ) )
return 0
;;
esac
fi
# Remove all options so completions don't have to deal with them.
local i
for (( i=1; i < ${#words[@]}; )); do
if [[ ${words[i]::1} == - ]]; then
words=( "${words[@]:0:i}" "${words[@]:i+1}" )
[[ $i -le $cword ]] && cword=$(( cword - 1 ))
else
i=$(( ++i ))
fi
done
local object=${words[1]}
local command=${words[2]}
local pprev=${words[cword - 2]}
local prev=${words[cword - 1]}
case $object in
device|pin|monitor)
if [[ $cword -eq 2 ]]; then
COMPREPLY=( $( compgen -W "help" -- "$cur") )
if [[ $object != "monitor" ]]; then
COMPREPLY+=( $( compgen -W \
"$(_dpll_get_optional_commands $object)" -- "$cur" ) )
fi
else
"_dpll_$object" $command
fi
;;
help)
return 0
;;
*)
COMPREPLY=( $( compgen -W "$objects help" -- "$cur" ) )
;;
esac
} &&
complete -F _dpll dpll
# ex: ts=4 sw=4 et filetype=sh