[RFC PATCH net-next v3 08/13] netlink: specs: add ethnl PHY_GET command set

From: Maxime Chevallier
Date: Fri Dec 01 2023 - 11:37:53 EST


The PHY_GET command, supporting both DUMP and GET operations, is used to
retrieve the list of PHYs connected to a netdevice, and get topology
information to know where exactly it sits on the physical link.

Add the netlink specs corresponding to that command, and bump the
ethtool-user.c|h autogenerated files.

Signed-off-by: Maxime Chevallier <maxime.chevallier@xxxxxxxxxxx>
---
V3: New patch

Documentation/netlink/specs/ethtool.yaml | 65 ++++++
tools/net/ynl/generated/ethtool-user.c | 257 +++++++++++++++++++++++
tools/net/ynl/generated/ethtool-user.h | 153 ++++++++++++++
3 files changed, 475 insertions(+)

diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 4e0790648913..280b090b5f7c 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -16,6 +16,11 @@ definitions:
name: stringset
type: enum
entries: []
+ -
+ name: phy-upstream-type
+ enum-name:
+ type: enum
+ entries: [ mac, phy ]

attribute-sets:
-
@@ -942,6 +947,45 @@ attribute-sets:
-
name: burst-tmr
type: u32
+ -
+ name: phy-upstream
+ attributes:
+ -
+ name: index
+ type: u32
+ -
+ name: sfp-name
+ type: string
+ -
+ name: phy
+ attributes:
+ -
+ name: header
+ type: nest
+ nested-attributes: header
+ -
+ name: index
+ type: u32
+ -
+ name: drvname
+ type: string
+ -
+ name: name
+ type: string
+ -
+ name: upstream-type
+ type: u8
+ enum: phy-upstream-type
+ -
+ name: upstream
+ type: nest
+ nested-attributes: phy-upstream
+ -
+ name: downstream-sfp-name
+ type: string
+ -
+ name: id
+ type: u32

operations:
enum-model: directional
@@ -1692,3 +1736,24 @@ operations:
name: mm-ntf
doc: Notification for change in MAC Merge configuration.
notify: mm-get
+ -
+ name: phy-get
+ doc: Get PHY devices attached to an interface
+
+ attribute-set: phy
+
+ do: &phy-get-op
+ request:
+ attributes:
+ - header
+ reply:
+ attributes:
+ - header
+ - index
+ - drvname
+ - name
+ - upstream-type
+ - upstream
+ - downstream-sfp-name
+ - id
+ dump: *phy-get-op
diff --git a/tools/net/ynl/generated/ethtool-user.c b/tools/net/ynl/generated/ethtool-user.c
index 295661eb3a3e..b63ba2d2e25e 100644
--- a/tools/net/ynl/generated/ethtool-user.c
+++ b/tools/net/ynl/generated/ethtool-user.c
@@ -59,6 +59,7 @@ static const char * const ethtool_op_strmap[] = {
[41] = "plca-ntf",
[ETHTOOL_MSG_MM_GET] = "mm-get",
[43] = "mm-ntf",
+ [ETHTOOL_MSG_PHY_GET] = "phy-get",
};

const char *ethtool_op_str(int op)
@@ -91,6 +92,18 @@ const char *ethtool_stringset_str(enum ethtool_stringset value)
return ethtool_stringset_strmap[value];
}

+static const char * const ethtool_phy_upstream_type_strmap[] = {
+ [0] = "mac",
+ [1] = "phy",
+};
+
+const char *ethtool_phy_upstream_type_str(int value)
+{
+ if (value < 0 || value >= (int)MNL_ARRAY_SIZE(ethtool_phy_upstream_type_strmap))
+ return NULL;
+ return ethtool_phy_upstream_type_strmap[value];
+}
+
/* Policies */
struct ynl_policy_attr ethtool_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
[ETHTOOL_A_HEADER_DEV_INDEX] = { .name = "dev-index", .type = YNL_PT_U32, },
@@ -154,6 +167,16 @@ struct ynl_policy_nest ethtool_mm_stat_nest = {
.table = ethtool_mm_stat_policy,
};

