Skip to content

Commit 10904df

Browse files
r-kannappanwesteri
authored andcommitted
thunderbolt: Improve software receiver lane margining
USB4 specification defines the metadata needed to perform software margining, as well as the necessary steps which include waiting for dwell time. - Add dwell_time attribute to set the wait time while performing margining and checking for link errors. - Add error_counter attribute to configure error counter prior to margining test. - Add voltage_time_offset attribute to set the voltage or time offset steps before performing the software margining test. - Perform software margining test for dwell duration, break if there are link errors, stop the clocks and provide results. Below is a minimalistic example how this can be used. Note these values are just examples. The exact values in practice depend on host specific capabilities and the type of measurement to be performed. # cd /sys/kernel/debug/thunderbolt/ROUTER/portX/margining/ # echo software > mode # echo 400 > dwell_time # echo 1 > run As usual the results attribute contains the results of a succesfull run. Signed-off-by: R Kannappan <[email protected]> Co-developed-by: Rene Sapiens <[email protected]> Signed-off-by: Rene Sapiens <[email protected]> Co-developed-by: Aapo Vienamo <[email protected]> Signed-off-by: Aapo Vienamo <[email protected]> Signed-off-by: Mika Westerberg <[email protected]>
1 parent 9fafd46 commit 10904df

File tree

4 files changed

+290
-16
lines changed

4 files changed

+290
-16
lines changed

drivers/thunderbolt/debugfs.c

Lines changed: 279 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <linux/bitfield.h>
1111
#include <linux/debugfs.h>
12+
#include <linux/delay.h>
1213
#include <linux/pm_runtime.h>
1314
#include <linux/uaccess.h>
1415

@@ -34,6 +35,14 @@
3435

3536
#define COUNTER_SET_LEN 3
3637

