From 18bbf4a9c094b1295ad3141a203bcc21839319fc Mon Sep 17 00:00:00 2001 From: Andrea Claudi Date: Tue, 27 Jan 2026 21:15:40 +0100 Subject: [PATCH] iproute-6.17.0-2.el9 * Tue Jan 27 2026 Andrea Claudi - 6.17.0-2.el9 - dpll: Add dpll command (Andrea Claudi) [RHEL-131661] - uapi: import dpll.h from last sync point (David Ahern) [RHEL-131661] - lib: Add str_to_bool helper function (Andrea Claudi) [RHEL-131661] - lib: Move mnlg to lib for shared use (Andrea Claudi) [RHEL-131661] Resolves: RHEL-131661 Signed-off-by: Andrea Claudi --- ...-lib-Move-mnlg-to-lib-for-shared-use.patch | 68 + ...-lib-Add-str_to_bool-helper-function.patch | 128 + ...i-import-dpll.h-from-last-sync-point.patch | 314 ++ 0004-dpll-Add-dpll-command.patch | 2923 +++++++++++++++++ iproute.spec | 6 +- 5 files changed, 3438 insertions(+), 1 deletion(-) create mode 100644 0001-lib-Move-mnlg-to-lib-for-shared-use.patch create mode 100644 0002-lib-Add-str_to_bool-helper-function.patch create mode 100644 0003-uapi-import-dpll.h-from-last-sync-point.patch create mode 100644 0004-dpll-Add-dpll-command.patch diff --git a/0001-lib-Move-mnlg-to-lib-for-shared-use.patch b/0001-lib-Move-mnlg-to-lib-for-shared-use.patch new file mode 100644 index 0000000..5f3d92b --- /dev/null +++ b/0001-lib-Move-mnlg-to-lib-for-shared-use.patch @@ -0,0 +1,68 @@ +From 7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48 Mon Sep 17 00:00:00 2001 +Message-ID: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +From: Andrea Claudi +Date: Tue, 27 Jan 2026 19:59:11 +0100 +Subject: [PATCH] lib: Move mnlg to lib for shared use + +JIRA: https://issues.redhat.com/browse/RHEL-131661 +Upstream Status: iproute2.git commit 0d61015ba9910 + +commit 0d61015ba9910b128db2d7b455056093755e21f9 +Author: Petr Oros +Date: Tue Nov 18 15:10:29 2025 +0100 + + lib: Move mnlg to lib for shared use + + Move mnlg.c to lib/ and mnlg.h to include/ to allow code reuse + across multiple tools. + + Signed-off-by: Petr Oros + Signed-off-by: David Ahern + +Signed-off-by: Andrea Claudi +--- + devlink/Makefile | 2 +- + {devlink => include}/mnlg.h | 0 + lib/Makefile | 2 +- + {devlink => lib}/mnlg.c | 0 + 4 files changed, 2 insertions(+), 2 deletions(-) + rename {devlink => include}/mnlg.h (100%) + rename {devlink => lib}/mnlg.c (100%) + +diff --git a/devlink/Makefile b/devlink/Makefile +index 1a1eed7e..ec62f0c6 100644 +--- a/devlink/Makefile ++++ b/devlink/Makefile +@@ -1,7 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0 + include ../config.mk + +-DEVLINKOBJ = devlink.o mnlg.o ++DEVLINKOBJ = devlink.o + TARGETS += devlink + LDLIBS += -lm + +diff --git a/devlink/mnlg.h b/include/mnlg.h +similarity index 100% +rename from devlink/mnlg.h +rename to include/mnlg.h +diff --git a/lib/Makefile b/lib/Makefile +index 0ba62942..340c37bc 100644 +--- a/lib/Makefile ++++ b/lib/Makefile +@@ -20,7 +20,7 @@ endif + + NLOBJ=libgenl.o libnetlink.o + ifeq ($(HAVE_MNL),y) +-NLOBJ += mnl_utils.o ++NLOBJ += mnl_utils.o mnlg.o + endif + + all: libnetlink.a libutil.a +diff --git a/devlink/mnlg.c b/lib/mnlg.c +similarity index 100% +rename from devlink/mnlg.c +rename to lib/mnlg.c +-- +2.52.0 + diff --git a/0002-lib-Add-str_to_bool-helper-function.patch b/0002-lib-Add-str_to_bool-helper-function.patch new file mode 100644 index 0000000..392e3d1 --- /dev/null +++ b/0002-lib-Add-str_to_bool-helper-function.patch @@ -0,0 +1,128 @@ +From bc56542924619f49773c44ac21d62124dac7ed76 Mon Sep 17 00:00:00 2001 +Message-ID: +In-Reply-To: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +References: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +From: Andrea Claudi +Date: Tue, 27 Jan 2026 19:59:11 +0100 +Subject: [PATCH] lib: Add str_to_bool helper function + +JIRA: https://issues.redhat.com/browse/RHEL-131661 +Upstream Status: iproute2.git commit 42f2f219c618f + +commit 42f2f219c618f0c1bf07e536f5cdcc27cb0d6a4b +Author: Petr Oros +Date: Tue Nov 18 15:10:30 2025 +0100 + + lib: Add str_to_bool helper function + + Add str_to_bool() helper function to lib/utils.c that uses + parse_one_of() to parse boolean values. Update devlink to + use this common implementation. + + Signed-off-by: Petr Oros + Signed-off-by: David Ahern + +Signed-off-by: Andrea Claudi +--- + devlink/devlink.c | 22 +++------------------- + include/utils.h | 1 + + lib/utils.c | 17 +++++++++++++++++ + 3 files changed, 21 insertions(+), 19 deletions(-) + +diff --git a/devlink/devlink.c b/devlink/devlink.c +index 171b8532..89b89d9c 100644 +--- a/devlink/devlink.c ++++ b/devlink/devlink.c +@@ -1038,22 +1038,6 @@ static int ifname_map_rev_lookup(struct dl *dl, const char *bus_name, + return -ENOENT; + } + +-static int strtobool(const char *str, bool *p_val) +-{ +- bool val; +- +- if (!strcmp(str, "true") || !strcmp(str, "1") || +- !strcmp(str, "enable")) +- val = true; +- else if (!strcmp(str, "false") || !strcmp(str, "0") || +- !strcmp(str, "disable")) +- val = false; +- else +- return -EINVAL; +- *p_val = val; +- return 0; +-} +- + static int ident_str_validate(char *str, unsigned int expected) + { + if (!str) +@@ -1356,7 +1340,7 @@ static int dl_argv_bool(struct dl *dl, bool *p_val) + return -EINVAL; + } + +- err = strtobool(str, p_val); ++ err = str_to_bool(str, p_val); + if (err) { + pr_err("\"%s\" is not a valid boolean value\n", str); + return err; +@@ -3821,7 +3805,7 @@ static int cmd_dev_param_set(struct dl *dl) + mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32); + break; + case MNL_TYPE_FLAG: +- err = strtobool(dl->opts.param_value, &val_bool); ++ err = str_to_bool(dl->opts.param_value, &val_bool); + if (err) + goto err_param_value_parse; + if (val_bool == ctx.value.vbool) +@@ -5393,7 +5377,7 @@ static int cmd_port_param_set(struct dl *dl) + mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32); + break; + case MNL_TYPE_FLAG: +- err = strtobool(dl->opts.param_value, &val_bool); ++ err = str_to_bool(dl->opts.param_value, &val_bool); + if (err) + goto err_param_value_parse; + if (val_bool == ctx.value.vbool) +diff --git a/include/utils.h b/include/utils.h +index 91e6e31f..d8e8d70e 100644 +--- a/include/utils.h ++++ b/include/utils.h +@@ -350,6 +350,7 @@ int parse_one_of_deprecated(const char *msg, const char *realval, + const char * const *list, + size_t len, int *p_err); + bool parse_on_off(const char *msg, const char *realval, int *p_err); ++int str_to_bool(const char *str, bool *p_val); + + int parse_mapping_num_all(__u32 *keyp, const char *key); + int parse_mapping_gen(int *argcp, char ***argvp, +diff --git a/lib/utils.c b/lib/utils.c +index dd242d4d..0719281a 100644 +--- a/lib/utils.c ++++ b/lib/utils.c +@@ -1820,6 +1820,23 @@ bool parse_on_off(const char *msg, const char *realval, int *p_err) + ARRAY_SIZE(values_on_off), p_err, strcmp); + } + ++int str_to_bool(const char *str, bool *p_val) ++{ ++ static const char * const values[] = { ++ "false", "true", ++ "0", "1", ++ "disable", "enable" ++ }; ++ int err, index; ++ ++ index = parse_one_of(NULL, str, values, ARRAY_SIZE(values), &err); ++ if (err) ++ return err; ++ ++ *p_val = index & 1; ++ return 0; ++} ++ + int parse_mapping_gen(int *argcp, char ***argvp, + int (*key_cb)(__u32 *keyp, const char *key), + int (*mapping_cb)(__u32 key, char *value, void *data), +-- +2.52.0 + diff --git a/0003-uapi-import-dpll.h-from-last-sync-point.patch b/0003-uapi-import-dpll.h-from-last-sync-point.patch new file mode 100644 index 0000000..281da5a --- /dev/null +++ b/0003-uapi-import-dpll.h-from-last-sync-point.patch @@ -0,0 +1,314 @@ +From 98fe34bfd3e78d1d3fbbe606c441411fad43e8ab Mon Sep 17 00:00:00 2001 +Message-ID: <98fe34bfd3e78d1d3fbbe606c441411fad43e8ab.1769541298.git.aclaudi@redhat.com> +In-Reply-To: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +References: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +From: David Ahern +Date: Fri, 21 Nov 2025 09:11:28 -0700 +Subject: [PATCH] uapi: import dpll.h from last sync point + +JIRA: https://issues.redhat.com/browse/RHEL-131661 +Upstream Status: iproute2.git commit 9771095a3b4de + +commit 9771095a3b4de3f965e6ce2c0dd322e94f6420f8 +Author: David Ahern +Date: Fri Nov 21 09:11:28 2025 -0700 + + uapi: import dpll.h from last sync point + + Signed-off-by: David Ahern + +Signed-off-by: Andrea Claudi +--- + include/uapi/linux/dpll.h | 280 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 280 insertions(+) + create mode 100644 include/uapi/linux/dpll.h + +diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h +new file mode 100644 +index 00000000..d9064eb9 +--- /dev/null ++++ b/include/uapi/linux/dpll.h +@@ -0,0 +1,280 @@ ++/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ ++/* Do not edit directly, auto-generated from: */ ++/* Documentation/netlink/specs/dpll.yaml */ ++/* YNL-GEN uapi header */ ++ ++#ifndef _LINUX_DPLL_H ++#define _LINUX_DPLL_H ++ ++#define DPLL_FAMILY_NAME "dpll" ++#define DPLL_FAMILY_VERSION 1 ++ ++/** ++ * enum dpll_mode - working modes a dpll can support, differentiates if and how ++ * dpll selects one of its inputs to syntonize with it, valid values for ++ * DPLL_A_MODE attribute ++ * @DPLL_MODE_MANUAL: input can be only selected by sending a request to dpll ++ * @DPLL_MODE_AUTOMATIC: highest prio input pin auto selected by dpll ++ */ ++enum dpll_mode { ++ DPLL_MODE_MANUAL = 1, ++ DPLL_MODE_AUTOMATIC, ++ ++ /* private: */ ++ __DPLL_MODE_MAX, ++ DPLL_MODE_MAX = (__DPLL_MODE_MAX - 1) ++}; ++ ++/** ++ * enum dpll_lock_status - provides information of dpll device lock status, ++ * valid values for DPLL_A_LOCK_STATUS attribute ++ * @DPLL_LOCK_STATUS_UNLOCKED: dpll was not yet locked to any valid input (or ++ * forced by setting DPLL_A_MODE to DPLL_MODE_DETACHED) ++ * @DPLL_LOCK_STATUS_LOCKED: dpll is locked to a valid signal, but no holdover ++ * available ++ * @DPLL_LOCK_STATUS_LOCKED_HO_ACQ: dpll is locked and holdover acquired ++ * @DPLL_LOCK_STATUS_HOLDOVER: dpll is in holdover state - lost a valid lock or ++ * was forced by disconnecting all the pins (latter possible only when dpll ++ * lock-state was already DPLL_LOCK_STATUS_LOCKED_HO_ACQ, if dpll lock-state ++ * was not DPLL_LOCK_STATUS_LOCKED_HO_ACQ, the dpll's lock-state shall remain ++ * DPLL_LOCK_STATUS_UNLOCKED) ++ */ ++enum dpll_lock_status { ++ DPLL_LOCK_STATUS_UNLOCKED = 1, ++ DPLL_LOCK_STATUS_LOCKED, ++ DPLL_LOCK_STATUS_LOCKED_HO_ACQ, ++ DPLL_LOCK_STATUS_HOLDOVER, ++ ++ /* private: */ ++ __DPLL_LOCK_STATUS_MAX, ++ DPLL_LOCK_STATUS_MAX = (__DPLL_LOCK_STATUS_MAX - 1) ++}; ++ ++/** ++ * enum dpll_lock_status_error - if previous status change was done due to a ++ * failure, this provides information of dpll device lock status error. Valid ++ * values for DPLL_A_LOCK_STATUS_ERROR attribute ++ * @DPLL_LOCK_STATUS_ERROR_NONE: dpll device lock status was changed without ++ * any error ++ * @DPLL_LOCK_STATUS_ERROR_UNDEFINED: dpll device lock status was changed due ++ * to undefined error. Driver fills this value up in case it is not able to ++ * obtain suitable exact error type. ++ * @DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN: dpll device lock status was changed ++ * because of associated media got down. This may happen for example if dpll ++ * device was previously locked on an input pin of type ++ * PIN_TYPE_SYNCE_ETH_PORT. ++ * @DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH: the FFO ++ * (Fractional Frequency Offset) between the RX and TX symbol rate on the ++ * media got too high. This may happen for example if dpll device was ++ * previously locked on an input pin of type PIN_TYPE_SYNCE_ETH_PORT. ++ */ ++enum dpll_lock_status_error { ++ DPLL_LOCK_STATUS_ERROR_NONE = 1, ++ DPLL_LOCK_STATUS_ERROR_UNDEFINED, ++ DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN, ++ DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH, ++ ++ /* private: */ ++ __DPLL_LOCK_STATUS_ERROR_MAX, ++ DPLL_LOCK_STATUS_ERROR_MAX = (__DPLL_LOCK_STATUS_ERROR_MAX - 1) ++}; ++ ++/* ++ * level of quality of a clock device. This mainly applies when the dpll ++ * lock-status is DPLL_LOCK_STATUS_HOLDOVER. The current list is defined ++ * according to the table 11-7 contained in ITU-T G.8264/Y.1364 document. One ++ * may extend this list freely by other ITU-T defined clock qualities, or ++ * different ones defined by another standardization body (for those, please ++ * use different prefix). ++ */ ++enum dpll_clock_quality_level { ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRC = 1, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_A, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_B, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEC1, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRTC, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRTC, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEEC, ++ DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRC, ++ ++ /* private: */ ++ __DPLL_CLOCK_QUALITY_LEVEL_MAX, ++ DPLL_CLOCK_QUALITY_LEVEL_MAX = (__DPLL_CLOCK_QUALITY_LEVEL_MAX - 1) ++}; ++ ++#define DPLL_TEMP_DIVIDER 1000 ++ ++/** ++ * enum dpll_type - type of dpll, valid values for DPLL_A_TYPE attribute ++ * @DPLL_TYPE_PPS: dpll produces Pulse-Per-Second signal ++ * @DPLL_TYPE_EEC: dpll drives the Ethernet Equipment Clock ++ */ ++enum dpll_type { ++ DPLL_TYPE_PPS = 1, ++ DPLL_TYPE_EEC, ++ ++ /* private: */ ++ __DPLL_TYPE_MAX, ++ DPLL_TYPE_MAX = (__DPLL_TYPE_MAX - 1) ++}; ++ ++/** ++ * enum dpll_pin_type - defines possible types of a pin, valid values for ++ * DPLL_A_PIN_TYPE attribute ++ * @DPLL_PIN_TYPE_MUX: aggregates another layer of selectable pins ++ * @DPLL_PIN_TYPE_EXT: external input ++ * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock ++ * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator ++ * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock ++ */ ++enum dpll_pin_type { ++ DPLL_PIN_TYPE_MUX = 1, ++ DPLL_PIN_TYPE_EXT, ++ DPLL_PIN_TYPE_SYNCE_ETH_PORT, ++ DPLL_PIN_TYPE_INT_OSCILLATOR, ++ DPLL_PIN_TYPE_GNSS, ++ ++ /* private: */ ++ __DPLL_PIN_TYPE_MAX, ++ DPLL_PIN_TYPE_MAX = (__DPLL_PIN_TYPE_MAX - 1) ++}; ++ ++/** ++ * enum dpll_pin_direction - defines possible direction of a pin, valid values ++ * for DPLL_A_PIN_DIRECTION attribute ++ * @DPLL_PIN_DIRECTION_INPUT: pin used as a input of a signal ++ * @DPLL_PIN_DIRECTION_OUTPUT: pin used to output the signal ++ */ ++enum dpll_pin_direction { ++ DPLL_PIN_DIRECTION_INPUT = 1, ++ DPLL_PIN_DIRECTION_OUTPUT, ++ ++ /* private: */ ++ __DPLL_PIN_DIRECTION_MAX, ++ DPLL_PIN_DIRECTION_MAX = (__DPLL_PIN_DIRECTION_MAX - 1) ++}; ++ ++#define DPLL_PIN_FREQUENCY_1_HZ 1 ++#define DPLL_PIN_FREQUENCY_10_KHZ 10000 ++#define DPLL_PIN_FREQUENCY_77_5_KHZ 77500 ++#define DPLL_PIN_FREQUENCY_10_MHZ 10000000 ++ ++/** ++ * enum dpll_pin_state - defines possible states of a pin, valid values for ++ * DPLL_A_PIN_STATE attribute ++ * @DPLL_PIN_STATE_CONNECTED: pin connected, active input of phase locked loop ++ * @DPLL_PIN_STATE_DISCONNECTED: pin disconnected, not considered as a valid ++ * input ++ * @DPLL_PIN_STATE_SELECTABLE: pin enabled for automatic input selection ++ */ ++enum dpll_pin_state { ++ DPLL_PIN_STATE_CONNECTED = 1, ++ DPLL_PIN_STATE_DISCONNECTED, ++ DPLL_PIN_STATE_SELECTABLE, ++ ++ /* private: */ ++ __DPLL_PIN_STATE_MAX, ++ DPLL_PIN_STATE_MAX = (__DPLL_PIN_STATE_MAX - 1) ++}; ++ ++/** ++ * enum dpll_pin_capabilities - defines possible capabilities of a pin, valid ++ * flags on DPLL_A_PIN_CAPABILITIES attribute ++ * @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed ++ * @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed ++ * @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed ++ */ ++enum dpll_pin_capabilities { ++ DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1, ++ DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2, ++ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, ++}; ++ ++#define DPLL_PHASE_OFFSET_DIVIDER 1000 ++ ++/** ++ * enum dpll_feature_state - Allow control (enable/disable) and status checking ++ * over features. ++ * @DPLL_FEATURE_STATE_DISABLE: feature shall be disabled ++ * @DPLL_FEATURE_STATE_ENABLE: feature shall be enabled ++ */ ++enum dpll_feature_state { ++ DPLL_FEATURE_STATE_DISABLE, ++ DPLL_FEATURE_STATE_ENABLE, ++}; ++ ++enum dpll_a { ++ DPLL_A_ID = 1, ++ DPLL_A_MODULE_NAME, ++ DPLL_A_PAD, ++ DPLL_A_CLOCK_ID, ++ DPLL_A_MODE, ++ DPLL_A_MODE_SUPPORTED, ++ DPLL_A_LOCK_STATUS, ++ DPLL_A_TEMP, ++ DPLL_A_TYPE, ++ DPLL_A_LOCK_STATUS_ERROR, ++ DPLL_A_CLOCK_QUALITY_LEVEL, ++ DPLL_A_PHASE_OFFSET_MONITOR, ++ DPLL_A_PHASE_OFFSET_AVG_FACTOR, ++ ++ __DPLL_A_MAX, ++ DPLL_A_MAX = (__DPLL_A_MAX - 1) ++}; ++ ++enum dpll_a_pin { ++ DPLL_A_PIN_ID = 1, ++ DPLL_A_PIN_PARENT_ID, ++ DPLL_A_PIN_MODULE_NAME, ++ DPLL_A_PIN_PAD, ++ DPLL_A_PIN_CLOCK_ID, ++ DPLL_A_PIN_BOARD_LABEL, ++ DPLL_A_PIN_PANEL_LABEL, ++ DPLL_A_PIN_PACKAGE_LABEL, ++ DPLL_A_PIN_TYPE, ++ DPLL_A_PIN_DIRECTION, ++ DPLL_A_PIN_FREQUENCY, ++ DPLL_A_PIN_FREQUENCY_SUPPORTED, ++ DPLL_A_PIN_FREQUENCY_MIN, ++ DPLL_A_PIN_FREQUENCY_MAX, ++ DPLL_A_PIN_PRIO, ++ DPLL_A_PIN_STATE, ++ DPLL_A_PIN_CAPABILITIES, ++ DPLL_A_PIN_PARENT_DEVICE, ++ DPLL_A_PIN_PARENT_PIN, ++ DPLL_A_PIN_PHASE_ADJUST_MIN, ++ DPLL_A_PIN_PHASE_ADJUST_MAX, ++ DPLL_A_PIN_PHASE_ADJUST, ++ DPLL_A_PIN_PHASE_OFFSET, ++ DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, ++ DPLL_A_PIN_ESYNC_FREQUENCY, ++ DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED, ++ DPLL_A_PIN_ESYNC_PULSE, ++ DPLL_A_PIN_REFERENCE_SYNC, ++ DPLL_A_PIN_PHASE_ADJUST_GRAN, ++ ++ __DPLL_A_PIN_MAX, ++ DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) ++}; ++ ++enum dpll_cmd { ++ DPLL_CMD_DEVICE_ID_GET = 1, ++ DPLL_CMD_DEVICE_GET, ++ DPLL_CMD_DEVICE_SET, ++ DPLL_CMD_DEVICE_CREATE_NTF, ++ DPLL_CMD_DEVICE_DELETE_NTF, ++ DPLL_CMD_DEVICE_CHANGE_NTF, ++ DPLL_CMD_PIN_ID_GET, ++ DPLL_CMD_PIN_GET, ++ DPLL_CMD_PIN_SET, ++ DPLL_CMD_PIN_CREATE_NTF, ++ DPLL_CMD_PIN_DELETE_NTF, ++ DPLL_CMD_PIN_CHANGE_NTF, ++ ++ __DPLL_CMD_MAX, ++ DPLL_CMD_MAX = (__DPLL_CMD_MAX - 1) ++}; ++ ++#define DPLL_MCGRP_MONITOR "monitor" ++ ++#endif /* _LINUX_DPLL_H */ +-- +2.52.0 + diff --git a/0004-dpll-Add-dpll-command.patch b/0004-dpll-Add-dpll-command.patch new file mode 100644 index 0000000..1983201 --- /dev/null +++ b/0004-dpll-Add-dpll-command.patch @@ -0,0 +1,2923 @@ +From 53bcc61889d40e8b88931ecb7b5c002b8632ccc1 Mon Sep 17 00:00:00 2001 +Message-ID: <53bcc61889d40e8b88931ecb7b5c002b8632ccc1.1769541298.git.aclaudi@redhat.com> +In-Reply-To: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +References: <7f2e9f746e820e43bafc4ecee18c33a7b6bf5d48.1769541298.git.aclaudi@redhat.com> +From: Andrea Claudi +Date: Tue, 27 Jan 2026 19:59:11 +0100 +Subject: [PATCH] dpll: Add dpll command + +JIRA: https://issues.redhat.com/browse/RHEL-131661 +Upstream Status: iproute2.git commit 656cfc3ce05b0 +Conflicts: adjust Makefile because of missing commit 6f7779ad4ef6 + ("netshaper: Add netshaper command") + +commit 656cfc3ce05b09931089f0c065b7592c4ea77f9b +Author: Petr Oros +Date: Tue Nov 18 15:10:31 2025 +0100 + + 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 + Co-developed-by: Ivan Vecera + Signed-off-by: Petr Oros + Signed-off-by: Ivan Vecera + Signed-off-by: David Ahern + +Signed-off-by: Andrea Claudi +--- + MAINTAINERS | 5 + + Makefile | 3 +- + bash-completion/dpll | 316 +++++++ + dpll/.gitignore | 1 + + dpll/Makefile | 38 + + dpll/dpll.c | 1951 ++++++++++++++++++++++++++++++++++++++++++ + man/man8/dpll.8 | 428 +++++++++ + 7 files changed, 2741 insertions(+), 1 deletion(-) + create mode 100644 bash-completion/dpll + create mode 100644 dpll/.gitignore + create mode 100644 dpll/Makefile + create mode 100644 dpll/dpll.c + create mode 100644 man/man8/dpll.8 + +diff --git a/MAINTAINERS b/MAINTAINERS +index 51833ec1..b175ba27 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -42,6 +42,11 @@ devlink + M: Jiri Pirko + F: devlink/* + ++dpll ++M: Petr Oros ++M: Ivan Vecera ++F: dpll/* ++ + netkit + M: Daniel Borkmann + M: Nikolay Aleksandrov +diff --git a/Makefile b/Makefile +index 2b2c3dec..a4b9d3f0 100644 +--- a/Makefile ++++ b/Makefile +@@ -71,7 +71,7 @@ YACCFLAGS = -d -t -v + + SUBDIRS=lib ip tc bridge misc netem genl man + ifeq ($(HAVE_MNL),y) +-SUBDIRS += tipc devlink rdma dcb vdpa ++SUBDIRS += tipc devlink rdma dcb vdpa dpll + endif + + LIBNETLINK=../lib/libutil.a ../lib/libnetlink.a +@@ -111,6 +111,7 @@ install: all + install -m 0755 -d $(DESTDIR)$(BASH_COMPDIR) + install -m 0644 bash-completion/tc $(DESTDIR)$(BASH_COMPDIR) + install -m 0644 bash-completion/devlink $(DESTDIR)$(BASH_COMPDIR) ++ install -m 0644 bash-completion/dpll $(DESTDIR)$(BASH_COMPDIR) + install -m 0644 include/bpf_elf.h $(DESTDIR)$(HDRDIR) + + version: +diff --git a/bash-completion/dpll b/bash-completion/dpll +new file mode 100644 +index 00000000..6e4d39a5 +--- /dev/null ++++ b/bash-completion/dpll +@@ -0,0 +1,316 @@ ++# 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 +diff --git a/dpll/.gitignore b/dpll/.gitignore +new file mode 100644 +index 00000000..9c5a7748 +--- /dev/null ++++ b/dpll/.gitignore +@@ -0,0 +1 @@ ++dpll +diff --git a/dpll/Makefile b/dpll/Makefile +new file mode 100644 +index 00000000..3a37184b +--- /dev/null ++++ b/dpll/Makefile +@@ -0,0 +1,38 @@ ++# SPDX-License-Identifier: GPL-2.0 ++include ../config.mk ++ ++# iproute2 libraries ++LIBNETLINK=../lib/libutil.a ../lib/libnetlink.a ++ ++TARGETS := ++ALLOBJS := ++ ++# Check if libmnl is available ++ifeq ($(HAVE_MNL),y) ++ ++DPLLOBJ = dpll.o ++TARGETS += dpll ++ALLOBJS += dpll.o ++ ++# libmnl flags ++dpll.o: CFLAGS += -I../include -I../include/uapi ++ ++else ++$(warning "libmnl not found, skipping dpll tool build") ++endif ++ ++# Default CFLAGS for all objects ++CFLAGS += -I../include -I../include/uapi ++ ++all: $(TARGETS) $(LIBS) ++ ++dpll: $(DPLLOBJ) $(LIBNETLINK) ++ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ ++ ++install: all ++ for i in $(TARGETS); \ ++ do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \ ++ done ++ ++clean: ++ rm -f $(ALLOBJS) $(TARGETS) +diff --git a/dpll/dpll.c b/dpll/dpll.c +new file mode 100644 +index 00000000..846ad4b0 +--- /dev/null ++++ b/dpll/dpll.c +@@ -0,0 +1,1951 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * dpll.c DPLL tool ++ * ++ * Authors: Petr Oros ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include "mnl_utils.h" ++#include "version.h" ++#include "utils.h" ++#include "json_print.h" ++ ++#define pr_err(args...) fprintf(stderr, ##args) ++ ++int json; ++ ++struct dpll { ++ struct mnlu_gen_socket nlg; ++ int argc; ++ char **argv; ++}; ++ ++static const char *str_enable_disable(bool v) ++{ ++ return v ? "enable" : "disable"; ++} ++ ++static struct str_num_map pin_state_map[] = { ++ { .str = "connected", .num = DPLL_PIN_STATE_CONNECTED }, ++ { .str = "disconnected", .num = DPLL_PIN_STATE_DISCONNECTED }, ++ { .str = "selectable", .num = DPLL_PIN_STATE_SELECTABLE }, ++ { ++ .str = NULL, ++ }, ++}; ++ ++static struct str_num_map pin_type_map[] = { ++ { .str = "mux", .num = DPLL_PIN_TYPE_MUX }, ++ { .str = "ext", .num = DPLL_PIN_TYPE_EXT }, ++ { .str = "synce-eth-port", .num = DPLL_PIN_TYPE_SYNCE_ETH_PORT }, ++ { .str = "int-oscillator", .num = DPLL_PIN_TYPE_INT_OSCILLATOR }, ++ { .str = "gnss", .num = DPLL_PIN_TYPE_GNSS }, ++ { ++ .str = NULL, ++ }, ++}; ++ ++static struct str_num_map pin_direction_map[] = { ++ { .str = "input", .num = DPLL_PIN_DIRECTION_INPUT }, ++ { .str = "output", .num = DPLL_PIN_DIRECTION_OUTPUT }, ++ { ++ .str = NULL, ++ }, ++}; ++ ++static int dpll_argc(struct dpll *dpll) ++{ ++ return dpll->argc; ++} ++ ++static const char *dpll_argv(struct dpll *dpll) ++{ ++ if (dpll_argc(dpll) == 0) ++ return NULL; ++ return *dpll->argv; ++} ++ ++static void dpll_arg_inc(struct dpll *dpll) ++{ ++ if (dpll_argc(dpll) == 0) ++ return; ++ dpll->argc--; ++ dpll->argv++; ++} ++ ++static const char *dpll_argv_next(struct dpll *dpll) ++{ ++ const char *ret; ++ ++ dpll_arg_inc(dpll); ++ if (dpll_argc(dpll) == 0) ++ return NULL; ++ ++ ret = *dpll->argv; ++ dpll_arg_inc(dpll); ++ return ret; ++} ++ ++static bool dpll_argv_match(struct dpll *dpll, const char *pattern) ++{ ++ if (dpll_argc(dpll) == 0) ++ return false; ++ return strcmp(dpll_argv(dpll), pattern) == 0; ++} ++ ++static int dpll_arg_required(struct dpll *dpll, const char *arg_name) ++{ ++ if (dpll_argc(dpll) == 0) { ++ pr_err("%s requires an argument\n", arg_name); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static bool dpll_argv_match_inc(struct dpll *dpll, const char *pattern) ++{ ++ if (!dpll_argv_match(dpll, pattern)) ++ return false; ++ dpll_arg_inc(dpll); ++ return true; ++} ++ ++static bool dpll_no_arg(struct dpll *dpll) ++{ ++ return dpll_argc(dpll) == 0; ++} ++ ++static int str_to_dpll_pin_state(const char *state_str, __u32 *state) ++{ ++ int num; ++ ++ num = str_map_lookup_str(pin_state_map, state_str); ++ if (num < 0) ++ return num; ++ *state = num; ++ return 0; ++} ++ ++static int str_to_dpll_pin_type(const char *type_str, __u32 *type) ++{ ++ int num; ++ ++ num = str_map_lookup_str(pin_type_map, type_str); ++ if (num < 0) ++ return num; ++ *type = num; ++ return 0; ++} ++ ++static int dpll_parse_state(struct dpll *dpll, __u32 *state) ++{ ++ const char *str = dpll_argv(dpll); ++ ++ if (str_to_dpll_pin_state(str, state)) { ++ pr_err("invalid state: %s (use connected/disconnected/selectable)\n", ++ str); ++ return -EINVAL; ++ } ++ dpll_arg_inc(dpll); ++ return 0; ++} ++ ++static int dpll_parse_direction(struct dpll *dpll, __u32 *direction) ++{ ++ const char *str = dpll_argv(dpll); ++ int num; ++ ++ num = str_map_lookup_str(pin_direction_map, str); ++ if (num < 0) { ++ pr_err("invalid direction: %s (use input/output)\n", str); ++ return num; ++ } ++ *direction = num; ++ dpll_arg_inc(dpll); ++ return 0; ++} ++ ++static int dpll_parse_pin_type(struct dpll *dpll, __u32 *type) ++{ ++ const char *str = dpll_argv(dpll); ++ ++ if (str_to_dpll_pin_type(str, type)) { ++ pr_err("invalid type: %s (use mux/ext/synce-eth-port/int-oscillator/gnss)\n", ++ str); ++ return -EINVAL; ++ } ++ dpll_arg_inc(dpll); ++ return 0; ++} ++ ++static int dpll_parse_u32(struct dpll *dpll, const char *arg_name, ++ __u32 *val_ptr) ++{ ++ const char *__str = dpll_argv_next(dpll); ++ ++ if (!__str) { ++ pr_err("%s requires an argument\n", arg_name); ++ return -EINVAL; ++ } ++ if (get_u32(val_ptr, __str, 0)) { ++ pr_err("invalid %s: %s\n", arg_name, __str); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int dpll_parse_attr_u32(struct dpll *dpll, struct nlmsghdr *nlh, ++ const char *arg_name, int attr_id) ++{ ++ __u32 val; ++ ++ if (dpll_parse_u32(dpll, arg_name, &val)) ++ return -EINVAL; ++ mnl_attr_put_u32(nlh, attr_id, val); ++ return 0; ++} ++ ++static int dpll_parse_attr_s32(struct dpll *dpll, struct nlmsghdr *nlh, ++ const char *arg_name, int attr_id) ++{ ++ const char *str = dpll_argv_next(dpll); ++ __s32 val; ++ ++ if (!str) { ++ pr_err("%s requires an argument\n", arg_name); ++ return -EINVAL; ++ } ++ if (get_s32(&val, str, 0)) { ++ pr_err("invalid %s: %s\n", arg_name, str); ++ return -EINVAL; ++ } ++ mnl_attr_put(nlh, attr_id, sizeof(val), &val); ++ return 0; ++} ++ ++static int dpll_parse_attr_u64(struct dpll *dpll, struct nlmsghdr *nlh, ++ const char *arg_name, int attr_id) ++{ ++ const char *str = dpll_argv_next(dpll); ++ __u64 val; ++ ++ if (!str) { ++ pr_err("%s requires an argument\n", arg_name); ++ return -EINVAL; ++ } ++ if (get_u64(&val, str, 0)) { ++ pr_err("invalid %s: %s\n", arg_name, str); ++ return -EINVAL; ++ } ++ mnl_attr_put_u64(nlh, attr_id, val); ++ return 0; ++} ++ ++static int dpll_parse_attr_str(struct dpll *dpll, struct nlmsghdr *nlh, ++ const char *arg_name, int attr_id) ++{ ++ const char *str = dpll_argv_next(dpll); ++ ++ if (!str) { ++ pr_err("%s requires an argument\n", arg_name); ++ return -EINVAL; ++ } ++ mnl_attr_put_strz(nlh, attr_id, str); ++ return 0; ++} ++ ++static int dpll_parse_attr_enum(struct dpll *dpll, struct nlmsghdr *nlh, ++ const char *arg_name, int attr_id, ++ int (*parse_func)(struct dpll *, __u32 *)) ++{ ++ __u32 val; ++ ++ if (dpll_arg_required(dpll, arg_name)) ++ return -EINVAL; ++ if (parse_func(dpll, &val)) ++ return -EINVAL; ++ mnl_attr_put_u32(nlh, attr_id, val); ++ return 0; ++} ++ ++/* Macros for printing netlink attributes ++ * These macros combine the common pattern of: ++ * ++ * if (tb[ATTR]) ++ * print_xxx(PRINT_ANY, "name", "format", mnl_attr_get_xxx(tb[ATTR])); ++ * ++ * Generic versions with custom format string (_FMT suffix) ++ * Simple versions auto-generate format string: " name: %d\n" ++ */ ++ ++#define DPLL_PR_INT_FMT(tb, attr_id, name, format_str) \ ++ do { \ ++ if (tb[attr_id]) \ ++ print_int( \ ++ PRINT_ANY, name, format_str, \ ++ *(__s32 *)mnl_attr_get_payload(tb[attr_id])); \ ++ } while (0) ++ ++#define DPLL_PR_UINT_FMT(tb, attr_id, name, format_str) \ ++ do { \ ++ if (tb[attr_id]) \ ++ print_uint(PRINT_ANY, name, format_str, \ ++ mnl_attr_get_u32(tb[attr_id])); \ ++ } while (0) ++ ++#define DPLL_PR_U64_FMT(tb, attr_id, name, format_str) \ ++ do { \ ++ if (tb[attr_id]) \ ++ print_lluint(PRINT_ANY, name, format_str, \ ++ mnl_attr_get_u64(tb[attr_id])); \ ++ } while (0) ++ ++#define DPLL_PR_STR_FMT(tb, attr_id, name, format_str) \ ++ do { \ ++ if (tb[attr_id]) \ ++ print_string(PRINT_ANY, name, format_str, \ ++ mnl_attr_get_str(tb[attr_id])); \ ++ } while (0) ++ ++/* Simple versions with auto-generated format */ ++#define DPLL_PR_INT(tb, attr_id, name) \ ++ DPLL_PR_INT_FMT(tb, attr_id, name, " " name ": %d\n") ++ ++#define DPLL_PR_UINT(tb, attr_id, name) \ ++ DPLL_PR_UINT_FMT(tb, attr_id, name, " " name ": %u\n") ++ ++#define DPLL_PR_U64(tb, attr_id, name) \ ++ DPLL_PR_U64_FMT(tb, attr_id, name, " " name ": %" PRIu64 "\n") ++ ++/* Helper to read signed int (can be s32 or s64 depending on value) */ ++static __s64 mnl_attr_get_sint(const struct nlattr *attr) ++{ ++ if (mnl_attr_get_payload_len(attr) == sizeof(__s32)) ++ return *(__s32 *)mnl_attr_get_payload(attr); ++ else ++ return *(__s64 *)mnl_attr_get_payload(attr); ++} ++ ++#define DPLL_PR_SINT_FMT(tb, attr_id, name, format_str) \ ++ do { \ ++ if (tb[attr_id]) \ ++ print_s64(PRINT_ANY, name, format_str, \ ++ mnl_attr_get_sint(tb[attr_id])); \ ++ } while (0) ++ ++#define DPLL_PR_SINT(tb, attr_id, name) \ ++ DPLL_PR_SINT_FMT(tb, attr_id, name, " " name ": %" PRId64 "\n") ++ ++#define DPLL_PR_STR(tb, attr_id, name) \ ++ DPLL_PR_STR_FMT(tb, attr_id, name, " " name ": %s\n") ++ ++/* Temperature macro - JSON prints raw millidegrees, human prints formatted */ ++#define DPLL_PR_TEMP(tb, attr_id) \ ++ do { \ ++ if (tb[attr_id]) { \ ++ __s32 temp = mnl_attr_get_u32(tb[attr_id]); \ ++ div_t d = div(temp, 1000); \ ++ print_int(PRINT_JSON, "temp", NULL, temp); \ ++ print_int(PRINT_FP, NULL, " temp: %d.", d.quot); \ ++ print_int(PRINT_FP, NULL, "%03d C\n", d.rem); \ ++ } \ ++ } while (0) ++ ++/* Generic version with custom format */ ++#define DPLL_PR_ENUM_STR_FMT(tb, attr_id, name, format_str, name_func) \ ++ do { \ ++ if (tb[attr_id]) \ ++ print_string( \ ++ PRINT_ANY, name, format_str, \ ++ name_func(mnl_attr_get_u32(tb[attr_id]))); \ ++ } while (0) ++ ++/* Simple version with auto-generated format */ ++#define DPLL_PR_ENUM_STR(tb, attr_id, name, name_func) \ ++ DPLL_PR_ENUM_STR_FMT(tb, attr_id, name, " " name ": %s\n", name_func) ++ ++/* Multi-attr enum printer - handles multiple occurrences of same attribute */ ++static void dpll_pr_multi_enum_str(const struct nlmsghdr *nlh, int attr_id, ++ const char *name, ++ const char *(*name_func)(__u32)) ++{ ++ struct nlattr *attr; ++ bool first = true; ++ ++ if (!nlh) ++ return; ++ ++ mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) ++ { ++ if (mnl_attr_get_type(attr) == attr_id) { ++ __u32 val = mnl_attr_get_u32(attr); ++ ++ if (first) { ++ open_json_array(PRINT_JSON, name); ++ print_string(PRINT_FP, NULL, " %s:", name); ++ first = false; ++ } ++ print_string(PRINT_ANY, NULL, " %s", name_func(val)); ++ } ++ } ++ ++ if (first) ++ return; ++ ++ close_json_array(PRINT_JSON, NULL); ++ print_nl(); ++} ++ ++/* Print frequency range (or single value if min==max) */ ++static void dpll_pr_freq_range(__u64 freq_min, __u64 freq_max) ++{ ++ open_json_object(NULL); ++ ++ /* JSON: always print both min and max */ ++ print_lluint(PRINT_JSON, "frequency-min", NULL, freq_min); ++ print_lluint(PRINT_JSON, "frequency-max", NULL, freq_max); ++ ++ /* FP: print range or single value */ ++ print_string(PRINT_FP, NULL, " ", NULL); ++ if (freq_min == freq_max) { ++ print_lluint(PRINT_FP, NULL, "%" PRIu64 " Hz\n", freq_min); ++ } else { ++ print_lluint(PRINT_FP, NULL, "%" PRIu64, freq_min); ++ print_string(PRINT_FP, NULL, "-", NULL); ++ print_lluint(PRINT_FP, NULL, "%" PRIu64 " Hz\n", freq_max); ++ } ++ ++ close_json_object(); ++} ++ ++static void help(void) ++{ ++ pr_err("Usage: dpll [ OPTIONS ] OBJECT { COMMAND | help }\n" ++ " dpll [ -j[son] ] [ -p[retty] ]\n" ++ "where OBJECT := { device | pin | monitor }\n" ++ " OPTIONS := { -V[ersion] | -j[son] | -p[retty] }\n"); ++} ++ ++static int cmd_device(struct dpll *dpll); ++static int cmd_pin(struct dpll *dpll); ++static int cmd_monitor(struct dpll *dpll); ++ ++static int dpll_cmd(struct dpll *dpll, int argc, char **argv) ++{ ++ dpll->argc = argc; ++ dpll->argv = argv; ++ ++ if (dpll_argv_match(dpll, "help") || dpll_no_arg(dpll)) { ++ help(); ++ return 0; ++ } else if (dpll_argv_match_inc(dpll, "device")) { ++ return cmd_device(dpll); ++ } else if (dpll_argv_match_inc(dpll, "pin")) { ++ return cmd_pin(dpll); ++ } else if (dpll_argv_match_inc(dpll, "monitor")) { ++ return cmd_monitor(dpll); ++ } ++ pr_err("Object \"%s\" not found\n", dpll_argv(dpll)); ++ return -ENOENT; ++} ++ ++static int dpll_init(struct dpll *dpll) ++{ ++ int err; ++ ++ err = mnlu_gen_socket_open(&dpll->nlg, "dpll", DPLL_FAMILY_VERSION); ++ if (err) { ++ pr_err("Failed to connect to DPLL Netlink (DPLL subsystem not available in kernel?)\n"); ++ return -1; ++ } ++ return 0; ++} ++ ++static void dpll_fini(struct dpll *dpll) ++{ ++ mnlu_gen_socket_close(&dpll->nlg); ++} ++ ++static struct dpll *dpll_alloc(void) ++{ ++ struct dpll *dpll; ++ ++ dpll = calloc(1, sizeof(*dpll)); ++ if (!dpll) ++ return NULL; ++ return dpll; ++} ++ ++static void dpll_free(struct dpll *dpll) ++{ ++ free(dpll); ++} ++ ++int main(int argc, char **argv) ++{ ++ static const struct option long_options[] = { ++ { "Version", no_argument, NULL, 'V' }, ++ { "json", no_argument, NULL, 'j' }, ++ { "pretty", no_argument, NULL, 'p' }, ++ { NULL, 0, NULL, 0 } ++ }; ++ const char *opt_short = "Vjp"; ++ struct dpll *dpll; ++ int err, opt, ret; ++ ++ dpll = dpll_alloc(); ++ if (!dpll) { ++ pr_err("Failed to allocate memory\n"); ++ return EXIT_FAILURE; ++ } ++ ++ while ((opt = getopt_long(argc, argv, opt_short, long_options, NULL)) >= ++ 0) { ++ switch (opt) { ++ case 'V': ++ printf("dpll utility, iproute2-%s\n", version); ++ ret = EXIT_SUCCESS; ++ goto dpll_free; ++ case 'j': ++ json = 1; ++ break; ++ case 'p': ++ pretty = true; ++ break; ++ default: ++ pr_err("Unknown option.\n"); ++ help(); ++ ret = EXIT_FAILURE; ++ goto dpll_free; ++ } ++ } ++ ++ argc -= optind; ++ argv += optind; ++ ++ new_json_obj_plain(json); ++ open_json_object(NULL); ++ ++ /* Skip netlink init for help commands */ ++ bool need_nl = true; ++ ++ if (argc > 0 && strcmp(argv[0], "help") == 0) ++ need_nl = false; ++ if (argc > 1 && strcmp(argv[1], "help") == 0) ++ need_nl = false; ++ ++ if (need_nl) { ++ err = dpll_init(dpll); ++ if (err) { ++ ret = EXIT_FAILURE; ++ goto json_cleanup; ++ } ++ } ++ ++ err = dpll_cmd(dpll, argc, argv); ++ if (err) { ++ ret = EXIT_FAILURE; ++ goto dpll_fini; ++ } ++ ++ ret = EXIT_SUCCESS; ++ ++dpll_fini: ++ if (need_nl) ++ dpll_fini(dpll); ++json_cleanup: ++ close_json_object(); ++ delete_json_obj_plain(); ++dpll_free: ++ dpll_free(dpll); ++ return ret; ++} ++ ++/* ++ * Device commands ++ */ ++ ++static void cmd_device_help(void) ++{ ++ pr_err("Usage: dpll device show [ id DEVICE_ID ]\n"); ++ pr_err(" dpll device set id DEVICE_ID [ phase-offset-monitor { enable | disable } ]\n"); ++ pr_err(" [ phase-offset-avg-factor NUM ]\n"); ++ pr_err(" dpll device id-get [ module-name NAME ] [ clock-id ID ] [ type TYPE ]\n"); ++} ++ ++static const char *dpll_mode_name(__u32 mode) ++{ ++ switch (mode) { ++ case DPLL_MODE_MANUAL: ++ return "manual"; ++ case DPLL_MODE_AUTOMATIC: ++ return "automatic"; ++ default: ++ return "unknown"; ++ } ++} ++ ++static const char *dpll_lock_status_name(__u32 status) ++{ ++ switch (status) { ++ case DPLL_LOCK_STATUS_UNLOCKED: ++ return "unlocked"; ++ case DPLL_LOCK_STATUS_LOCKED: ++ return "locked"; ++ case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: ++ return "locked-ho-acq"; ++ case DPLL_LOCK_STATUS_HOLDOVER: ++ return "holdover"; ++ default: ++ return "unknown"; ++ } ++} ++ ++static const char *dpll_type_name(__u32 type) ++{ ++ switch (type) { ++ case DPLL_TYPE_PPS: ++ return "pps"; ++ case DPLL_TYPE_EEC: ++ return "eec"; ++ default: ++ return "unknown"; ++ } ++} ++ ++static int str_to_dpll_type(const char *s, __u32 *type) ++{ ++ if (!strcmp(s, "pps")) ++ *type = DPLL_TYPE_PPS; ++ else if (!strcmp(s, "eec")) ++ *type = DPLL_TYPE_EEC; ++ else ++ return -EINVAL; ++ return 0; ++} ++ ++static const char *dpll_lock_status_error_name(__u32 error) ++{ ++ switch (error) { ++ case DPLL_LOCK_STATUS_ERROR_NONE: ++ return "none"; ++ case DPLL_LOCK_STATUS_ERROR_UNDEFINED: ++ return "undefined"; ++ case DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN: ++ return "media-down"; ++ case DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH: ++ return "fractional-frequency-offset-too-high"; ++ default: ++ return "unknown"; ++ } ++} ++ ++static const char *dpll_clock_quality_level_name(__u32 level) ++{ ++ switch (level) { ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRC: ++ return "itu-opt1-prc"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_A: ++ return "itu-opt1-ssu-a"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_B: ++ return "itu-opt1-ssu-b"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEC1: ++ return "itu-opt1-eec1"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRTC: ++ return "itu-opt1-prtc"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRTC: ++ return "itu-opt1-eprtc"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEEC: ++ return "itu-opt1-eeec"; ++ case DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRC: ++ return "itu-opt1-eprc"; ++ default: ++ return "unknown"; ++ } ++} ++ ++/* Netlink attribute parsing - device attributes */ ++static int attr_cb(const struct nlattr *attr, void *data) ++{ ++ int type = mnl_attr_get_type(attr); ++ const struct nlattr **tb = data; ++ ++ if (mnl_attr_type_valid(attr, DPLL_A_MAX) < 0) ++ return MNL_CB_OK; ++ ++ tb[type] = attr; ++ return MNL_CB_OK; ++} ++ ++/* Netlink attribute parsing - pin attributes */ ++static int attr_pin_cb(const struct nlattr *attr, void *data) ++{ ++ int type = mnl_attr_get_type(attr); ++ const struct nlattr **tb = data; ++ ++ if (mnl_attr_type_valid(attr, DPLL_A_PIN_MAX) < 0) ++ return MNL_CB_OK; ++ ++ tb[type] = attr; ++ return MNL_CB_OK; ++} ++ ++/* Print device attributes */ ++static void dpll_device_print_attrs(const struct nlmsghdr *nlh, ++ struct nlattr **tb) ++{ ++ DPLL_PR_UINT_FMT(tb, DPLL_A_ID, "id", "device id %u:\n"); ++ DPLL_PR_STR(tb, DPLL_A_MODULE_NAME, "module-name"); ++ DPLL_PR_ENUM_STR(tb, DPLL_A_MODE, "mode", dpll_mode_name); ++ DPLL_PR_U64(tb, DPLL_A_CLOCK_ID, "clock-id"); ++ DPLL_PR_ENUM_STR(tb, DPLL_A_TYPE, "type", dpll_type_name); ++ DPLL_PR_ENUM_STR(tb, DPLL_A_LOCK_STATUS, "lock-status", ++ dpll_lock_status_name); ++ DPLL_PR_ENUM_STR(tb, DPLL_A_LOCK_STATUS_ERROR, "lock-status-error", ++ dpll_lock_status_error_name); ++ dpll_pr_multi_enum_str(nlh, DPLL_A_CLOCK_QUALITY_LEVEL, ++ "clock-quality-level", ++ dpll_clock_quality_level_name); ++ DPLL_PR_TEMP(tb, DPLL_A_TEMP); ++ dpll_pr_multi_enum_str(nlh, DPLL_A_MODE_SUPPORTED, "mode-supported", ++ dpll_mode_name); ++ DPLL_PR_ENUM_STR_FMT(tb, DPLL_A_PHASE_OFFSET_MONITOR, ++ "phase-offset-monitor", ++ " phase-offset-monitor: %s\n", ++ str_enable_disable); ++ DPLL_PR_UINT(tb, DPLL_A_PHASE_OFFSET_AVG_FACTOR, ++ "phase-offset-avg-factor"); ++} ++ ++/* Netlink callback - device get (single device) */ ++static int cmd_device_show_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ struct nlattr *tb[DPLL_A_MAX + 1] = {}; ++ ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), attr_cb, tb); ++ dpll_device_print_attrs(nlh, tb); ++ ++ return MNL_CB_OK; ++} ++ ++/* Netlink callback - device dump (multiple devices) */ ++static int cmd_device_show_dump_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ struct nlattr *tb[DPLL_A_MAX + 1] = {}; ++ ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), attr_cb, tb); ++ ++ open_json_object(NULL); ++ dpll_device_print_attrs(nlh, tb); ++ close_json_object(); ++ ++ return MNL_CB_OK; ++} ++ ++static int cmd_device_show_id(struct dpll *dpll, __u32 id) ++{ ++ struct nlmsghdr *nlh; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_DEVICE_GET, ++ NLM_F_REQUEST | NLM_F_ACK); ++ mnl_attr_put_u32(nlh, DPLL_A_ID, id); ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, cmd_device_show_cb, NULL); ++ if (err < 0) { ++ pr_err("Failed to get device %u\n", id); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int cmd_device_show_dump(struct dpll *dpll) ++{ ++ struct nlmsghdr *nlh; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_DEVICE_GET, ++ NLM_F_REQUEST | NLM_F_ACK | ++ NLM_F_DUMP); ++ ++ open_json_array(PRINT_JSON, "device"); ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, cmd_device_show_dump_cb, ++ NULL); ++ if (err < 0) { ++ pr_err("Failed to dump devices\n"); ++ close_json_array(PRINT_JSON, NULL); ++ return -1; ++ } ++ ++ close_json_array(PRINT_JSON, NULL); ++ ++ return 0; ++} ++ ++static int cmd_device_show(struct dpll *dpll) ++{ ++ bool has_id = false; ++ __u32 id = 0; ++ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match(dpll, "id")) { ++ if (dpll_parse_u32(dpll, "id", &id)) ++ return -EINVAL; ++ has_id = true; ++ } else { ++ pr_err("unknown option: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ } ++ ++ if (has_id) ++ return cmd_device_show_id(dpll, id); ++ ++ return cmd_device_show_dump(dpll); ++} ++ ++static int cmd_device_set(struct dpll *dpll) ++{ ++ struct nlmsghdr *nlh; ++ bool has_id = false; ++ __u32 id = 0; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_DEVICE_SET, ++ NLM_F_REQUEST | NLM_F_ACK); ++ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match(dpll, "id")) { ++ if (dpll_parse_u32(dpll, "id", &id)) ++ return -EINVAL; ++ mnl_attr_put_u32(nlh, DPLL_A_ID, id); ++ has_id = true; ++ } else if (dpll_argv_match(dpll, "phase-offset-monitor")) { ++ const char *str = dpll_argv_next(dpll); ++ bool val; ++ ++ if (!str) { ++ pr_err("phase-offset-monitor requires an argument\n"); ++ return -EINVAL; ++ } ++ if (str_to_bool(str, &val)) { ++ pr_err("invalid phase-offset-monitor value: %s (use enable/disable)\n", ++ str); ++ return -EINVAL; ++ } ++ mnl_attr_put_u32(nlh, DPLL_A_PHASE_OFFSET_MONITOR, ++ val ? 1 : 0); ++ } else if (dpll_argv_match(dpll, "phase-offset-avg-factor")) { ++ if (dpll_parse_attr_u32(dpll, nlh, ++ "phase-offset-avg-factor", ++ DPLL_A_PHASE_OFFSET_AVG_FACTOR)) ++ return -EINVAL; ++ } else { ++ pr_err("unknown option: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ } ++ ++ if (!has_id) { ++ pr_err("device id is required\n"); ++ return -EINVAL; ++ } ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, NULL, NULL); ++ if (err < 0) { ++ pr_err("Failed to set device\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* Netlink callback - print device ID found by query */ ++static int cmd_device_id_get_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ struct nlattr *tb[DPLL_A_MAX + 1] = {}; ++ int *found = data; ++ ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), attr_cb, tb); ++ ++ if (tb[DPLL_A_ID]) { ++ __u32 id = mnl_attr_get_u32(tb[DPLL_A_ID]); ++ ++ print_uint(PRINT_ANY, "id", "%u\n", id); ++ if (found) ++ *found = 1; ++ } ++ ++ return MNL_CB_OK; ++} ++ ++static int cmd_device_id_get(struct dpll *dpll) ++{ ++ struct nlmsghdr *nlh; ++ int found = 0; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_DEVICE_ID_GET, ++ NLM_F_REQUEST | NLM_F_ACK); ++ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match(dpll, "module-name")) { ++ if (dpll_parse_attr_str(dpll, nlh, "module-name", ++ DPLL_A_MODULE_NAME)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "clock-id")) { ++ if (dpll_parse_attr_u64(dpll, nlh, "clock-id", ++ DPLL_A_CLOCK_ID)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "type")) { ++ const char *str = dpll_argv_next(dpll); ++ __u32 val; ++ ++ if (!str) { ++ pr_err("type requires an argument\n"); ++ return -EINVAL; ++ } ++ if (str_to_dpll_type(str, &val)) { ++ pr_err("invalid type: %s (use pps/eec)\n", str); ++ return -EINVAL; ++ } ++ mnl_attr_put_u32(nlh, DPLL_A_TYPE, val); ++ } else { ++ pr_err("unknown option: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ } ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, cmd_device_id_get_cb, ++ &found); ++ if (err < 0) { ++ pr_err("Failed to get device id\n"); ++ return -1; ++ } ++ ++ if (!found) { ++ pr_err("No device found matching the criteria\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int cmd_device(struct dpll *dpll) ++{ ++ if (dpll_argv_match(dpll, "help") || dpll_no_arg(dpll)) { ++ cmd_device_help(); ++ return 0; ++ } else if (dpll_argv_match_inc(dpll, "show")) { ++ return cmd_device_show(dpll); ++ } else if (dpll_argv_match_inc(dpll, "set")) { ++ return cmd_device_set(dpll); ++ } else if (dpll_argv_match_inc(dpll, "id-get")) { ++ return cmd_device_id_get(dpll); ++ } ++ ++ pr_err("Command \"%s\" not found\n", ++ dpll_argv(dpll) ? dpll_argv(dpll) : ""); ++ return -ENOENT; ++} ++ ++/* ++ * Pin commands ++ */ ++ ++static void cmd_pin_help(void) ++{ ++ pr_err("Usage: dpll pin show [ id PIN_ID ] [ device DEVICE_ID ]\n"); ++ pr_err(" dpll pin set id PIN_ID [ frequency FREQ ]\n"); ++ pr_err(" [ phase-adjust ADJUST ]\n"); ++ pr_err(" [ esync-frequency FREQ ]\n"); ++ pr_err(" [ parent-device DEVICE_ID [ direction DIR ]\n"); ++ pr_err(" [ prio PRIO ]\n"); ++ pr_err(" [ state STATE ] ]\n"); ++ pr_err(" [ parent-pin PIN_ID [ state STATE ] ]\n"); ++ pr_err(" [ reference-sync PIN_ID [ state STATE ] ]\n"); ++ pr_err(" dpll pin id-get [ module-name NAME ] [ clock-id ID ]\n"); ++ pr_err(" [ board-label LABEL ] [ panel-label LABEL ]\n"); ++ pr_err(" [ package-label LABEL ] [ type TYPE ]\n"); ++} ++ ++static const char *dpll_pin_type_name(__u32 type) ++{ ++ const char *str; ++ ++ str = str_map_lookup_uint(pin_type_map, type); ++ return str ? str : "unknown"; ++} ++ ++static const char *dpll_pin_state_name(__u32 state) ++{ ++ const char *str; ++ ++ str = str_map_lookup_uint(pin_state_map, state); ++ return str ? str : "unknown"; ++} ++ ++static const char *dpll_pin_direction_name(__u32 direction) ++{ ++ const char *str; ++ ++ str = str_map_lookup_uint(pin_direction_map, direction); ++ return str ? str : "unknown"; ++} ++ ++static void dpll_pin_capabilities_name(__u32 capabilities) ++{ ++ if (capabilities & DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE) ++ print_string(PRINT_FP, NULL, " state-can-change", NULL); ++ if (capabilities & DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE) ++ print_string(PRINT_FP, NULL, " priority-can-change", NULL); ++ if (capabilities & DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE) ++ print_string(PRINT_FP, NULL, " direction-can-change", NULL); ++} ++ ++/* Multi-attribute collection context */ ++struct multi_attr_ctx { ++ int count; ++ struct nlattr **entries; ++}; ++ ++static void dpll_pin_print_freq_supported(struct nlattr *attr) ++{ ++ struct multi_attr_ctx *ctx = (struct multi_attr_ctx *)attr; ++ int i; ++ ++ if (!attr) ++ return; ++ ++ open_json_array(PRINT_JSON, "frequency-supported"); ++ print_string(PRINT_FP, NULL, " frequency-supported:\n", NULL); ++ ++ /* Iterate through all collected frequency-supported entries */ ++ for (i = 0; i < ctx->count; i++) { ++ struct nlattr *tb_freq[DPLL_A_PIN_MAX + 1] = {}; ++ __u64 freq_min = 0, freq_max = 0; ++ ++ mnl_attr_parse_nested(ctx->entries[i], attr_pin_cb, tb_freq); ++ ++ if (tb_freq[DPLL_A_PIN_FREQUENCY_MIN]) ++ freq_min = mnl_attr_get_u64( ++ tb_freq[DPLL_A_PIN_FREQUENCY_MIN]); ++ if (tb_freq[DPLL_A_PIN_FREQUENCY_MAX]) ++ freq_max = mnl_attr_get_u64( ++ tb_freq[DPLL_A_PIN_FREQUENCY_MAX]); ++ ++ dpll_pr_freq_range(freq_min, freq_max); ++ } ++ close_json_array(PRINT_JSON, NULL); ++} ++ ++static void dpll_pin_print_capabilities(struct nlattr *attr) ++{ ++ __u32 caps; ++ ++ if (!attr) ++ return; ++ ++ caps = mnl_attr_get_u32(attr); ++ open_json_array(PRINT_JSON, "capabilities"); ++ if (caps & DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE) ++ print_string(PRINT_JSON, NULL, NULL, "state-can-change"); ++ if (caps & DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE) ++ print_string(PRINT_JSON, NULL, NULL, "priority-can-change"); ++ if (caps & DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE) ++ print_string(PRINT_JSON, NULL, NULL, "direction-can-change"); ++ close_json_array(PRINT_JSON, NULL); ++ ++ print_hex(PRINT_FP, NULL, " capabilities: 0x%x", caps); ++ dpll_pin_capabilities_name(caps); ++ print_nl(); ++} ++ ++static void dpll_pin_print_esync_freq_supported(struct nlattr *attr) ++{ ++ struct multi_attr_ctx *ctx = (struct multi_attr_ctx *)attr; ++ int i; ++ ++ if (!attr) ++ return; ++ ++ open_json_array(PRINT_JSON, "esync-frequency-supported"); ++ print_string(PRINT_FP, NULL, " esync-frequency-supported:\n", NULL); ++ ++ /* Iterate through all collected esync-frequency-supported entries */ ++ for (i = 0; i < ctx->count; i++) { ++ struct nlattr *tb_freq[DPLL_A_PIN_MAX + 1] = {}; ++ __u64 freq_min = 0, freq_max = 0; ++ ++ mnl_attr_parse_nested(ctx->entries[i], attr_pin_cb, tb_freq); ++ ++ if (tb_freq[DPLL_A_PIN_FREQUENCY_MIN]) ++ freq_min = mnl_attr_get_u64( ++ tb_freq[DPLL_A_PIN_FREQUENCY_MIN]); ++ if (tb_freq[DPLL_A_PIN_FREQUENCY_MAX]) ++ freq_max = mnl_attr_get_u64( ++ tb_freq[DPLL_A_PIN_FREQUENCY_MAX]); ++ ++ dpll_pr_freq_range(freq_min, freq_max); ++ } ++ close_json_array(PRINT_JSON, NULL); ++} ++ ++static void dpll_pin_print_parent_devices(struct nlattr *attr) ++{ ++ struct multi_attr_ctx *ctx = (struct multi_attr_ctx *)attr; ++ int i; ++ ++ if (!attr) ++ return; ++ ++ open_json_array(PRINT_JSON, "parent-device"); ++ print_string(PRINT_FP, NULL, " parent-device:\n", NULL); ++ ++ /* Iterate through all collected parent-device entries */ ++ for (i = 0; i < ctx->count; i++) { ++ struct nlattr *tb_parent[DPLL_A_PIN_MAX + 1] = {}; ++ ++ mnl_attr_parse_nested(ctx->entries[i], attr_pin_cb, tb_parent); ++ ++ open_json_object(NULL); ++ print_string(PRINT_FP, NULL, " ", NULL); ++ ++ DPLL_PR_UINT_FMT(tb_parent, DPLL_A_PIN_PARENT_ID, "parent-id", ++ "id %u"); ++ DPLL_PR_ENUM_STR_FMT(tb_parent, DPLL_A_PIN_DIRECTION, ++ "direction", " direction %s", ++ dpll_pin_direction_name); ++ DPLL_PR_UINT_FMT(tb_parent, DPLL_A_PIN_PRIO, "prio", ++ " prio %u"); ++ DPLL_PR_ENUM_STR_FMT(tb_parent, DPLL_A_PIN_STATE, "state", ++ " state %s", dpll_pin_state_name); ++ DPLL_PR_SINT_FMT(tb_parent, DPLL_A_PIN_PHASE_OFFSET, ++ "phase-offset", " phase-offset %" PRId64); ++ ++ print_nl(); ++ close_json_object(); ++ } ++ close_json_array(PRINT_JSON, NULL); ++} ++ ++static void dpll_pin_print_parent_pins(struct nlattr *attr) ++{ ++ struct multi_attr_ctx *ctx = (struct multi_attr_ctx *)attr; ++ int i; ++ ++ if (!attr) ++ return; ++ ++ open_json_array(PRINT_JSON, "parent-pin"); ++ print_string(PRINT_FP, NULL, " parent-pin:\n", NULL); ++ ++ for (i = 0; i < ctx->count; i++) { ++ struct nlattr *tb_parent[DPLL_A_PIN_MAX + 1] = {}; ++ ++ mnl_attr_parse_nested(ctx->entries[i], attr_pin_cb, tb_parent); ++ ++ open_json_object(NULL); ++ print_string(PRINT_FP, NULL, " ", NULL); ++ ++ DPLL_PR_UINT_FMT(tb_parent, DPLL_A_PIN_PARENT_ID, "parent-id", ++ "id %u"); ++ DPLL_PR_ENUM_STR_FMT(tb_parent, DPLL_A_PIN_STATE, "state", ++ " state %s", dpll_pin_state_name); ++ ++ print_nl(); ++ close_json_object(); ++ } ++ close_json_array(PRINT_JSON, NULL); ++} ++ ++static void dpll_pin_print_refsync_pins(struct nlattr *attr) ++{ ++ struct multi_attr_ctx *ctx = (struct multi_attr_ctx *)attr; ++ int i; ++ ++ if (!attr) ++ return; ++ ++ open_json_array(PRINT_JSON, "reference-sync"); ++ print_string(PRINT_FP, NULL, " reference-sync:\n", NULL); ++ ++ for (i = 0; i < ctx->count; i++) { ++ struct nlattr *tb_ref[DPLL_A_PIN_MAX + 1] = {}; ++ ++ mnl_attr_parse_nested(ctx->entries[i], attr_pin_cb, tb_ref); ++ ++ open_json_object(NULL); ++ print_string(PRINT_FP, NULL, " ", NULL); ++ ++ DPLL_PR_UINT_FMT(tb_ref, DPLL_A_PIN_ID, "id", "pin %u"); ++ DPLL_PR_ENUM_STR_FMT(tb_ref, DPLL_A_PIN_STATE, "state", ++ " state %s", dpll_pin_state_name); ++ ++ print_nl(); ++ close_json_object(); ++ } ++ close_json_array(PRINT_JSON, NULL); ++} ++ ++/* Print pin attributes */ ++static void dpll_pin_print_attrs(struct nlattr **tb) ++{ ++ DPLL_PR_UINT_FMT(tb, DPLL_A_PIN_ID, "id", "pin id %u:\n"); ++ DPLL_PR_STR(tb, DPLL_A_PIN_MODULE_NAME, "module-name"); ++ DPLL_PR_U64(tb, DPLL_A_PIN_CLOCK_ID, "clock-id"); ++ DPLL_PR_STR(tb, DPLL_A_PIN_BOARD_LABEL, "board-label"); ++ DPLL_PR_STR(tb, DPLL_A_PIN_PANEL_LABEL, "panel-label"); ++ DPLL_PR_STR(tb, DPLL_A_PIN_PACKAGE_LABEL, "package-label"); ++ DPLL_PR_ENUM_STR(tb, DPLL_A_PIN_TYPE, "type", dpll_pin_type_name); ++ DPLL_PR_U64_FMT(tb, DPLL_A_PIN_FREQUENCY, "frequency", ++ " frequency: %" PRIu64 " Hz\n"); ++ ++ dpll_pin_print_freq_supported(tb[DPLL_A_PIN_FREQUENCY_SUPPORTED]); ++ ++ dpll_pin_print_capabilities(tb[DPLL_A_PIN_CAPABILITIES]); ++ ++ DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST_MIN, "phase-adjust-min"); ++ DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST_MAX, "phase-adjust-max"); ++ DPLL_PR_UINT(tb, DPLL_A_PIN_PHASE_ADJUST_GRAN, "phase-adjust-gran"); ++ DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST, "phase-adjust"); ++ ++ DPLL_PR_SINT(tb, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, ++ "fractional-frequency-offset"); ++ ++ DPLL_PR_U64_FMT(tb, DPLL_A_PIN_ESYNC_FREQUENCY, "esync-frequency", ++ " esync-frequency: %" PRIu64 " Hz\n"); ++ ++ dpll_pin_print_esync_freq_supported( ++ tb[DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED]); ++ ++ DPLL_PR_UINT_FMT(tb, DPLL_A_PIN_ESYNC_PULSE, "esync-pulse", ++ " esync-pulse: %u\n"); ++ ++ dpll_pin_print_parent_devices(tb[DPLL_A_PIN_PARENT_DEVICE]); ++ ++ dpll_pin_print_parent_pins(tb[DPLL_A_PIN_PARENT_PIN]); ++ ++ dpll_pin_print_refsync_pins(tb[DPLL_A_PIN_REFERENCE_SYNC]); ++} ++ ++struct multi_attr_counter { ++ int attr_type; ++ int count; ++}; ++ ++/* Count how many times a specific attribute type appears */ ++static int count_multi_attr_cb(const struct nlattr *attr, void *data) ++{ ++ struct multi_attr_counter *counter = data; ++ int type = mnl_attr_get_type(attr); ++ ++ if (type == counter->attr_type) ++ counter->count++; ++ return MNL_CB_OK; ++} ++ ++/* Helper to count specific multi-attr type occurrences */ ++static unsigned int multi_attr_count_get(const struct nlmsghdr *nlh, ++ struct genlmsghdr *genl, int attr_type) ++{ ++ struct multi_attr_counter counter; ++ ++ counter.attr_type = attr_type; ++ counter.count = 0; ++ mnl_attr_parse(nlh, sizeof(*genl), count_multi_attr_cb, &counter); ++ return counter.count; ++} ++ ++/* Initialize multi-attr context with proper allocation */ ++static int multi_attr_ctx_init(struct multi_attr_ctx *ctx, unsigned int count) ++{ ++ if (count == 0) { ++ ctx->count = 0; ++ ctx->entries = NULL; ++ return 0; ++ } ++ ++ ctx->entries = calloc(count, sizeof(struct nlattr *)); ++ if (!ctx->entries) ++ return -ENOMEM; ++ ctx->count = 0; ++ return 0; ++} ++ ++/* Free multi-attr context */ ++static void multi_attr_ctx_free(struct multi_attr_ctx *ctx) ++{ ++ free(ctx->entries); ++ ctx->entries = NULL; ++ ctx->count = 0; ++} ++ ++/* Generic helper to collect specific multi-attr type */ ++struct multi_attr_collector { ++ int attr_type; ++ struct multi_attr_ctx *ctx; ++}; ++ ++static int collect_multi_attr_cb(const struct nlattr *attr, void *data) ++{ ++ struct multi_attr_collector *collector = data; ++ int type = mnl_attr_get_type(attr); ++ ++ if (type == collector->attr_type) { ++ collector->ctx->entries[collector->ctx->count++] = ++ (struct nlattr *)attr; ++ } ++ return MNL_CB_OK; ++} ++ ++static void dpll_multi_attr_parse(const struct nlmsghdr *nlh, int attr_type, ++ struct multi_attr_ctx *ctx) ++{ ++ struct multi_attr_collector collector; ++ ++ collector.attr_type = attr_type; ++ collector.ctx = ctx; ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), collect_multi_attr_cb, ++ &collector); ++} ++ ++/* Callback for pin get (single) */ ++static int cmd_pin_show_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); ++ struct multi_attr_ctx parent_dev_ctx = { 0 }, parent_pin_ctx = { 0 }, ++ ref_sync_ctx = { 0 }; ++ struct multi_attr_ctx freq_supp_ctx = { 0 }, ++ esync_freq_supp_ctx = { 0 }; ++ struct nlattr *tb[DPLL_A_PIN_MAX + 1] = {}; ++ unsigned int count; ++ int ret; ++ ++ /* First parse to get main attributes */ ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), attr_pin_cb, tb); ++ ++ /* Pass 1: Count multi-attr occurrences and allocate */ ++ count = multi_attr_count_get(nlh, genl, DPLL_A_PIN_PARENT_DEVICE); ++ if (count > 0 && multi_attr_ctx_init(&parent_dev_ctx, count) < 0) ++ goto err_alloc; ++ ++ count = multi_attr_count_get(nlh, genl, DPLL_A_PIN_PARENT_PIN); ++ if (count > 0 && multi_attr_ctx_init(&parent_pin_ctx, count) < 0) ++ goto err_alloc; ++ ++ count = multi_attr_count_get(nlh, genl, DPLL_A_PIN_REFERENCE_SYNC); ++ if (count > 0 && multi_attr_ctx_init(&ref_sync_ctx, count) < 0) ++ goto err_alloc; ++ ++ count = multi_attr_count_get(nlh, genl, DPLL_A_PIN_FREQUENCY_SUPPORTED); ++ if (count > 0 && multi_attr_ctx_init(&freq_supp_ctx, count) < 0) ++ goto err_alloc; ++ ++ count = multi_attr_count_get(nlh, genl, ++ DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED); ++ if (count > 0 && multi_attr_ctx_init(&esync_freq_supp_ctx, count) < 0) ++ goto err_alloc; ++ ++ /* Pass 2: Collect multi-attr entries */ ++ if (parent_dev_ctx.entries) ++ dpll_multi_attr_parse(nlh, DPLL_A_PIN_PARENT_DEVICE, ++ &parent_dev_ctx); ++ if (parent_pin_ctx.entries) ++ dpll_multi_attr_parse(nlh, DPLL_A_PIN_PARENT_PIN, ++ &parent_pin_ctx); ++ if (ref_sync_ctx.entries) ++ dpll_multi_attr_parse(nlh, DPLL_A_PIN_REFERENCE_SYNC, ++ &ref_sync_ctx); ++ if (freq_supp_ctx.entries) ++ dpll_multi_attr_parse(nlh, DPLL_A_PIN_FREQUENCY_SUPPORTED, ++ &freq_supp_ctx); ++ if (esync_freq_supp_ctx.entries) ++ dpll_multi_attr_parse(nlh, DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED, ++ &esync_freq_supp_ctx); ++ ++ /* Replace tb entries with contexts */ ++ if (parent_dev_ctx.count > 0) ++ tb[DPLL_A_PIN_PARENT_DEVICE] = (struct nlattr *)&parent_dev_ctx; ++ if (parent_pin_ctx.count > 0) ++ tb[DPLL_A_PIN_PARENT_PIN] = (struct nlattr *)&parent_pin_ctx; ++ if (ref_sync_ctx.count > 0) ++ tb[DPLL_A_PIN_REFERENCE_SYNC] = (struct nlattr *)&ref_sync_ctx; ++ if (freq_supp_ctx.count > 0) ++ tb[DPLL_A_PIN_FREQUENCY_SUPPORTED] = ++ (struct nlattr *)&freq_supp_ctx; ++ if (esync_freq_supp_ctx.count > 0) ++ tb[DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED] = ++ (struct nlattr *)&esync_freq_supp_ctx; ++ ++ dpll_pin_print_attrs(tb); ++ ++ ret = MNL_CB_OK; ++ goto cleanup; ++ ++err_alloc: ++ fprintf(stderr, ++ "Failed to allocate memory for multi-attr collection\n"); ++ ret = MNL_CB_ERROR; ++ ++cleanup: ++ /* Free allocated memory */ ++ multi_attr_ctx_free(&parent_dev_ctx); ++ multi_attr_ctx_free(&parent_pin_ctx); ++ multi_attr_ctx_free(&ref_sync_ctx); ++ multi_attr_ctx_free(&freq_supp_ctx); ++ multi_attr_ctx_free(&esync_freq_supp_ctx); ++ ++ return ret; ++} ++ ++/* Callback for pin dump (multiple) - wraps each pin in object */ ++static int cmd_pin_show_dump_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ int ret; ++ ++ open_json_object(NULL); ++ ret = cmd_pin_show_cb(nlh, data); ++ close_json_object(); ++ ++ return ret; ++} ++ ++static int cmd_pin_show_id(struct dpll *dpll, __u32 id) ++{ ++ struct nlmsghdr *nlh; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_PIN_GET, ++ NLM_F_REQUEST | NLM_F_ACK); ++ mnl_attr_put_u32(nlh, DPLL_A_PIN_ID, id); ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, cmd_pin_show_cb, NULL); ++ if (err < 0) { ++ pr_err("Failed to get pin %u\n", id); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int cmd_pin_show_dump(struct dpll *dpll, bool has_device_id, ++ __u32 device_id) ++{ ++ struct nlmsghdr *nlh; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_PIN_GET, ++ NLM_F_REQUEST | NLM_F_ACK | ++ NLM_F_DUMP); ++ ++ /* If device_id specified, filter pins by device */ ++ if (has_device_id) ++ mnl_attr_put_u32(nlh, DPLL_A_ID, device_id); ++ ++ /* Open JSON array for multiple pins */ ++ open_json_array(PRINT_JSON, "pin"); ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, cmd_pin_show_dump_cb, ++ NULL); ++ if (err < 0) { ++ pr_err("Failed to dump pins\n"); ++ close_json_array(PRINT_JSON, NULL); ++ return -1; ++ } ++ ++ /* Close JSON array */ ++ close_json_array(PRINT_JSON, NULL); ++ ++ return 0; ++} ++ ++static int cmd_pin_show(struct dpll *dpll) ++{ ++ bool has_pin_id = false, has_device_id = false; ++ __u32 pin_id = 0, device_id = 0; ++ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match(dpll, "id")) { ++ if (dpll_parse_u32(dpll, "id", &pin_id)) ++ return -EINVAL; ++ has_pin_id = true; ++ } else if (dpll_argv_match(dpll, "device")) { ++ if (dpll_parse_u32(dpll, "device", &device_id)) ++ return -EINVAL; ++ has_device_id = true; ++ } else { ++ pr_err("unknown option: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ } ++ ++ if (has_pin_id) ++ return cmd_pin_show_id(dpll, pin_id); ++ else ++ return cmd_pin_show_dump(dpll, has_device_id, device_id); ++} ++ ++static int cmd_pin_parse_parent_device(struct dpll *dpll, struct nlmsghdr *nlh) ++{ ++ struct nlattr *nest; ++ __u32 parent_id; ++ ++ dpll_arg_inc(dpll); ++ if (dpll_arg_required(dpll, "parent-device")) ++ return -EINVAL; ++ ++ if (get_u32(&parent_id, dpll_argv(dpll), 0)) { ++ pr_err("invalid parent-device id: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ dpll_arg_inc(dpll); ++ ++ nest = mnl_attr_nest_start(nlh, DPLL_A_PIN_PARENT_DEVICE); ++ mnl_attr_put_u32(nlh, DPLL_A_PIN_PARENT_ID, parent_id); ++ ++ /* Parse optional parent-device attributes */ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match_inc(dpll, "direction")) { ++ if (dpll_parse_attr_enum(dpll, nlh, "direction", ++ DPLL_A_PIN_DIRECTION, ++ dpll_parse_direction)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "prio")) { ++ if (dpll_parse_attr_u32(dpll, nlh, "prio", ++ DPLL_A_PIN_PRIO)) ++ return -EINVAL; ++ } else if (dpll_argv_match_inc(dpll, "state")) { ++ if (dpll_parse_attr_enum(dpll, nlh, "state", ++ DPLL_A_PIN_STATE, ++ dpll_parse_state)) ++ return -EINVAL; ++ } else { ++ /* Not a parent-device attribute, break to parse ++ * next option. ++ */ ++ break; ++ } ++ } ++ ++ mnl_attr_nest_end(nlh, nest); ++ ++ return 0; ++} ++ ++static int cmd_pin_parse_parent_pin(struct dpll *dpll, struct nlmsghdr *nlh) ++{ ++ struct nlattr *nest; ++ __u32 parent_id; ++ ++ dpll_arg_inc(dpll); ++ if (dpll_arg_required(dpll, "parent-pin")) ++ return -EINVAL; ++ ++ if (get_u32(&parent_id, dpll_argv(dpll), 0)) { ++ pr_err("invalid parent-pin id: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ dpll_arg_inc(dpll); ++ ++ nest = mnl_attr_nest_start(nlh, DPLL_A_PIN_PARENT_PIN); ++ mnl_attr_put_u32(nlh, DPLL_A_PIN_PARENT_ID, parent_id); ++ ++ /* Parse optional parent-pin attributes */ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match_inc(dpll, "state")) { ++ if (dpll_parse_attr_enum(dpll, nlh, "state", ++ DPLL_A_PIN_STATE, ++ dpll_parse_state)) ++ return -EINVAL; ++ } else { ++ /* Not a parent-pin attribute, break to parse next ++ * option. ++ */ ++ break; ++ } ++ } ++ ++ mnl_attr_nest_end(nlh, nest); ++ ++ return 0; ++} ++ ++static int cmd_pin_parse_reference_sync(struct dpll *dpll, struct nlmsghdr *nlh) ++{ ++ struct nlattr *nest; ++ __u32 ref_pin_id; ++ ++ dpll_arg_inc(dpll); ++ if (dpll_arg_required(dpll, "reference-sync")) ++ return -EINVAL; ++ ++ if (get_u32(&ref_pin_id, dpll_argv(dpll), 0)) { ++ pr_err("invalid reference-sync pin id: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ dpll_arg_inc(dpll); ++ ++ nest = mnl_attr_nest_start(nlh, DPLL_A_PIN_REFERENCE_SYNC); ++ mnl_attr_put_u32(nlh, DPLL_A_PIN_ID, ref_pin_id); ++ ++ /* Parse optional reference-sync attributes */ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match_inc(dpll, "state")) { ++ if (dpll_parse_attr_enum(dpll, nlh, "state", ++ DPLL_A_PIN_STATE, ++ dpll_parse_state)) ++ return -EINVAL; ++ } else { ++ /* Not a reference-sync attribute, break to parse ++ * next option. ++ */ ++ break; ++ } ++ } ++ ++ mnl_attr_nest_end(nlh, nest); ++ ++ return 0; ++} ++ ++static int cmd_pin_set(struct dpll *dpll) ++{ ++ struct nlmsghdr *nlh; ++ bool has_id = false; ++ __u32 id = 0; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_PIN_SET, ++ NLM_F_REQUEST | NLM_F_ACK); ++ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match(dpll, "id")) { ++ if (dpll_parse_u32(dpll, "id", &id)) ++ return -EINVAL; ++ mnl_attr_put_u32(nlh, DPLL_A_PIN_ID, id); ++ has_id = true; ++ } else if (dpll_argv_match(dpll, "frequency")) { ++ if (dpll_parse_attr_u64(dpll, nlh, "frequency", ++ DPLL_A_PIN_FREQUENCY)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "phase-adjust")) { ++ if (dpll_parse_attr_s32(dpll, nlh, "phase-adjust", ++ DPLL_A_PIN_PHASE_ADJUST)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "esync-frequency")) { ++ if (dpll_parse_attr_u64(dpll, nlh, "esync-frequency", ++ DPLL_A_PIN_ESYNC_FREQUENCY)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "parent-device")) { ++ if (cmd_pin_parse_parent_device(dpll, nlh)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "parent-pin")) { ++ if (cmd_pin_parse_parent_pin(dpll, nlh)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "reference-sync")) { ++ if (cmd_pin_parse_reference_sync(dpll, nlh)) ++ return -EINVAL; ++ } else { ++ pr_err("unknown option: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ } ++ ++ if (!has_id) { ++ pr_err("pin id is required\n"); ++ return -EINVAL; ++ } ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, NULL, NULL); ++ if (err < 0) { ++ pr_err("Failed to set pin\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int cmd_pin_id_get_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ struct nlattr *tb[DPLL_A_PIN_MAX + 1] = {}; ++ int *found = data; ++ ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), attr_pin_cb, tb); ++ ++ if (tb[DPLL_A_PIN_ID]) { ++ __u32 id = mnl_attr_get_u32(tb[DPLL_A_PIN_ID]); ++ ++ print_uint(PRINT_ANY, "id", "%u\n", id); ++ if (found) ++ *found = 1; ++ } ++ ++ return MNL_CB_OK; ++} ++ ++static int cmd_pin_id_get(struct dpll *dpll) ++{ ++ struct nlmsghdr *nlh; ++ int found = 0; ++ int err; ++ ++ nlh = mnlu_gen_socket_cmd_prepare(&dpll->nlg, DPLL_CMD_PIN_ID_GET, ++ NLM_F_REQUEST | NLM_F_ACK); ++ ++ while (dpll_argc(dpll) > 0) { ++ if (dpll_argv_match(dpll, "module-name")) { ++ if (dpll_parse_attr_str(dpll, nlh, "module-name", ++ DPLL_A_PIN_MODULE_NAME)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "clock-id")) { ++ if (dpll_parse_attr_u64(dpll, nlh, "clock-id", ++ DPLL_A_PIN_CLOCK_ID)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "board-label")) { ++ if (dpll_parse_attr_str(dpll, nlh, "board-label", ++ DPLL_A_PIN_BOARD_LABEL)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "panel-label")) { ++ if (dpll_parse_attr_str(dpll, nlh, "panel-label", ++ DPLL_A_PIN_PANEL_LABEL)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "package-label")) { ++ if (dpll_parse_attr_str(dpll, nlh, "package-label", ++ DPLL_A_PIN_PACKAGE_LABEL)) ++ return -EINVAL; ++ } else if (dpll_argv_match(dpll, "type")) { ++ if (dpll_parse_attr_enum(dpll, nlh, "type", ++ DPLL_A_PIN_TYPE, ++ dpll_parse_pin_type)) ++ return -EINVAL; ++ } else { ++ pr_err("unknown option: %s\n", dpll_argv(dpll)); ++ return -EINVAL; ++ } ++ } ++ ++ err = mnlu_gen_socket_sndrcv(&dpll->nlg, nlh, cmd_pin_id_get_cb, ++ &found); ++ if (err < 0) { ++ pr_err("Failed to get pin id\n"); ++ return -1; ++ } ++ ++ if (!found) { ++ pr_err("No pin found matching the criteria\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int cmd_pin(struct dpll *dpll) ++{ ++ if (dpll_argv_match(dpll, "help") || dpll_no_arg(dpll)) { ++ cmd_pin_help(); ++ return 0; ++ } else if (dpll_argv_match_inc(dpll, "show")) { ++ return cmd_pin_show(dpll); ++ } else if (dpll_argv_match_inc(dpll, "set")) { ++ return cmd_pin_set(dpll); ++ } else if (dpll_argv_match_inc(dpll, "id-get")) { ++ return cmd_pin_id_get(dpll); ++ } ++ ++ pr_err("Command \"%s\" not found\n", ++ dpll_argv(dpll) ? dpll_argv(dpll) : ""); ++ return -ENOENT; ++} ++ ++/* Monitor command - notification handling */ ++static int cmd_monitor_cb(const struct nlmsghdr *nlh, void *data) ++{ ++ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); ++ const char *cmd_name = "UNKNOWN"; ++ const char *json_name = "unknown"; ++ int ret = MNL_CB_OK; ++ ++ switch (genl->cmd) { ++ case DPLL_CMD_DEVICE_CREATE_NTF: ++ cmd_name = "DEVICE_CREATE"; ++ json_name = "device-create-ntf"; ++ /* fallthrough */ ++ case DPLL_CMD_DEVICE_CHANGE_NTF: ++ if (genl->cmd == DPLL_CMD_DEVICE_CHANGE_NTF) { ++ cmd_name = "DEVICE_CHANGE"; ++ json_name = "device-change-ntf"; ++ } ++ /* fallthrough */ ++ case DPLL_CMD_DEVICE_DELETE_NTF: { ++ if (genl->cmd == DPLL_CMD_DEVICE_DELETE_NTF) { ++ cmd_name = "DEVICE_DELETE"; ++ json_name = "device-delete-ntf"; ++ } ++ struct nlattr *tb[DPLL_A_MAX + 1] = {}; ++ ++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), attr_cb, tb); ++ ++ open_json_object(NULL); ++ print_string(PRINT_JSON, "name", NULL, json_name); ++ open_json_object("msg"); ++ print_string(PRINT_FP, NULL, "[%s] ", cmd_name); ++ ++ dpll_device_print_attrs(nlh, tb); ++ ++ close_json_object(); ++ close_json_object(); ++ break; ++ } ++ case DPLL_CMD_PIN_CREATE_NTF: ++ cmd_name = "PIN_CREATE"; ++ json_name = "pin-create-ntf"; ++ /* fallthrough */ ++ case DPLL_CMD_PIN_CHANGE_NTF: ++ if (genl->cmd == DPLL_CMD_PIN_CHANGE_NTF) { ++ cmd_name = "PIN_CHANGE"; ++ json_name = "pin-change-ntf"; ++ } ++ /* fallthrough */ ++ case DPLL_CMD_PIN_DELETE_NTF: { ++ if (genl->cmd == DPLL_CMD_PIN_DELETE_NTF) { ++ cmd_name = "PIN_DELETE"; ++ json_name = "pin-delete-ntf"; ++ } ++ ++ open_json_object(NULL); ++ print_string(PRINT_JSON, "name", NULL, json_name); ++ open_json_object("msg"); ++ print_string(PRINT_FP, NULL, "[%s] ", cmd_name); ++ ++ ret = cmd_pin_show_cb(nlh, NULL); ++ ++ close_json_object(); ++ close_json_object(); ++ break; ++ } ++ default: ++ pr_err("Unknown notification command: %d\n", genl->cmd); ++ break; ++ } ++ ++ return ret; ++} ++ ++static int cmd_monitor(struct dpll *dpll) ++{ ++ int netlink_fd, signal_fd = -1; ++ struct pollfd pfds[2]; ++ sigset_t mask; ++ int ret = 0; ++ ++ ret = mnlg_socket_group_add(&dpll->nlg, "monitor"); ++ if (ret) { ++ pr_err("Failed to subscribe to monitor group: %s\n", ++ strerror(errno)); ++ return ret; ++ } ++ ++ print_string(PRINT_FP, NULL, ++ "Monitoring DPLL events (Press Ctrl+C to stop)...\n", ++ NULL); ++ ++ sigemptyset(&mask); ++ sigaddset(&mask, SIGINT); ++ sigaddset(&mask, SIGTERM); ++ if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { ++ pr_err("Failed to block signals: %s\n", strerror(errno)); ++ return -errno; ++ } ++ ++ signal_fd = signalfd(-1, &mask, SFD_CLOEXEC); ++ if (signal_fd < 0) { ++ pr_err("Failed to create signalfd: %s\n", strerror(errno)); ++ ret = -errno; ++ goto err_sigmask; ++ } ++ ++ netlink_fd = mnlg_socket_get_fd(&dpll->nlg); ++ if (netlink_fd < 0) { ++ pr_err("Failed to get netlink socket fd\n"); ++ ret = -1; ++ goto err_signalfd; ++ } ++ ++ ret = fcntl(netlink_fd, F_GETFL); ++ if (ret < 0) { ++ pr_err("Failed to get netlink socket flags: %s\n", ++ strerror(errno)); ++ ret = -errno; ++ goto err_signalfd; ++ } ++ if (fcntl(netlink_fd, F_SETFL, ret | O_NONBLOCK) < 0) { ++ pr_err("Failed to set netlink socket to non-blocking: %s\n", ++ strerror(errno)); ++ ret = -errno; ++ goto err_signalfd; ++ } ++ ++ open_json_array(PRINT_JSON, "monitor"); ++ ++ pfds[0].fd = signal_fd; ++ pfds[0].events = POLLIN; ++ pfds[1].fd = netlink_fd; ++ pfds[1].events = POLLIN; ++ ++ while (1) { ++ ret = poll(pfds, ARRAY_SIZE(pfds), -1); ++ if (ret < 0) { ++ if (errno == EINTR) ++ continue; ++ pr_err("poll() failed: %s\n", strerror(errno)); ++ ret = -errno; ++ break; ++ } ++ ++ if (pfds[0].revents & POLLIN) { ++ ret = 0; ++ break; ++ } ++ ++ if (pfds[1].revents & POLLIN) { ++ ret = mnlu_gen_socket_recv_run(&dpll->nlg, ++ cmd_monitor_cb, NULL); ++ if (ret < 0 && errno != EAGAIN && ++ errno != EWOULDBLOCK) { ++ pr_err("Failed to receive notifications: %s\n", ++ strerror(errno)); ++ break; ++ } ++ } ++ } ++ ++ close_json_array(PRINT_JSON, NULL); ++ ++err_signalfd: ++ if (signal_fd >= 0) ++ close(signal_fd); ++err_sigmask: ++ sigprocmask(SIG_UNBLOCK, &mask, NULL); ++ ++ return ret < 0 ? ret : 0; ++} +diff --git a/man/man8/dpll.8 b/man/man8/dpll.8 +new file mode 100644 +index 00000000..fcc1c4a6 +--- /dev/null ++++ b/man/man8/dpll.8 +@@ -0,0 +1,428 @@ ++.TH DPLL 8 "23 October 2025" "iproute2" "Linux" ++.SH NAME ++dpll \- Digital Phase Locked Loop (DPLL) subsystem management ++.SH SYNOPSIS ++.ad l ++.in +8 ++.ti -8 ++.B dpll ++.RI "[ " OPTIONS " ]" ++.B device ++.RI "{ " COMMAND " | " ++.BR help " }" ++.sp ++ ++.ti -8 ++.B dpll ++.RI "[ " OPTIONS " ]" ++.B pin ++.RI "{ " COMMAND " | " ++.BR help " }" ++.sp ++ ++.ti -8 ++.B dpll ++.RI "[ " OPTIONS " ]" ++.B monitor ++ ++.ti -8 ++.IR OPTIONS " := { " ++\fB\-V\fR[\fIersion\fR] | ++\fB\-j\fR[\fIson\fR] | ++\fB\-p\fR[\fIretty\fR] } ++ ++.SH DESCRIPTION ++The ++.B dpll ++utility is used to configure and monitor Digital Phase Locked Loop (DPLL) ++devices and pins. DPLLs are used for clock synchronization in various ++hardware, particularly in telecommunications and networking equipment. ++ ++A DPLL device can lock to one or more input pins and provide synchronized ++output. Pins can be physical external signals (like GNSS 1PPS, SyncE), or ++internal oscillators. ++ ++.SH OPTIONS ++.TP ++.BR "\-V" , " \-Version" ++Print the version of the ++.B dpll ++utility and exit. ++ ++.TP ++.BR "\-j" , " \-json" ++Output results in JavaScript Object Notation (JSON). ++ ++.TP ++.BR "\-p" , " \-pretty" ++When combined with \-j, generates a pretty JSON output with indentation ++and newlines for better human readability. ++ ++.SH DEVICE COMMANDS ++ ++.SS dpll device show [ id ID ] ++ ++Display information about DPLL devices. If no arguments are specified, ++shows all devices in the system. ++ ++.TP ++.BI id " ID" ++Show only the device with the specified numeric identifier. ++ ++.PP ++Output includes: ++.RS ++.IP \[bu] 2 ++Device ID ++.IP \[bu] ++Module name providing the device ++.IP \[bu] ++Clock ID (unique identifier) ++.IP \[bu] ++Operating mode (manual, automatic, holdover, freerun) ++.IP \[bu] ++Lock status (locked-ho-ack, locked, unlocked, holdover) ++.IP \[bu] ++Temperature (if supported) ++.IP \[bu] ++Type (PPS or EEC) ++.RE ++ ++.SS dpll device set id ID [ phase-offset-monitor { enable | disable } ] [ phase-offset-avg-factor FACTOR ] ++ ++Configure DPLL device parameters. ++ ++.TP ++.BI id " ID" ++Specifies which device to configure (required). ++ ++.TP ++.BI phase-offset-monitor " { enable | disable | true | false | 0 | 1 }" ++Enable or disable phase offset monitoring between the device and its pins. ++When enabled, the kernel continuously measures and reports phase differences. ++ ++.TP ++.BI phase-offset-avg-factor " FACTOR" ++Set the averaging factor (1-255) applied to phase offset calculations. ++Higher values provide smoother but slower-responding measurements. ++ ++.SS dpll device id-get [ module-name NAME ] [ clock-id ID ] [ type TYPE ] ++ ++Retrieve the device ID based on identifying attributes. Useful for scripting ++when you need to find a device's numeric ID. At least one attribute should ++be specified to identify the device. ++ ++.TP ++.BI module-name " NAME" ++Kernel module name. ++ ++.TP ++.BI clock-id " ID" ++64-bit clock identifier in decimal or hex (0x prefix). ++ ++.TP ++.BI type " TYPE" ++Device type: ++.BR pps " or " eec . ++ ++.SH PIN COMMANDS ++ ++.SS dpll pin show [ id ID ] [ device ID ] ++ ++Display information about DPLL pins. If no arguments are specified, ++shows all pins in the system. ++ ++.TP ++.BI id " ID" ++Show only the pin with the specified numeric identifier. ++ ++.TP ++.BI device " ID" ++Show only pins associated with the specified device ID. ++ ++.PP ++Output includes: ++.RS ++.IP \[bu] 2 ++Pin ID ++.IP \[bu] ++Module name ++.IP \[bu] ++Clock ID ++.IP \[bu] ++Board label (hardware label from device tree or ACPI) ++.IP \[bu] ++Pin type (mux, ext, synce-eth-port, int-oscillator, gnss) ++.IP \[bu] ++Frequency and supported frequency ranges ++.IP \[bu] ++Capabilities (state-can-change, priority-can-change, direction-can-change) ++.IP \[bu] ++Phase adjustment range, granularity, and current value ++.IP \[bu] ++Parent device relationships (direction, priority, state, phase offset) ++.IP \[bu] ++Parent pin relationships ++.IP \[bu] ++Reference sync information ++.IP \[bu] ++Esync frequency support (if applicable) ++.RE ++ ++.SS dpll pin set id ID [ PARAMETER VALUE ] ... ++ ++Configure DPLL pin parameters. Multiple parameters can be specified ++in a single command. ++ ++.TP ++.BI id " ID" ++Specifies which pin to configure (required). ++ ++.TP ++.BI frequency " FREQ" ++Set pin frequency in Hz. The pin must support the specified frequency ++(check frequency-supported ranges in pin show output). ++ ++.TP ++.BI phase-adjust " ADJUSTMENT" ++Set phase adjustment in picoseconds. This value fine-tunes the phase ++of the output signal. Negative values shift the phase backwards, ++positive values shift it forwards. The value must be within the ++phase-adjust-min and phase-adjust-max range. ++ ++.TP ++.BI esync-frequency " FREQUENCY" ++Set enhanced SyncE (Synchronous Ethernet) frequency in Hz for capable pins. ++ ++.TP ++.BI parent-device " DEVICE_ID " "[ " "direction DIR" " ] [ " "prio PRIO" " ] [ " "state STATE" " ]" ++Configure the relationship between this pin and a parent DPLL device. ++.RS ++.TP ++.BI direction " { input | output }" ++Set the pin's direction relative to the parent device. ++.TP ++.BI prio " PRIORITY" ++Set priority (0-255) for this pin on the parent device. ++.TP ++.BI state " { connected | disconnected | selectable }" ++Set the pin's state on the parent device. ++.RE ++ ++.TP ++.BI parent-pin " PIN_ID " "[ " "state STATE" " ]" ++Configure the relationship to a parent pin. ++ ++.TP ++.BI reference-sync " PIN_ID " "[ " "state STATE" " ]" ++Configure reference sync relationship with another pin. ++ ++.SS dpll pin id-get [ SELECTOR ] ... ++ ++Retrieve a pin ID based on identifying attributes. ++ ++.TP ++.BI module-name " NAME" ++Filter by kernel module name. ++ ++.TP ++.BI clock-id " ID" ++Filter by 64-bit clock identifier. ++ ++.TP ++.BI board-label " LABEL" ++Filter by board label (hardware identifier). ++ ++.TP ++.BI panel-label " LABEL" ++Filter by panel label. ++ ++.TP ++.BI package-label " LABEL" ++Filter by package label. ++ ++.TP ++.BI type " TYPE" ++Filter by pin type: ++.BR mux ", " ext ", " synce-eth-port ", " int-oscillator ", " gnss . ++ ++.SH MONITOR COMMAND ++ ++.SS dpll monitor ++ ++Monitor DPLL subsystem events in real-time. Displays notifications about: ++.RS ++.IP \[bu] 2 ++Device creation, deletion, and configuration changes ++.IP \[bu] ++Pin creation, deletion, and configuration changes ++.IP \[bu] ++Lock status changes ++.IP \[bu] ++Phase offset updates ++.IP \[bu] ++Frequency changes ++.RE ++ ++.PP ++Events are prefixed with their type: [DEVICE_CREATE], [DEVICE_CHANGE], ++[DEVICE_DELETE], [PIN_CREATE], [PIN_CHANGE], [PIN_DELETE]. ++ ++.PP ++Press Ctrl+C to stop monitoring. ++ ++.SH EXAMPLES ++ ++.SS Show all DPLL devices ++.nf ++.B dpll device show ++.fi ++ ++.SS Show specific device in JSON format ++.nf ++.B dpll -j device show id 0 ++.fi ++ ++.SS Enable phase offset monitoring on device 0 ++.nf ++.B dpll device set id 0 phase-offset-monitor enable ++.fi ++ ++.SS Show all pins ++.nf ++.B dpll pin show ++.fi ++ ++.SS Show pin with pretty JSON output ++.nf ++.B dpll -jp pin show id 5 ++.fi ++ ++.SS Set pin frequency to 10 MHz ++.nf ++.B dpll pin set id 0 frequency 10000000 ++.fi ++ ++.SS Configure pin relationship to parent device ++.nf ++.B dpll pin set id 1 parent-device 0 prio 10 direction input state connected ++.fi ++ ++.SS Adjust phase by -1000 picoseconds ++.nf ++.B dpll pin set id 2 phase-adjust -1000 ++.fi ++ ++.SS Set multiple pin parameters at once ++.nf ++.B dpll pin set id 3 frequency 10000000 phase-adjust -1000 ++.fi ++ ++.SS Monitor DPLL events ++.nf ++.B dpll monitor ++.fi ++ ++.SS Monitor events in JSON format ++.nf ++.B dpll -jp monitor ++.fi ++ ++.SS Get device ID by module name ++.nf ++.B dpll device id-get module-name ice ++.fi ++ ++.SS Get pin ID by board label ++.nf ++.B dpll pin id-get board-label "GNSS-1PPS" ++.fi ++ ++.SH PHASE ADJUSTMENT ++Phase adjustment is specified in picoseconds (1e-12 seconds) and allows ++fine-tuning of signal phase. This is crucial for precise time synchronization ++applications like 5G networks and high-frequency trading. ++ ++.PP ++Important considerations: ++.RS ++.IP \[bu] 2 ++Check phase-adjust-min and phase-adjust-max before setting values ++.IP \[bu] ++Some hardware requires values to be multiples of phase-adjust-gran ++.IP \[bu] ++Negative values shift phase backwards (earlier in time) ++.IP \[bu] ++Positive values shift phase forwards (later in time) ++.IP \[bu] ++The kernel validates all phase adjustment requests ++.RE ++ ++.SH CAPABILITIES ++Pins may have various capabilities that determine which operations are allowed: ++ ++.TP ++.B state-can-change ++The pin's state (connected/disconnected/selectable) can be modified. ++ ++.TP ++.B priority-can-change ++The pin's priority can be modified. This may apply to top-level priority ++or priority within parent-device relationships. ++ ++.TP ++.B direction-can-change ++The pin's direction (input/output) can be modified. ++ ++.PP ++Use ++.B dpll pin show ++to check which capabilities a pin supports before attempting configuration ++changes. ++ ++.SH EXIT STATUS ++.TP ++.B 0 ++Success ++.TP ++.B 1 ++General failure ++.TP ++.B 2 ++Invalid arguments or usage ++.TP ++.B 255 ++Netlink communication error ++ ++.SH NOTES ++.IP \[bu] 2 ++The DPLL subsystem requires kernel support (CONFIG_DPLL=y or m) ++.IP \[bu] ++Hardware support varies by device and driver ++.IP \[bu] ++Some operations require specific hardware capabilities ++.IP \[bu] ++Phase offset values are measured by hardware and cannot be set directly ++.IP \[bu] ++Changes to device/pin configuration may affect system clock synchronization ++ ++.SH SEE ALSO ++.BR ip (8), ++.BR devlink (8), ++.BR ethtool (8) ++ ++.PP ++Linux kernel documentation: ++.I Documentation/driver-api/dpll.rst ++ ++.PP ++Netlink specification: ++.I Documentation/netlink/specs/dpll.yaml ++ ++.SH AUTHOR ++dpll was written by Arkadiusz Kubalewski, Vadim Fedorenko, and others. ++ ++This manual page was written by Petr Oros. ++ ++.SH REPORTING BUGS ++Report bugs to +-- +2.52.0 + diff --git a/iproute.spec b/iproute.spec index 77088b9..16fd8df 100644 --- a/iproute.spec +++ b/iproute.spec @@ -8,6 +8,10 @@ Source0: https://kernel.org/pub/linux/utils/net/%{name}2/%{name}2-%{v Source1: rt_dsfield.deprecated %endif Source2: README.etc +Patch0: 0001-lib-Move-mnlg-to-lib-for-shared-use.patch +Patch1: 0002-lib-Add-str_to_bool-helper-function.patch +Patch2: 0003-uapi-import-dpll.h-from-last-sync-point.patch +Patch3: 0004-dpll-Add-dpll-command.patch License: GPL-2.0-or-later AND NIST-PD BuildRequires: bison @@ -129,7 +133,7 @@ fi %attr(644,root,root) %{_sysconfdir}/iproute2/* %exclude %{_sbindir}/tc %exclude %{_sbindir}/routel -%{_datadir}/bash-completion/completions/devlink +%{_datadir}/bash-completion/completions/* %files tc %license COPYING