+struct ynl_policy_attr ethtool_phy_upstream_policy[ETHTOOL_A_PHY_UPSTREAM_MAX + 1] = {
+ [ETHTOOL_A_PHY_UPSTREAM_INDEX] = { .name = "index", .type = YNL_PT_U32, },
+ [ETHTOOL_A_PHY_UPSTREAM_SFP_NAME] = { .name = "sfp-name", .type = YNL_PT_NUL_STR, },
+};
+
+struct ynl_policy_nest ethtool_phy_upstream_nest = {
+ .max_attr = ETHTOOL_A_PHY_UPSTREAM_MAX,
+ .table = ethtool_phy_upstream_policy,
+};
+
struct ynl_policy_attr ethtool_cable_result_policy[ETHTOOL_A_CABLE_RESULT_MAX + 1] = {
[ETHTOOL_A_CABLE_RESULT_PAIR] = { .name = "pair", .type = YNL_PT_U8, },
[ETHTOOL_A_CABLE_RESULT_CODE] = { .name = "code", .type = YNL_PT_U8, },
@@ -667,6 +690,22 @@ struct ynl_policy_nest ethtool_mm_nest = {
.table = ethtool_mm_policy,
};

+struct ynl_policy_attr ethtool_phy_policy[ETHTOOL_A_PHY_MAX + 1] = {
+ [ETHTOOL_A_PHY_HEADER] = { .name = "header", .type = YNL_PT_NEST, .nest = &ethtool_header_nest, },
+ [ETHTOOL_A_PHY_INDEX] = { .name = "index", .type = YNL_PT_U32, },
+ [ETHTOOL_A_PHY_DRVNAME] = { .name = "drvname", .type = YNL_PT_NUL_STR, },
+ [ETHTOOL_A_PHY_NAME] = { .name = "name", .type = YNL_PT_NUL_STR, },
+ [ETHTOOL_A_PHY_UPSTREAM_TYPE] = { .name = "upstream-type", .type = YNL_PT_U8, },
+ [ETHTOOL_A_PHY_UPSTREAM] = { .name = "upstream", .type = YNL_PT_NEST, .nest = &ethtool_phy_upstream_nest, },
+ [ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME] = { .name = "downstream-sfp-name", .type = YNL_PT_NUL_STR, },
+ [ETHTOOL_A_PHY_ID] = { .name = "id", .type = YNL_PT_U32, },
+};
+
+struct ynl_policy_nest ethtool_phy_nest = {
+ .max_attr = ETHTOOL_A_PHY_MAX,
+ .table = ethtool_phy_policy,
+};
+
/* Common nested types */
void ethtool_header_free(struct ethtool_header *obj)
{
@@ -899,6 +938,42 @@ int ethtool_mm_stat_parse(struct ynl_parse_arg *yarg,
return 0;
}

+void ethtool_phy_upstream_free(struct ethtool_phy_upstream *obj)
+{
+ free(obj->sfp_name);
+}
+
+int ethtool_phy_upstream_parse(struct ynl_parse_arg *yarg,
+ const struct nlattr *nested)
+{
+ struct ethtool_phy_upstream *dst = yarg->data;
+ const struct nlattr *attr;
+
+ mnl_attr_for_each_nested(attr, nested) {
+ unsigned int type = mnl_attr_get_type(attr);
+
+ if (type == ETHTOOL_A_PHY_UPSTREAM_INDEX) {
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+ dst->_present.index = 1;
+ dst->index = mnl_attr_get_u32(attr);
+ } else if (type == ETHTOOL_A_PHY_UPSTREAM_SFP_NAME) {
+ unsigned int len;
+
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+
+ len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr));
+ dst->_present.sfp_name_len = len;
+ dst->sfp_name = malloc(len + 1);
+ memcpy(dst->sfp_name, mnl_attr_get_str(attr), len);
+ dst->sfp_name[len] = 0;
+ }
+ }
+
+ return 0;
+}
+
void ethtool_cable_result_free(struct ethtool_cable_result *obj)
{
}
@@ -6158,6 +6233,188 @@ int ethtool_mm_set(struct ynl_sock *ys, struct ethtool_mm_set_req *req)
return 0;
}