38+
/*
39+
* USB4 spec doesn't specify dwell range, the range of 100 ms to 500 ms
40+
* probed to give good results.
41+
*/
42+
#define MIN_DWELL_TIME 100 /* ms */
43+
#define MAX_DWELL_TIME 500 /* ms */
44+
#define DWELL_SAMPLE_INTERVAL 10
45+
3746
/* Sideband registers and their sizes as defined in the USB4 spec */
3847
struct sb_reg {
3948
unsigned int reg;
@@ -399,6 +408,9 @@ static ssize_t retimer_sb_regs_write(struct file *file,
399408
* range (in mV).
400409
* @time_steps: Number of time margin steps
401410
* @max_time_offset: Maximum time margin offset (in mUI)
411+
* @voltage_time_offset: Offset for voltage / time for software margining
412+
* @dwell_time: Dwell time for software margining (in ms)
413+
* @error_counter: Error counter operation for software margining
402414
* @optional_voltage_offset_range: Enable optional extended voltage range
403415
* @software: %true if software margining is used instead of hardware
404416
* @time: %true if time margining is used instead of voltage
@@ -422,12 +434,33 @@ struct tb_margining {
422434
unsigned int max_voltage_offset_optional_range;
423435
unsigned int time_steps;
424436
unsigned int max_time_offset;
437+
unsigned int voltage_time_offset;
438+
unsigned int dwell_time;
439+
enum usb4_margin_sw_error_counter error_counter;
425440
bool optional_voltage_offset_range;
426441
bool software;
427442
bool time;
428443
bool right_high;
429444
};
430445

446+
static int margining_modify_error_counter(struct tb_margining *margining,
447+
u32 lanes, enum usb4_margin_sw_error_counter error_counter)
448+
{
449+
struct usb4_port_margining_params params = { 0 };
450+
struct tb_port *port = margining->port;
451+
u32 result;
452+
453+
if (error_counter != USB4_MARGIN_SW_ERROR_COUNTER_CLEAR &&
454+
error_counter != USB4_MARGIN_SW_ERROR_COUNTER_STOP)
455+
return -EOPNOTSUPP;
456+
457+
params.error_counter = error_counter;
458+
params.lanes = lanes;
459+
460+
return usb4_port_sw_margin(port, margining->target, margining->index,
461+
&params, &result);
462+
}
463+
431464
static bool supports_software(const struct tb_margining *margining)
432465
{
433466
return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
@@ -689,9 +722,165 @@ static int margining_lanes_show(struct seq_file *s, void *not_used)
689722
DEBUGFS_ATTR_RW(margining_lanes);
690723

691724
static ssize_t
692-
margining_optional_voltage_offset_write(struct file *file,
693-
const char __user *user_buf,
694-
size_t count, loff_t *ppos)
725+
margining_voltage_time_offset_write(struct file *file,
726+
const char __user *user_buf,
727+
size_t count, loff_t *ppos)
728+
{
729+
struct seq_file *s = file->private_data;
730+
struct tb_margining *margining = s->private;
731+
struct tb *tb = margining->port->sw->tb;
732+
unsigned int max_margin;
733+
unsigned int val;
734+
int ret;
735+
736+
ret = kstrtouint_from_user(user_buf, count, 10, &val);
737+
if (ret)
738+
return ret;
739+
740+
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
741+
if (!margining->software)
742+
return -EOPNOTSUPP;
743+
744+
if (margining->time)
745+
max_margin = margining->time_steps;
746+
else
747+
if (margining->optional_voltage_offset_range)
748+
max_margin = margining->voltage_steps_optional_range;
749+
else
750+
max_margin = margining->voltage_steps;
751+
752+
margining->voltage_time_offset = clamp(val, 0, max_margin);
753+
}
754+
755+
return count;
756+
}
757+
758+
static int margining_voltage_time_offset_show(struct seq_file *s,
759+
void *not_used)
760+
{
761+
const struct tb_margining *margining = s->private;
762+
struct tb *tb = margining->port->sw->tb;
763+
764+
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
765+
if (!margining->software)
766+
return -EOPNOTSUPP;
767+
768+
seq_printf(s, "%d\n", margining->voltage_time_offset);
769+
}
770+
771+
return 0;
772+
}
773+
DEBUGFS_ATTR_RW(margining_voltage_time_offset);
774+
775+
static ssize_t
776+
margining_error_counter_write(struct file *file, const char __user *user_buf,
777+
size_t count, loff_t *ppos)
778+
{
779+
enum usb4_margin_sw_error_counter error_counter;
780+
struct seq_file *s = file->private_data;
781+
struct tb_margining *margining = s->private;
782+
struct tb *tb = margining->port->sw->tb;
783+
char *buf;
784+
785+
buf = validate_and_copy_from_user(user_buf, &count);
786+
if (IS_ERR(buf))
787+
return PTR_ERR(buf);
788+
789+
buf[count - 1] = '\0';
790+
791+
if (!strcmp(buf, "nop"))
792+
error_counter = USB4_MARGIN_SW_ERROR_COUNTER_NOP;
793+
else if (!strcmp(buf, "clear"))
794+
error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR;
795+
else if (!strcmp(buf, "start"))
796+
error_counter = USB4_MARGIN_SW_ERROR_COUNTER_START;
797+
else if (!strcmp(buf, "stop"))
798+
error_counter = USB4_MARGIN_SW_ERROR_COUNTER_STOP;
799+
else
800+
return -EINVAL;
801+
802+
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
803+
if (!margining->software)
804+
return -EOPNOTSUPP;
805+
806+
margining->error_counter = error_counter;
807+
}
808+
809+
return count;
810+
}
811+
812+
static int margining_error_counter_show(struct seq_file *s, void *not_used)
813+
{
814+
const struct tb_margining *margining = s->private;
815+
struct tb *tb = margining->port->sw->tb;
816+
817+
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
818+
if (!margining->software)
819+
return -EOPNOTSUPP;
820+
821+
switch (margining->error_counter) {
822+
case USB4_MARGIN_SW_ERROR_COUNTER_NOP:
823+
seq_puts(s, "[nop] clear start stop\n");
824+
break;
825+
case USB4_MARGIN_SW_ERROR_COUNTER_CLEAR:
826+
seq_puts(s, "nop [clear] start stop\n");
827+
break;
828+
case USB4_MARGIN_SW_ERROR_COUNTER_START:
829+
seq_puts(s, "nop clear [start] stop\n");
830+
break;
831+
case USB4_MARGIN_SW_ERROR_COUNTER_STOP:
832+
seq_puts(s, "nop clear start [stop]\n");
833+
break;
834+
}
835+
}
836+
837+
return 0;
838+
}
839+
DEBUGFS_ATTR_RW(margining_error_counter);
840+
841+
static ssize_t
842+
margining_dwell_time_write(struct file *file, const char __user *user_buf,
843+
size_t count, loff_t *ppos)
844+
{
845+
struct seq_file *s = file->private_data;
846+
struct tb_margining *margining = s->private;
847+
struct tb *tb = margining->port->sw->tb;
848+
unsigned int val;
849+
int ret;
850+
851+
ret = kstrtouint_from_user(user_buf, count, 10, &val);
852+
if (ret)
853+
return ret;
854+
855+
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
856+
if (!margining->software)
857+
return -EOPNOTSUPP;
858+
859+
margining->dwell_time = clamp(val, MIN_DWELL_TIME, MAX_DWELL_TIME);
860+
}
861+
862+
return count;
863+
}
864+
865+
static int margining_dwell_time_show(struct seq_file *s, void *not_used)
866+
{
867+
struct tb_margining *margining = s->private;
868+
struct tb *tb = margining->port->sw->tb;
869+
870+
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
871+
if (!margining->software)
872+
return -EOPNOTSUPP;
873+
874+
seq_printf(s, "%d\n", margining->dwell_time);
875+
}
876+
877+
return 0;
878+
}
879+
DEBUGFS_ATTR_RW(margining_dwell_time);
880+
881+
static ssize_t
882+
margining_optional_voltage_offset_write(struct file *file, const char __user *user_buf,
883+
size_t count, loff_t *ppos)
695884
{
696885
struct seq_file *s = file->private_data;
697886
struct tb_margining *margining = s->private;
@@ -796,6 +985,51 @@ static int margining_mode_show(struct seq_file *s, void *not_used)
796985
}
797986
DEBUGFS_ATTR_RW(margining_mode);
798987

