mirror of
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git
synced 2026-01-26 14:13:24 +00:00
Add a new userspace tool for managing and monitoring DPLL devices via the
Linux kernel DPLL subsystem. The tool uses libmnl for netlink communication
and provides a complete interface for device and pin configuration.
The tool supports:
- Device management: enumerate devices, query capabilities (lock status,
temperature, supported modes, clock quality levels), configure phase-offset
monitoring and averaging
- Pin management: enumerate pins with hierarchical relationships, configure
frequencies (including esync), phase adjustments, priorities, states, and
directions
- Complex topologies: handle parent-device and parent-pin relationships,
reference synchronization tracking, multi-attribute queries (frequency
ranges, capabilities)
- ID resolution: query device/pin IDs by various attributes (module-name,
clock-id, board-label, type)
- Monitoring: real-time display of device and pin state changes via netlink
multicast notifications
- Output formats: both human-readable and JSON output (with pretty-print
support)
The tool belongs in iproute2 as DPLL devices are tightly integrated with
network interfaces - modern NICs provide hardware clock synchronization
support. The DPLL subsystem uses the same netlink infrastructure as other
networking subsystems, and the tool follows established iproute2 patterns
for command structure, output formatting, and error handling.
Example usage:
# dpll device show
# dpll device id-get module-name ice
# dpll device set id 0 phase-offset-monitor enable
# dpll pin show
# dpll pin set id 0 frequency 10000000
# dpll pin set id 13 parent-device 0 state connected prio 10
# dpll pin set id 0 reference-sync 1 state connected
# dpll monitor
# dpll -j -p device show
Testing notes:
Tested on real hardware with ice and zl3073x drivers. All commands work
(device show/set/id-get, pin show/set/id-get, monitor). JSON output was
carefully compared with cli.py - the tools are interchangeable.
v2:
- Added testing notes
- Added MAINTAINERS entry
- Removed unused -n parameter from man page
v3:
- Use shared mnlg and str_to_bool helpers from lib
- Use str_num_map for bidirectional string/enum mapping
- Remove unnecessary else after return
- Remove misleading "legacy" comments
- Simplify DPLL_PR_MULTI_ENUM_STR macro
- Convert json_output to global json variable
- Use appropriate mnl helpers with proper type casting to respect signed integer data types
- Use DPLL_PR_INT_FMT for phase-adjust-gran to respect signed integer type
- Remove dpll_link from Makefile (mistake in v2)
v4:
- Replace DPLL_PR_MULTI_ENUM_STR macro with dpll_pr_multi_enum_str() function
- Replace pr_out("\n") with print_nl() for one-line mode support
- Remove all is_json_context() code splitting, use PRINT_FP/PRINT_JSON/PRINT_ANY appropriately
- Add dpll_pr_freq_range() helper function to reduce code duplication
v5
- Fix checkpatch warnings
- Use structure initialization instead of memset
- Use sigemptyset() for proper signal mask initialization
- Remove redundant if (json) checks around JSON functions
- Use signalfd for signal handling in monitor
- Set netlink socket to non-blocking in monitor
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Co-developed-by: Ivan Vecera <ivecera@redhat.com>
Signed-off-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Signed-off-by: David Ahern <dsahern@kernel.org>
1952 lines
49 KiB
C
1952 lines
49 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* dpll.c DPLL tool
|
|
*
|
|
* Authors: Petr Oros <poros@redhat.com>
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <inttypes.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/signalfd.h>
|
|
#include <unistd.h>
|
|
#include <linux/dpll.h>
|
|
#include <linux/genetlink.h>
|
|
#include <libmnl/libmnl.h>
|
|
|
|
#include <mnlg.h>
|
|
#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;
|
|
}
|