[RFC PATCH net-next 2/2] ethtool: add PHY test framework (phy-test-get, phy-test-act)
From: Shubham Das <hidden>
Date: 2026-08-25 04:37:20
Subsystem:
networking drivers, networking [ethtool], networking [general], the rest, yaml netlink (ynl) · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn, Linus Torvalds, Donald Hunter
Add ETHTOOL_MSG_PHY_TEST_GET and ETHTOOL_MSG_PHY_TEST_ACT to configure and monitor PHY test functions on individual interface blocks and lanes. The framework uses the block identifier returned by ETHTOOL_MSG_INTF_CAPS_GET to select the hardware block under test. This allows userspace to target test operations at specific PHY, MAC, or module datapath components exposed by the driver. Supported test patterns: - PRBS: prbs7, prbs9, prbs11, prbs13, prbs15, prbs23, prbs31 - PRBS quaternary (PAM4): prbs13q, prbs31q - SSPRQ (Short Stress Pattern Random Quaternary) - Square wave: square-nrz, square-pam4 (JP03A) - TX linearity (LIN, IEEE 802.3 94.2.9.4) - Scrambled idle - 8b/10b comma: K28.5, K28.7 Supported test actions: - bert start / stop: bit error ratio measurement - inject-errors N: inject bit errors into the TX stream - active-tests: bitmask showing which tests are running Usage flow: 1. ethtool --get-intf-caps eth1 # discover testable blocks 2. ethtool --phy-test eth1 block 3 lane 0 tx-pattern prbs31 3. ethtool --phy-test eth2 block 3 lane 0 rx-pattern prbs31 4. ethtool --phy-test eth2 block 3 lane 0 bert start 5. ethtool --show-phy-test eth2 block 3 # read BERT counters 6. ethtool --phy-test eth2 block 3 lane 0 inject-errors 5 # verify checker 7. ethtool --show-phy-test eth2 block 3 # confirm error count increased 8. ethtool --phy-test eth2 block 3 lane 0 bert stop Signed-off-by: Shubham Das <redacted> --- Documentation/netlink/specs/ethtool.yaml | 107 +++++++++++ include/linux/ethtool.h | 43 +++++ .../uapi/linux/ethtool_netlink_generated.h | 56 ++++++ net/ethtool/Makefile | 2 +- net/ethtool/netlink.c | 14 ++ net/ethtool/netlink.h | 5 + net/ethtool/phytest.c | 171 ++++++++++++++++++ 7 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 net/ethtool/phytest.c
diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 77d77a896fb0..361eb3c66577 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml@@ -229,6 +229,36 @@ definitions: - fec - pma - pmd + - + name: phy-test-pattern + type: enum + doc: PHY test pattern types for PRBS generation/checking. + entries: + - off + - prbs7 + - prbs9 + - prbs11 + - prbs13 + - prbs15 + - prbs23 + - prbs31 + - ssprq + - prbs13q + - prbs31q + - square-nrz + - square-pam4 + - tx-linearity + - scrambled-idle + - k28-5 + - k28-7 + - + name: phy-test-action + type: enum + doc: BERT control actions. + entries: + - none + - start + - stop attribute-sets: -
@@ -1991,6 +2021,48 @@ attribute-sets: type: nest nested-attributes: intf-block multi-attr: true + - + name: phy-test + doc: PHY test configuration and status. + attributes: + - + name: header + type: nest + nested-attributes: header + - + name: block-id + type: u32 + - + name: lane + type: u32 + - + name: tx-pattern + type: u32 + enum: phy-test-pattern + - + name: rx-pattern + type: u32 + enum: phy-test-pattern + - + name: bert-action + type: u32 + enum: phy-test-action + - + name: inject-error-count + type: u32 + - + name: active-tests + type: u32 + doc: Bitmask of currently running tests (bit 0 = BERT). + - + name: checker-lock + type: u8 + - + name: error-count + type: u64 + - + name: total-bits-sent + type: u64 operations: enum-model: directional
@@ -2958,6 +3030,41 @@ operations: - header - blocks dump: *intf-caps-get-op + - + name: phy-test-get + doc: Get PHY test status (patterns, BERT counters). + attribute-set: phy-test + do: + request: + attributes: + - header + - block-id + - lane + reply: + attributes: + - header + - block-id + - lane + - tx-pattern + - rx-pattern + - active-tests + - checker-lock + - error-count + - total-bits-sent + - + name: phy-test-act + doc: Configure PHY test (set patterns, start/stop BERT, inject errors). + attribute-set: phy-test + do: + request: + attributes: + - header + - block-id + - lane + - tx-pattern + - rx-pattern + - bert-action + - inject-error-count mcast-groups: list:
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 3465f9c2f0aa..f9748e739f03 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h@@ -1244,6 +1244,45 @@ struct ethtool_intf_caps { struct ethtool_intf_block blocks[INTF_CAPS_MAX_BLOCKS]; }; +/* Bitmask of which ethtool_phy_test fields were explicitly specified */ +#define PHY_TEST_CMD_TX_PATTERN BIT(0) +#define PHY_TEST_CMD_RX_PATTERN BIT(1) +#define PHY_TEST_CMD_BERT_ACTION BIT(2) +#define PHY_TEST_CMD_INJECT_COUNT BIT(3) +#define PHY_TEST_CMD_LANE BIT(4) +#define PHY_TEST_CMD_BLOCK_ID BIT(5) + +/* Bitmask of currently active tests (read-only) */ +#define PHY_TEST_ACTIVE_BERT BIT(0) + +/** + * struct ethtool_phy_test - PHY test configuration and status + * @cmd: Bitmask of which fields are valid (PHY_TEST_CMD_*) + * @block_id: Block to operate on (from intf-caps-get) + * @lane: Lane number (0-based) + * @tx_pattern: TX pattern generator setting + * @rx_pattern: RX pattern checker setting + * @bert_action: BERT start/stop control + * @inject_error_count: Number of errors to inject + * @active_tests: Bitmask of running tests (PHY_TEST_ACTIVE_*) + * @checker_lock: RX checker lock status (read-only) + * @error_count: BERT error counter (read-only) + * @total_bits_sent: BERT total bits counter (read-only) + */ +struct ethtool_phy_test { + u32 cmd; + u32 block_id; + u32 lane; + enum phy_test_pattern tx_pattern; + enum phy_test_pattern rx_pattern; + enum phy_test_action bert_action; + u32 inject_error_count; + u32 active_tests; + u8 checker_lock; + u64 error_count; + u64 total_bits_sent; +}; + struct ethtool_ops { u32 supported_input_xfrm:8; u32 cap_link_lanes_supported:1;
@@ -1403,6 +1442,10 @@ struct ethtool_ops { void (*get_mm_stats)(struct net_device *dev, struct ethtool_mm_stats *stats); int (*get_intf_caps)(struct net_device *dev, struct ethtool_intf_caps *caps); + int (*get_phy_test)(struct net_device *dev, + struct ethtool_phy_test *test); + int (*set_phy_test)(struct net_device *dev, + struct ethtool_phy_test *test); }; int ethtool_check_ops(const struct ethtool_ops *ops);
diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h
index bd4cca1cc4fc..3102bb7ba337 100644
--- a/include/uapi/linux/ethtool_netlink_generated.h
+++ b/include/uapi/linux/ethtool_netlink_generated.h@@ -894,6 +894,8 @@ enum { ETHTOOL_MSG_RSS_DELETE_ACT, ETHTOOL_MSG_MSE_GET, ETHTOOL_MSG_INTF_CAPS_GET, + ETHTOOL_MSG_PHY_TEST_GET, + ETHTOOL_MSG_PHY_TEST_ACT, __ETHTOOL_MSG_USER_CNT, ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)
@@ -956,6 +958,7 @@ enum { ETHTOOL_MSG_RSS_DELETE_NTF, ETHTOOL_MSG_MSE_GET_REPLY, ETHTOOL_MSG_INTF_CAPS_GET_REPLY, + ETHTOOL_MSG_PHY_TEST_GET_REPLY, __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
@@ -1009,4 +1012,57 @@ enum { ETHTOOL_A_INTF_CAPS_MAX = (__ETHTOOL_A_INTF_CAPS_CNT - 1) }; +/* PHY test pattern types */ +enum phy_test_pattern { + PHY_TEST_PATTERN_OFF, + PHY_TEST_PATTERN_PRBS7, + PHY_TEST_PATTERN_PRBS9, + PHY_TEST_PATTERN_PRBS11, + PHY_TEST_PATTERN_PRBS13, + PHY_TEST_PATTERN_PRBS15, + PHY_TEST_PATTERN_PRBS23, + PHY_TEST_PATTERN_PRBS31, + PHY_TEST_PATTERN_SSPRQ, + PHY_TEST_PATTERN_PRBS13Q, + PHY_TEST_PATTERN_PRBS31Q, + PHY_TEST_PATTERN_SQUARE_NRZ, + PHY_TEST_PATTERN_SQUARE_PAM4, + PHY_TEST_PATTERN_TX_LINEARITY, + PHY_TEST_PATTERN_SCRAMBLED_IDLE, + PHY_TEST_PATTERN_K28_5, + PHY_TEST_PATTERN_K28_7, + + __PHY_TEST_PATTERN_COUNT, + PHY_TEST_PATTERN_MAX = (__PHY_TEST_PATTERN_COUNT - 1) +}; + +/* PHY test BERT actions */ +enum phy_test_action { + PHY_TEST_ACTION_NONE, + PHY_TEST_ACTION_START, + PHY_TEST_ACTION_STOP, + + __PHY_TEST_ACTION_COUNT, + PHY_TEST_ACTION_MAX = (__PHY_TEST_ACTION_COUNT - 1) +}; + +/* Attributes of phy-test-get / phy-test-act messages */ +enum { + ETHTOOL_A_PHY_TEST_UNSPEC, + ETHTOOL_A_PHY_TEST_HEADER, + ETHTOOL_A_PHY_TEST_BLOCK_ID, + ETHTOOL_A_PHY_TEST_LANE, + ETHTOOL_A_PHY_TEST_TX_PATTERN, + ETHTOOL_A_PHY_TEST_RX_PATTERN, + ETHTOOL_A_PHY_TEST_BERT_ACTION, + ETHTOOL_A_PHY_TEST_INJECT_ERROR_COUNT, + ETHTOOL_A_PHY_TEST_ACTIVE_TESTS, + ETHTOOL_A_PHY_TEST_CHECKER_LOCK, + ETHTOOL_A_PHY_TEST_ERROR_COUNT, + ETHTOOL_A_PHY_TEST_TOTAL_BITS_SENT, + + __ETHTOOL_A_PHY_TEST_CNT, + ETHTOOL_A_PHY_TEST_MAX = (__ETHTOOL_A_PHY_TEST_CNT - 1) +}; + #endif /* _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H */
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 773b21c7ab7c..7dd2c5624ced 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile@@ -9,4 +9,4 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \ channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \ tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \ module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o \ - phy.o tsconfig.o mse.o intf_caps.o + phy.o tsconfig.o mse.o intf_caps.o phytest.o
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index e90dae573452..56e5c811454b 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c@@ -432,6 +432,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { [ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops, [ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops, [ETHTOOL_MSG_INTF_CAPS_GET] = ðnl_intf_caps_request_ops, + [ETHTOOL_MSG_PHY_TEST_GET] = ðnl_phy_test_request_ops, }; static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -1582,6 +1583,19 @@ static const struct genl_ops ethtool_genl_ops[] = { .policy = ethnl_intf_caps_get_policy, .maxattr = ARRAY_SIZE(ethnl_intf_caps_get_policy) - 1, }, + { + .cmd = ETHTOOL_MSG_PHY_TEST_GET, + .doit = ethnl_default_doit, + .policy = ethnl_phy_test_get_policy, + .maxattr = ARRAY_SIZE(ethnl_phy_test_get_policy) - 1, + }, + { + .cmd = ETHTOOL_MSG_PHY_TEST_ACT, + .flags = GENL_UNS_ADMIN_PERM, + .doit = ethnl_act_phy_test, + .policy = ethnl_phy_test_act_policy, + .maxattr = ARRAY_SIZE(ethnl_phy_test_act_policy) - 1, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 32b16f81f453..1caba60d83c4 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h@@ -475,6 +475,7 @@ extern const struct ethnl_request_ops ethnl_phy_request_ops; extern const struct ethnl_request_ops ethnl_tsconfig_request_ops; extern const struct ethnl_request_ops ethnl_mse_request_ops; extern const struct ethnl_request_ops ethnl_intf_caps_request_ops; +extern const struct ethnl_request_ops ethnl_phy_test_request_ops; extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1]; extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
@@ -532,6 +533,10 @@ extern const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFIG_HEAD extern const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFIG_MAX + 1]; extern const struct nla_policy ethnl_mse_get_policy[ETHTOOL_A_MSE_HEADER + 1]; extern const struct nla_policy ethnl_intf_caps_get_policy[ETHTOOL_A_INTF_CAPS_HEADER + 1]; +extern const struct nla_policy ethnl_phy_test_get_policy[ETHTOOL_A_PHY_TEST_LANE + 1]; +extern const struct nla_policy ethnl_phy_test_act_policy[ETHTOOL_A_PHY_TEST_MAX + 1]; + +int ethnl_act_phy_test(struct sk_buff *skb, struct genl_info *info); int ethnl_set_features(struct sk_buff *skb, struct genl_info *info); int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);
diff --git a/net/ethtool/phytest.c b/net/ethtool/phytest.c
new file mode 100644
index 000000000000..dedcd036b7c2
--- /dev/null
+++ b/net/ethtool/phytest.c@@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "netlink.h" +#include "common.h" + +struct phy_test_req_info { + struct ethnl_req_info base; +}; + +struct phy_test_reply_data { + struct ethnl_reply_data base; + struct ethtool_phy_test test; +}; + +#define PHY_TEST_REPDATA(__reply_base) \ + container_of(__reply_base, struct phy_test_reply_data, base) + +/* PHY_TEST_GET */ + +const struct nla_policy ethnl_phy_test_get_policy[ETHTOOL_A_PHY_TEST_LANE + 1] = { + [ETHTOOL_A_PHY_TEST_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_PHY_TEST_BLOCK_ID] = { .type = NLA_U32 }, + [ETHTOOL_A_PHY_TEST_LANE] = { .type = NLA_U32 }, +}; + +static int phy_test_reply_size(const struct ethnl_req_info *req_base, + const struct ethnl_reply_data *reply_base) +{ + return nla_total_size(sizeof(u32)) + /* block_id */ + nla_total_size(sizeof(u32)) + /* lane */ + nla_total_size(sizeof(u32)) + /* tx_pattern */ + nla_total_size(sizeof(u32)) + /* rx_pattern */ + nla_total_size(sizeof(u32)) + /* active_tests */ + nla_total_size(sizeof(u8)) + /* checker_lock */ + nla_total_size(sizeof(u64)) + /* error_count */ + nla_total_size(sizeof(u64)); /* total_bits_sent */ +} + +static int phy_test_prepare_data(const struct ethnl_req_info *req_base, + struct ethnl_reply_data *reply_base, + const struct genl_info *info) +{ + struct phy_test_reply_data *data = PHY_TEST_REPDATA(reply_base); + struct net_device *dev = reply_base->dev; + struct nlattr **tb = info->attrs; + + if (!dev->ethtool_ops->get_phy_test) + return -EOPNOTSUPP; + + memset(&data->test, 0, sizeof(data->test)); + + if (tb[ETHTOOL_A_PHY_TEST_BLOCK_ID]) { + data->test.block_id = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_BLOCK_ID]); + data->test.cmd |= PHY_TEST_CMD_BLOCK_ID; + } + if (tb[ETHTOOL_A_PHY_TEST_LANE]) { + data->test.lane = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_LANE]); + data->test.cmd |= PHY_TEST_CMD_LANE; + } + + return dev->ethtool_ops->get_phy_test(dev, &data->test); +} + +static int phy_test_fill_reply(struct sk_buff *skb, + const struct ethnl_req_info *req_base, + const struct ethnl_reply_data *reply_base) +{ + const struct phy_test_reply_data *data = PHY_TEST_REPDATA(reply_base); + const struct ethtool_phy_test *t = &data->test; + + if (nla_put_u32(skb, ETHTOOL_A_PHY_TEST_BLOCK_ID, t->block_id) || + nla_put_u32(skb, ETHTOOL_A_PHY_TEST_LANE, t->lane) || + nla_put_u32(skb, ETHTOOL_A_PHY_TEST_TX_PATTERN, t->tx_pattern) || + nla_put_u32(skb, ETHTOOL_A_PHY_TEST_RX_PATTERN, t->rx_pattern) || + nla_put_u32(skb, ETHTOOL_A_PHY_TEST_ACTIVE_TESTS, + t->active_tests) || + nla_put_u8(skb, ETHTOOL_A_PHY_TEST_CHECKER_LOCK, t->checker_lock) || + nla_put_u64_64bit(skb, ETHTOOL_A_PHY_TEST_ERROR_COUNT, + t->error_count, ETHTOOL_A_PHY_TEST_UNSPEC) || + nla_put_u64_64bit(skb, ETHTOOL_A_PHY_TEST_TOTAL_BITS_SENT, + t->total_bits_sent, ETHTOOL_A_PHY_TEST_UNSPEC)) + return -EMSGSIZE; + + return 0; +} + +const struct ethnl_request_ops ethnl_phy_test_request_ops = { + .request_cmd = ETHTOOL_MSG_PHY_TEST_GET, + .reply_cmd = ETHTOOL_MSG_PHY_TEST_GET_REPLY, + .hdr_attr = ETHTOOL_A_PHY_TEST_HEADER, + .req_info_size = sizeof(struct phy_test_req_info), + .reply_data_size = sizeof(struct phy_test_reply_data), + .prepare_data = phy_test_prepare_data, + .reply_size = phy_test_reply_size, + .fill_reply = phy_test_fill_reply, +}; + +/* PHY_TEST_ACT */ + +const struct nla_policy ethnl_phy_test_act_policy[ETHTOOL_A_PHY_TEST_MAX + 1] = { + [ETHTOOL_A_PHY_TEST_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_PHY_TEST_BLOCK_ID] = { .type = NLA_U32 }, + [ETHTOOL_A_PHY_TEST_LANE] = { .type = NLA_U32 }, + [ETHTOOL_A_PHY_TEST_TX_PATTERN] = { .type = NLA_U32 }, + [ETHTOOL_A_PHY_TEST_RX_PATTERN] = { .type = NLA_U32 }, + [ETHTOOL_A_PHY_TEST_BERT_ACTION] = { .type = NLA_U32 }, + [ETHTOOL_A_PHY_TEST_INJECT_ERROR_COUNT] = { .type = NLA_U32 }, +}; + +int ethnl_act_phy_test(struct sk_buff *skb, struct genl_info *info) +{ + struct ethnl_req_info req_info = {}; + struct nlattr **tb = info->attrs; + struct ethtool_phy_test test = {}; + struct net_device *dev; + int ret; + + ret = ethnl_parse_header_dev_get(&req_info, + tb[ETHTOOL_A_PHY_TEST_HEADER], + genl_info_net(info), info->extack, + true); + if (ret < 0) + return ret; + + dev = req_info.dev; + + if (!dev->ethtool_ops->set_phy_test) { + ret = -EOPNOTSUPP; + goto out_dev; + } + + if (tb[ETHTOOL_A_PHY_TEST_BLOCK_ID]) { + test.block_id = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_BLOCK_ID]); + test.cmd |= PHY_TEST_CMD_BLOCK_ID; + } + if (tb[ETHTOOL_A_PHY_TEST_LANE]) { + test.lane = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_LANE]); + test.cmd |= PHY_TEST_CMD_LANE; + } + if (tb[ETHTOOL_A_PHY_TEST_TX_PATTERN]) { + test.tx_pattern = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_TX_PATTERN]); + test.cmd |= PHY_TEST_CMD_TX_PATTERN; + } + if (tb[ETHTOOL_A_PHY_TEST_RX_PATTERN]) { + test.rx_pattern = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_RX_PATTERN]); + test.cmd |= PHY_TEST_CMD_RX_PATTERN; + } + if (tb[ETHTOOL_A_PHY_TEST_BERT_ACTION]) { + test.bert_action = nla_get_u32(tb[ETHTOOL_A_PHY_TEST_BERT_ACTION]); + test.cmd |= PHY_TEST_CMD_BERT_ACTION; + } + if (tb[ETHTOOL_A_PHY_TEST_INJECT_ERROR_COUNT]) { + test.inject_error_count = + nla_get_u32(tb[ETHTOOL_A_PHY_TEST_INJECT_ERROR_COUNT]); + test.cmd |= PHY_TEST_CMD_INJECT_COUNT; + } + + rtnl_lock(); + ret = ethnl_ops_begin(dev); + if (ret < 0) + goto out_rtnl; + + ret = dev->ethtool_ops->set_phy_test(dev, &test); + ethnl_ops_complete(dev); + +out_rtnl: + rtnl_unlock(); +out_dev: + ethnl_parse_header_dev_put(&req_info); + return ret; +}
--
2.25.1