+/* ============== ETHTOOL_MSG_PHY_GET ============== */
+/* ETHTOOL_MSG_PHY_GET - do */
+void ethtool_phy_get_req_free(struct ethtool_phy_get_req *req)
+{
+ ethtool_header_free(&req->header);
+ free(req);
+}
+
+void ethtool_phy_get_rsp_free(struct ethtool_phy_get_rsp *rsp)
+{
+ ethtool_header_free(&rsp->header);
+ free(rsp->drvname);
+ free(rsp->name);
+ ethtool_phy_upstream_free(&rsp->upstream);
+ free(rsp->downstream_sfp_name);
+ free(rsp);
+}
+
+int ethtool_phy_get_rsp_parse(const struct nlmsghdr *nlh, void *data)
+{
+ struct ynl_parse_arg *yarg = data;
+ struct ethtool_phy_get_rsp *dst;
+ const struct nlattr *attr;
+ struct ynl_parse_arg parg;
+
+ dst = yarg->data;
+ parg.ys = yarg->ys;
+
+ mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) {
+ unsigned int type = mnl_attr_get_type(attr);
+
+ if (type == ETHTOOL_A_PHY_HEADER) {
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+ dst->_present.header = 1;
+
+ parg.rsp_policy = &ethtool_header_nest;
+ parg.data = &dst->header;
+ if (ethtool_header_parse(&parg, attr))
+ return MNL_CB_ERROR;
+ } else if (type == ETHTOOL_A_PHY_INDEX) {
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+ dst->_present.index = 1;
+ dst->index = mnl_attr_get_u32(attr);
+ } else if (type == ETHTOOL_A_PHY_DRVNAME) {
+ unsigned int len;
+
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+
+ len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr));
+ dst->_present.drvname_len = len;
+ dst->drvname = malloc(len + 1);
+ memcpy(dst->drvname, mnl_attr_get_str(attr), len);
+ dst->drvname[len] = 0;
+ } else if (type == ETHTOOL_A_PHY_NAME) {
+ unsigned int len;
+
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+
+ len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr));
+ dst->_present.name_len = len;
+ dst->name = malloc(len + 1);
+ memcpy(dst->name, mnl_attr_get_str(attr), len);
+ dst->name[len] = 0;
+ } else if (type == ETHTOOL_A_PHY_UPSTREAM_TYPE) {
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+ dst->_present.upstream_type = 1;
+ dst->upstream_type = mnl_attr_get_u8(attr);
+ } else if (type == ETHTOOL_A_PHY_UPSTREAM) {
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+ dst->_present.upstream = 1;
+
+ parg.rsp_policy = &ethtool_phy_upstream_nest;
+ parg.data = &dst->upstream;
+ if (ethtool_phy_upstream_parse(&parg, attr))
+ return MNL_CB_ERROR;
+ } else if (type == ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME) {
+ unsigned int len;
+
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+
+ len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr));
+ dst->_present.downstream_sfp_name_len = len;
+ dst->downstream_sfp_name = malloc(len + 1);
+ memcpy(dst->downstream_sfp_name, mnl_attr_get_str(attr), len);
+ dst->downstream_sfp_name[len] = 0;
+ } else if (type == ETHTOOL_A_PHY_ID) {
+ if (ynl_attr_validate(yarg, attr))
+ return MNL_CB_ERROR;
+ dst->_present.id = 1;
+ dst->id = mnl_attr_get_u32(attr);
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+struct ethtool_phy_get_rsp *
+ethtool_phy_get(struct ynl_sock *ys, struct ethtool_phy_get_req *req)
+{
+ struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };
+ struct ethtool_phy_get_rsp *rsp;
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = ynl_gemsg_start_req(ys, ys->family_id, ETHTOOL_MSG_PHY_GET, 1);
+ ys->req_policy = &ethtool_phy_nest;
+ yrs.yarg.rsp_policy = &ethtool_phy_nest;
+
+ if (req->_present.header)
+ ethtool_header_put(nlh, ETHTOOL_A_PHY_HEADER, &req->header);
+
+ rsp = calloc(1, sizeof(*rsp));
+ yrs.yarg.data = rsp;
+ yrs.cb = ethtool_phy_get_rsp_parse;
+ yrs.rsp_cmd = ETHTOOL_MSG_PHY_GET;
+
+ err = ynl_exec(ys, nlh, &yrs);
+ if (err < 0)
+ goto err_free;
+
+ return rsp;
+
+err_free:
+ ethtool_phy_get_rsp_free(rsp);
+ return NULL;
+}
+
+/* ETHTOOL_MSG_PHY_GET - dump */
+void ethtool_phy_get_list_free(struct ethtool_phy_get_list *rsp)
+{
+ struct ethtool_phy_get_list *next = rsp;
+
+ while ((void *)next != YNL_LIST_END) {
+ rsp = next;
+ next = rsp->next;
+
+ ethtool_header_free(&rsp->obj.header);
+ free(rsp->obj.drvname);
+ free(rsp->obj.name);
+ ethtool_phy_upstream_free(&rsp->obj.upstream);
+ free(rsp->obj.downstream_sfp_name);
+ free(rsp);
+ }
+}
+
+struct ethtool_phy_get_list *
+ethtool_phy_get_dump(struct ynl_sock *ys, struct ethtool_phy_get_req_dump *req)
+{
+ struct ynl_dump_state yds = {};
+ struct nlmsghdr *nlh;
+ int err;
+
+ yds.ys = ys;
+ yds.alloc_sz = sizeof(struct ethtool_phy_get_list);
+ yds.cb = ethtool_phy_get_rsp_parse;
+ yds.rsp_cmd = ETHTOOL_MSG_PHY_GET;
+ yds.rsp_policy = &ethtool_phy_nest;
+
+ nlh = ynl_gemsg_start_dump(ys, ys->family_id, ETHTOOL_MSG_PHY_GET, 1);
+ ys->req_policy = &ethtool_phy_nest;
+
+ if (req->_present.header)
+ ethtool_header_put(nlh, ETHTOOL_A_PHY_HEADER, &req->header);
+
+ err = ynl_exec_dump(ys, nlh, &yds);
+ if (err < 0)
+ goto free_list;
+
+ return yds.first;
+
+free_list:
+ ethtool_phy_get_list_free(yds.first);
+ return NULL;
+}
+
/* ETHTOOL_MSG_CABLE_TEST_NTF - event */
int ethtool_cable_test_ntf_rsp_parse(const struct nlmsghdr *nlh, void *data)
{
diff --git a/tools/net/ynl/generated/ethtool-user.h b/tools/net/ynl/generated/ethtool-user.h
index 97c079c0f332..59ebb0a1a09f 100644
--- a/tools/net/ynl/generated/ethtool-user.h
+++ b/tools/net/ynl/generated/ethtool-user.h
@@ -20,6 +20,7 @@ extern const struct ynl_family ynl_ethtool_family;
const char *ethtool_op_str(int op);
const char *ethtool_udp_tunnel_type_str(int value);
const char *ethtool_stringset_str(enum ethtool_stringset value);
+const char *ethtool_phy_upstream_type_str(int value);

/* Common nested types */
struct ethtool_header {
@@ -90,6 +91,16 @@ struct ethtool_mm_stat {
__u64 hold_count;
};

+struct ethtool_phy_upstream {
+ struct {
+ __u32 index:1;
+ __u32 sfp_name_len;
+ } _present;
+
+ __u32 index;
+ char *sfp_name;
+};
+
struct ethtool_cable_result {
struct {
__u32 pair:1;
@@ -6018,6 +6029,148 @@ ethtool_mm_set_req_set_tx_min_frag_size(struct ethtool_mm_set_req *req,
*/
int ethtool_mm_set(struct ynl_sock *ys, struct ethtool_mm_set_req *req);

+/* ============== ETHTOOL_MSG_PHY_GET ============== */
+/* ETHTOOL_MSG_PHY_GET - do */
+struct ethtool_phy_get_req {
+ struct {
+ __u32 header:1;
+ } _present;
+
+ struct ethtool_header header;
+};
+
+static inline struct ethtool_phy_get_req *ethtool_phy_get_req_alloc(void)
+{
+ return calloc(1, sizeof(struct ethtool_phy_get_req));
+}
+void ethtool_phy_get_req_free(struct ethtool_phy_get_req *req);
+
+static inline void
+ethtool_phy_get_req_set_header_dev_index(struct ethtool_phy_get_req *req,
+ __u32 dev_index)
+{
+ req->_present.header = 1;
+ req->header._present.dev_index = 1;
+ req->header.dev_index = dev_index;
+}
+static inline void
+ethtool_phy_get_req_set_header_dev_name(struct ethtool_phy_get_req *req,
+ const char *dev_name)
+{
+ free(req->header.dev_name);
+ req->header._present.dev_name_len = strlen(dev_name);
+ req->header.dev_name = malloc(req->header._present.dev_name_len + 1);
+ memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len);
+ req->header.dev_name[req->header._present.dev_name_len] = 0;
+}
+static inline void
+ethtool_phy_get_req_set_header_flags(struct ethtool_phy_get_req *req,
+ __u32 flags)
+{
+ req->_present.header = 1;
+ req->header._present.flags = 1;
+ req->header.flags = flags;
+}
+static inline void
+ethtool_phy_get_req_set_header_phy_index(struct ethtool_phy_get_req *req,
+ __u32 phy_index)
+{
+ req->_present.header = 1;
+ req->header._present.phy_index = 1;
+ req->header.phy_index = phy_index;
+}
+
+struct ethtool_phy_get_rsp {
+ struct {
+ __u32 header:1;
+ __u32 index:1;
+ __u32 drvname_len;
+ __u32 name_len;
+ __u32 upstream_type:1;
+ __u32 upstream:1;
+ __u32 downstream_sfp_name_len;
+ __u32 id:1;
+ } _present;
+
+ struct ethtool_header header;
+ __u32 index;
+ char *drvname;
+ char *name;
+ __u8 upstream_type;
+ struct ethtool_phy_upstream upstream;
+ char *downstream_sfp_name;
+ __u32 id;
+};
+
+void ethtool_phy_get_rsp_free(struct ethtool_phy_get_rsp *rsp);
+
+/*
+ * Get PHY devices attached to an interface
+ */
+struct ethtool_phy_get_rsp *
+ethtool_phy_get(struct ynl_sock *ys, struct ethtool_phy_get_req *req);
+
+/* ETHTOOL_MSG_PHY_GET - dump */
+struct ethtool_phy_get_req_dump {
+ struct {
+ __u32 header:1;
+ } _present;
+
+ struct ethtool_header header;
+};
+
+static inline struct ethtool_phy_get_req_dump *
+ethtool_phy_get_req_dump_alloc(void)
+{
+ return calloc(1, sizeof(struct ethtool_phy_get_req_dump));
+}
+void ethtool_phy_get_req_dump_free(struct ethtool_phy_get_req_dump *req);
+
+static inline void
+ethtool_phy_get_req_dump_set_header_dev_index(struct ethtool_phy_get_req_dump *req,
+ __u32 dev_index)
+{
+ req->_present.header = 1;
+ req->header._present.dev_index = 1;
+ req->header.dev_index = dev_index;
+}
+static inline void
+ethtool_phy_get_req_dump_set_header_dev_name(struct ethtool_phy_get_req_dump *req,
+ const char *dev_name)
+{
+ free(req->header.dev_name);
+ req->header._present.dev_name_len = strlen(dev_name);
+ req->header.dev_name = malloc(req->header._present.dev_name_len + 1);
+ memcpy(req->header.dev_name, dev_name, req->header._present.dev_name_len);
+ req->header.dev_name[req->header._present.dev_name_len] = 0;
+}
+static inline void
+ethtool_phy_get_req_dump_set_header_flags(struct ethtool_phy_get_req_dump *req,
+ __u32 flags)
+{
+ req->_present.header = 1;
+ req->header._present.flags = 1;
+ req->header.flags = flags;
+}
+static inline void
+ethtool_phy_get_req_dump_set_header_phy_index(struct ethtool_phy_get_req_dump *req,
+ __u32 phy_index)
+{
+ req->_present.header = 1;
+ req->header._present.phy_index = 1;
+ req->header.phy_index = phy_index;
+}
+
+struct ethtool_phy_get_list {
+ struct ethtool_phy_get_list *next;
+ struct ethtool_phy_get_rsp obj __attribute__((aligned(8)));
+};
+
+void ethtool_phy_get_list_free(struct ethtool_phy_get_list *rsp);
+
+struct ethtool_phy_get_list *
+ethtool_phy_get_dump(struct ynl_sock *ys, struct ethtool_phy_get_req_dump *req);
+
/* ETHTOOL_MSG_CABLE_TEST_NTF - event */
struct ethtool_cable_test_ntf_rsp {
struct {
--
2.42.0