988+
static int margining_run_sw(struct tb_margining *margining,
989+
struct usb4_port_margining_params *params)
990+
{
991+
u32 nsamples = margining->dwell_time / DWELL_SAMPLE_INTERVAL;
992+
int ret, i;
993+
994+
ret = usb4_port_sw_margin(margining->port, margining->target, margining->index,
995+
params, margining->results);
996+
if (ret)
997+
goto out_stop;
998+
999+
for (i = 0; i <= nsamples; i++) {
1000+
u32 errors = 0;
1001+
1002+
ret = usb4_port_sw_margin_errors(margining->port, margining->target,
1003+
margining->index, &margining->results[1]);
1004+
if (ret)
1005+
break;
1006+
1007+
if (margining->lanes == USB4_MARGIN_SW_LANE_0)
1008+
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
1009+
margining->results[1]);
1010+
else if (margining->lanes == USB4_MARGIN_SW_LANE_1)
1011+
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
1012+
margining->results[1]);
1013+
else if (margining->lanes == USB4_MARGIN_SW_ALL_LANES)
1014+
errors = margining->results[1];
1015+
1016+
/* Any errors stop the test */
1017+
if (errors)
1018+
break;
1019+
1020+
fsleep(DWELL_SAMPLE_INTERVAL * USEC_PER_MSEC);
1021+
}
1022+
1023+
out_stop:
1024+
/*
1025+
* Stop the counters but don't clear them to allow the
1026+
* different error counter configurations.
1027+
*/
1028+
margining_modify_error_counter(margining, margining->lanes,
1029+
USB4_MARGIN_SW_ERROR_COUNTER_STOP);
1030+
return ret;
1031+
}
1032+
7991033
static int margining_run_write(void *data, u64 val)
8001034
{
8011035
struct tb_margining *margining = data;
@@ -836,11 +1070,15 @@ static int margining_run_write(void *data, u64 val)
8361070
clx = ret;
8371071
}
8381072

1073+
/* Clear the results */
1074+
memset(margining->results, 0, sizeof(margining->results));
1075+
8391076
if (margining->software) {
8401077
struct usb4_port_margining_params params = {
8411078
.error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR,
8421079
.lanes = margining->lanes,
8431080
.time = margining->time,
1081+
.voltage_time_offset = margining->voltage_time_offset,
8441082
.right_high = margining->right_high,
8451083
.optional_voltage_offset_range = margining->optional_voltage_offset_range,
8461084
};
@@ -850,14 +1088,7 @@ static int margining_run_write(void *data, u64 val)
8501088
margining->time ? "time" : "voltage", dev_name(dev),
8511089
margining->lanes);
8521090

