From 879519402cb5116538faf91952180f557083f9c3 Mon Sep 17 00:00:00 2001 From: Rene Sapiens Date: Thu, 27 Aug 2026 17:18:47 -0700 Subject: [PATCH] thunderbolt: Add extended error counter support for software lane margining Add support for software margining extended error counters. Increase the results buffer size to accommodate the new dwords used for extended error counters for RX0, RX1 and RX2. Signed-off-by: Rene Sapiens Co-developed-by: Anurag Signed-off-by: Anurag Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 34 ++++++++++++++++++++++++++++++++-- drivers/thunderbolt/sb_regs.h | 2 ++ drivers/thunderbolt/tb.h | 2 +- drivers/thunderbolt/usb4.c | 5 +++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index ea80354c410a..43de2938d1e4 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -38,6 +38,7 @@ #define COUNTER_SET_LEN 3 #define HW_MARGINING_DWORDS 3 +#define SW_MARGINING_DWORDS 4 /* * USB4 spec doesn't specify dwell range, the range of 100 ms to 500 ms @@ -496,7 +497,7 @@ struct tb_margining { unsigned int gen; bool asym_rx; u32 caps[3]; - u32 results[3]; + u32 results[5]; enum usb4_margining_lane lanes; unsigned int min_ber_level; unsigned int max_ber_level; @@ -614,6 +615,11 @@ supports_optional_voltage_offset_range(const struct tb_margining *margining) return margining->caps[0] & USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT; } +static bool supports_extended_error_counter(const struct tb_margining *margining) +{ + return margining->caps[0] & USB4_MARGIN_CAP_0_EXT_ERR_COUNTER; +} + static ssize_t margining_ber_level_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) @@ -703,6 +709,8 @@ static int margining_caps_show(struct seq_file *s, void *not_used) ber_level_show(s, margining->max_ber_level); } else { seq_puts(s, "# hardware margining: no\n"); + seq_printf(s, "# extended error counter: %s\n", + str_yes_no(supports_extended_error_counter(margining))); } seq_printf(s, "# all lanes simultaneously: %s\n", @@ -1157,10 +1165,16 @@ static int margining_run_sw(struct tb_margining *margining, { u32 nsamples = margining->dwell_time / DWELL_SAMPLE_INTERVAL; int ret, i; + u32 dwords; if (params->error_counter != USB4_MARGIN_SW_ERROR_COUNTER_START) goto out_stop_or_clear; + if (supports_extended_error_counter(margining)) + dwords = SW_MARGINING_DWORDS; + else + dwords = 1; + ret = usb4_port_sw_margin(margining->port, margining->target, margining->index, params, margining->results); if (ret) @@ -1170,7 +1184,8 @@ static int margining_run_sw(struct tb_margining *margining, u32 errors = 0; ret = usb4_port_sw_margin_errors(margining->port, margining->target, - margining->index, &margining->results[1]); + margining->index, &margining->results[1], + dwords); if (ret) { tb_port_warn(margining->port, "failed to read margining error counters\n"); break; @@ -1450,18 +1465,30 @@ static int margining_results_show(struct seq_file *s, void *not_used) u32 lane_errors, result; seq_printf(s, "0x%08x\n", margining->results[1]); + if (supports_extended_error_counter(margining)) { + seq_printf(s, "0x%08x\n", margining->results[2]); + seq_printf(s, "0x%08x\n", margining->results[3]); + seq_printf(s, "0x%08x\n", margining->results[4]); + } + result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]); if (result == USB4_MARGINING_LANE_RX0 || result == USB4_MARGINING_LANE_ALL) { lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK, margining->results[1]); + if (supports_extended_error_counter(margining)) + lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, + margining->results[2]); seq_printf(s, "# lane 0 errors: %u\n", lane_errors); } if (result == USB4_MARGINING_LANE_RX1 || result == USB4_MARGINING_LANE_ALL) { lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK, margining->results[1]); + if (supports_extended_error_counter(margining)) + lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, + margining->results[3]); seq_printf(s, "# lane 1 errors: %u\n", lane_errors); } if (margining->asym_rx && @@ -1469,6 +1496,9 @@ static int margining_results_show(struct seq_file *s, void *not_used) result == USB4_MARGINING_LANE_ALL)) { lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK, margining->results[1]); + if (supports_extended_error_counter(margining)) + lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, + margining->results[4]); seq_printf(s, "# lane 2 errors: %u\n", lane_errors); } } diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h index 5391502a4b87..b801f3a694be 100644 --- a/drivers/thunderbolt/sb_regs.h +++ b/drivers/thunderbolt/sb_regs.h @@ -59,6 +59,7 @@ enum usb4_sb_opcode { #define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK GENMASK(18, 13) #define USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT BIT(19) #define USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK GENMASK(26, 20) +#define USB4_MARGIN_CAP_0_EXT_ERR_COUNTER BIT(28) #define USB4_MARGIN_CAP_1_MAX_VOLT_OFS_OPT_MASK GENMASK(7, 0) #define USB4_MARGIN_CAP_1_TIME_DESTR BIT(8) #define USB4_MARGIN_CAP_1_TIME_INDP_MASK GENMASK(10, 9) @@ -108,5 +109,6 @@ enum usb4_sb_opcode { #define USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK GENMASK(3, 0) #define USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK GENMASK(7, 4) #define USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK GENMASK(11, 8) +#define USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK GENMASK(15, 4) #endif diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index c112954ce3fd..e86845762e22 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1442,7 +1442,7 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, u8 index, const struct usb4_port_margining_params *params, u32 *results); int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, - u8 index, u32 *errors); + u8 index, u32 *errors, size_t dwords); int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index); int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 5fd4fe070f25..fe1b061f7055 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -1826,13 +1826,14 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, * @target: Sideband target * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER * @errors: Error metadata is copied here. + * @dwords: Number of dwords to read error counter values * * This reads back the software margining error counters from the port. * * Return: %0 on success, negative errno otherwise. */ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, - u8 index, u32 *errors) + u8 index, u32 *errors, size_t dwords) { int ret; @@ -1842,7 +1843,7 @@ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, return ret; return usb4_port_sb_read(port, target, index, USB4_SB_METADATA, errors, - sizeof(*errors)); + sizeof(*errors) * dwords); } static inline int usb4_port_retimer_op(struct tb_port *port, u8 index,