853-
ret = usb4_port_sw_margin(port, margining->target, margining->index, &params,
854-
&margining->results[0]);
855-
if (ret)
856-
goto out_clx;
857-
858-
ret = usb4_port_sw_margin_errors(port, margining->target,
859-
margining->index,
860-
&margining->results[0]);
1091+
ret = margining_run_sw(margining, &params);
8611092
} else {
8621093
struct usb4_port_margining_params params = {
8631094
.ber_level = margining->ber_level,
@@ -867,10 +1098,6 @@ static int margining_run_write(void *data, u64 val)
8671098
.optional_voltage_offset_range = margining->optional_voltage_offset_range,
8681099
};
8691100

870-
/* Clear the results */
871-
margining->results[0] = 0;
872-
margining->results[1] = 0;
873-
8741101
tb_port_dbg(port,
8751102
"running hardware %s lane margining for %s lanes %u\n",
8761103
margining->time ? "time" : "voltage", dev_name(dev),
@@ -880,7 +1107,6 @@ static int margining_run_write(void *data, u64 val)
8801107
margining->results);
8811108
}
8821109

883-
out_clx:
8841110
if (down_sw)
8851111
tb_switch_clx_enable(down_sw, clx);
8861112
out_unlock:
@@ -909,6 +1135,13 @@ static ssize_t margining_results_write(struct file *file,
9091135
margining->results[0] = 0;
9101136
margining->results[1] = 0;
9111137

1138+
if (margining->software) {
1139+
/* Clear the error counters */
1140+
margining_modify_error_counter(margining,
1141+
USB4_MARGIN_SW_ALL_LANES,
1142+
USB4_MARGIN_SW_ERROR_COUNTER_CLEAR);
1143+
}
1144+
9121145
mutex_unlock(&tb->lock);
9131146
return count;
9141147
}
@@ -998,6 +1231,24 @@ static int margining_results_show(struct seq_file *s, void *not_used)
9981231
voltage_margin_show(s, margining, val);
9991232
}
10001233
}
1234+
} else {
1235+
u32 lane_errors, result;
1236+
1237+
seq_printf(s, "0x%08x\n", margining->results[1]);
1238+
result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]);
1239+
1240+
if (result == USB4_MARGIN_SW_LANE_0 ||
1241+
result == USB4_MARGIN_SW_ALL_LANES) {
1242+
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
1243+
margining->results[1]);
1244+
seq_printf(s, "# lane 0 errors: %u\n", lane_errors);
1245+
}
1246+
if (result == USB4_MARGIN_SW_LANE_1 ||
1247+
result == USB4_MARGIN_SW_ALL_LANES) {
1248+
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
1249+
margining->results[1]);
1250+
seq_printf(s, "# lane 1 errors: %u\n", lane_errors);
1251+
}
10011252
}
10021253

10031254
mutex_unlock(&tb->lock);
@@ -1211,9 +1462,21 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
12111462
debugfs_create_file("margin", 0600, dir, margining,
12121463
&margining_margin_fops);
12131464

1465+
margining->error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR;
1466+
margining->dwell_time = MIN_DWELL_TIME;
1467+
12141468
if (supports_optional_voltage_offset_range(margining))
12151469
debugfs_create_file("optional_voltage_offset", DEBUGFS_MODE, dir, margining,
12161470
&margining_optional_voltage_offset_fops);
1471+
1472+
if (supports_software(margining)) {
1473+
debugfs_create_file("voltage_time_offset", DEBUGFS_MODE, dir, margining,
1474+
&margining_voltage_time_offset_fops);
1475+
debugfs_create_file("error_counter", DEBUGFS_MODE, dir, margining,
1476+
&margining_error_counter_fops);
1477+
debugfs_create_file("dwell_time", DEBUGFS_MODE, dir, margining,
1478+
&margining_dwell_time_fops);
1479+
}
12171480
return margining;
12181481
}
12191482

0 commit comments

Comments
 (0)