diff options
Diffstat (limited to '')
-rw-r--r-- | lmr/lmr.h | 108 | ||||
-rw-r--r-- | lmr/margin.c | 80 | ||||
-rw-r--r-- | lmr/margin_args.c | 302 | ||||
-rw-r--r-- | lmr/margin_hw.c | 77 | ||||
-rw-r--r-- | lmr/margin_log.c | 25 | ||||
-rw-r--r-- | lmr/margin_results.c | 322 |
6 files changed, 703 insertions, 211 deletions
@@ -1,7 +1,7 @@ /* * The PCI Utilities -- Margining utility main header * - * Copyright (c) 2023 KNS Group LLC (YADRO) + * Copyright (c) 2023-2024 KNS Group LLC (YADRO) * * Can be freely distributed and used under the terms of the GNU GPL v2+. * @@ -15,19 +15,17 @@ #include "pciutils.h" -#define MARGIN_STEP_MS 1000 - -#define MARGIN_TIM_MIN 20 -#define MARGIN_TIM_RECOMMEND 30 -#define MARGIN_VOLT_MIN 50 - enum margin_hw { MARGIN_HW_DEFAULT, MARGIN_ICE_LAKE_RC }; +// in ps +static const double margin_ui[] = { 62.5, 31.25 }; + /* PCI Device wrapper for margining functions */ struct margin_dev { struct pci_dev *dev; int lmr_cap_addr; - u8 width; + u8 neg_width; + u8 max_width; u8 retimers_n; u8 link_speed; @@ -39,11 +37,6 @@ struct margin_dev { bool hawd; // Hardware Autonomous Width Disable }; -struct margin_link { - struct margin_dev down_port; - struct margin_dev up_port; -}; - /* Specification Revision 5.0 Table 8-11 */ struct margin_params { bool ind_error_sampler; @@ -95,7 +88,7 @@ enum margin_test_status { /* All lanes Receiver results */ struct margin_results { - u8 recvn; // Receiver Number + u8 recvn; // Receiver Number; from 1 to 6 struct margin_params params; bool lane_reversal; u8 link_speed; @@ -104,7 +97,7 @@ struct margin_results { /* Used to convert steps to physical quantity. Calculated from MaxOffset and NumSteps */ - double tim_coef; + double tim_coef; // from steps to % UI double volt_coef; bool tim_off_reported; @@ -115,31 +108,56 @@ struct margin_results { }; /* pcilmr arguments */ -struct margin_args { + +// Common args +struct margin_com_args { + u8 error_limit; // [0; 63] + bool run_margin; // Or print params only + u8 verbosity; // 0 - basic; + // 1 - add info about remaining time and lanes in progress during margining + u64 steps_utility; // For ETA logging + bool save_csv; + char *dir_for_csv; + u8 dwell_time; +}; + +struct margin_recv_args { + // Grading options + struct { + bool valid; + double criteria; // in ps/mV + bool one_side_is_whole; + } t, v; +}; + +struct margin_link_args { + struct margin_com_args *common; u8 steps_t; // 0 == use NumTimingSteps u8 steps_v; // 0 == use NumVoltageSteps u8 parallel_lanes; // [1; MaxLanes + 1] - u8 error_limit; // [0; 63] u8 recvs[6]; // Receivers Numbers u8 recvs_n; // 0 == margin all available receivers - u8 lanes[32]; // Lanes to Margin - u8 lanes_n; // 0 == margin all available lanes - bool run_margin; // Or print params only - u8 verbosity; // 0 - basic; - // 1 - add info about remaining time and lanes in progress during margining + struct margin_recv_args recv_args[6]; + u8 lanes[32]; // Lanes to Margin + u8 lanes_n; // 0 == margin all available lanes +}; - u64 *steps_utility; // For ETA logging +struct margin_link { + struct margin_dev down_port; + struct margin_dev up_port; + struct margin_link_args args; }; /* Receiver structure */ struct margin_recv { struct margin_dev *dev; - u8 recvn; // Receiver Number + u8 recvn; // Receiver Number; from 1 to 6 bool lane_reversal; struct margin_params *params; u8 parallel_lanes; u8 error_limit; + u8 dwell_time; }; struct margin_lanes_data { @@ -159,8 +177,23 @@ struct margin_lanes_data { u8 verbosity; }; +/* margin_args */ + +enum margin_mode { MARGIN, FULL, SCAN }; + +extern const char *usage; + +struct margin_link *margin_parse_util_args(struct pci_access *pacc, int argc, char **argv, + enum margin_mode mode, u8 *links_n); + /* margin_hw */ +bool margin_port_is_down(struct pci_dev *dev); + +/* Results through down/up ports */ +bool margin_find_pair(struct pci_access *pacc, struct pci_dev *dev, struct pci_dev **down_port, + struct pci_dev **up_port); + /* Verify that devices form the link with 16 GT/s or 32 GT/s data rate */ bool margin_verify_link(struct pci_dev *down_port, struct pci_dev *up_port); @@ -183,12 +216,11 @@ void margin_restore_link(struct margin_link *link); bool margin_read_params(struct pci_access *pacc, struct pci_dev *dev, u8 recvn, struct margin_params *params); -enum margin_test_status margin_process_args(struct margin_dev *dev, struct margin_args *args); +enum margin_test_status margin_process_args(struct margin_link *link); -/* Awaits that args are prepared through process_args. +/* Awaits that links are prepared through process_args. Returns number of margined Receivers through recvs_n */ -struct margin_results *margin_test_link(struct margin_link *link, struct margin_args *args, - u8 *recvs_n); +struct margin_results *margin_test_link(struct margin_link *link, u8 *recvs_n); void margin_free_results(struct margin_results *results, u8 results_n); @@ -201,8 +233,9 @@ void margin_log(char *format, ...); /* b:d.f -> b:d.f */ void margin_log_bdfs(struct pci_dev *down_port, struct pci_dev *up_port); +void margin_gen_bdfs(struct pci_dev *down_port, struct pci_dev *up_port, char *dest, size_t maxlen); -/* Print Link header (bdfs, width, speed) */ +/* Print Link header (bdfs, neg_width, speed) */ void margin_log_link(struct margin_link *link); void margin_log_params(struct margin_params *params); @@ -220,9 +253,20 @@ void margin_log_hw_quirks(struct margin_recv *recv); /* margin_results */ -void margin_results_print_brief(struct margin_results *results, u8 recvs_n); +// Min values are taken from PCIe Base Spec Rev. 5.0 Section 8.4.2. +// Rec values are based on PCIe Arch PHY Test Spec Rev 5.0 +// (Transmitter Electrical Compliance) + +// values in ps +static const double margin_ew_min[] = { 18.75, 9.375 }; +static const double margin_ew_rec[] = { 23.75, 10.1565 }; + +static const double margin_eh_min[] = { 15, 15 }; +static const double margin_eh_rec[] = { 21, 19.75 }; + +void margin_results_print_brief(struct margin_results *results, u8 recvs_n, + struct margin_link_args *args); -void margin_results_save_csv(struct margin_results *results, u8 recvs_n, char *dir, - struct pci_dev *up_port); +void margin_results_save_csv(struct margin_results *results, u8 recvs_n, struct margin_link *link); #endif diff --git a/lmr/margin.c b/lmr/margin.c index a8c6571..4d68031 100644 --- a/lmr/margin.c +++ b/lmr/margin.c @@ -1,7 +1,7 @@ /* * The PCI Utilities -- Obtain the margin information of the Link * - * Copyright (c) 2023 KNS Group LLC (YADRO) + * Copyright (c) 2023-2024 KNS Group LLC (YADRO) * * Can be freely distributed and used under the terms of the GNU GPL v2+. * @@ -143,13 +143,17 @@ margin_report_cmd(struct margin_dev *dev, u8 lane, margin_cmd cmd, margin_cmd *r } static void -margin_apply_hw_quirks(struct margin_recv *recv) +margin_apply_hw_quirks(struct margin_recv *recv, struct margin_link_args *args) { switch (recv->dev->hw) { case MARGIN_ICE_LAKE_RC: if (recv->recvn == 1) - recv->params->volt_offset = 12; + { + recv->params->volt_offset = 12; + args->recv_args[recv->recvn - 1].t.one_side_is_whole = true; + args->recv_args[recv->recvn - 1].t.valid = true; + } break; default: break; @@ -161,7 +165,7 @@ read_params_internal(struct margin_dev *dev, u8 recvn, bool lane_reversal, struct margin_params *params) { margin_cmd resp; - u8 lane = lane_reversal ? dev->width - 1 : 0; + u8 lane = lane_reversal ? dev->max_width - 1 : 0; margin_set_cmd(dev, lane, NO_COMMAND); bool status = margin_report_cmd(dev, lane, REPORT_CAPS(recvn), &resp); if (status) @@ -260,7 +264,7 @@ margin_test_lanes(struct margin_lanes_data arg) pci_write_word(arg.recv->dev->dev, ctrl_addr, step_cmd); } } - msleep(MARGIN_STEP_MS); + msleep(arg.recv->dwell_time * 1000); for (int i = 0; i < arg.lanes_n; i++) { @@ -300,7 +304,7 @@ margin_test_lanes(struct margin_lanes_data arg) /* Awaits that Receiver is prepared through prep_dev function */ static bool -margin_test_receiver(struct margin_dev *dev, u8 recvn, struct margin_args *args, +margin_test_receiver(struct margin_dev *dev, u8 recvn, struct margin_link_args *args, struct margin_results *results) { u8 *lanes_to_margin = args->lanes; @@ -312,7 +316,8 @@ margin_test_receiver(struct margin_dev *dev, u8 recvn, struct margin_args *args, .lane_reversal = false, .params = ¶ms, .parallel_lanes = args->parallel_lanes ? args->parallel_lanes : 1, - .error_limit = args->error_limit }; + .error_limit = args->common->error_limit, + .dwell_time = args->common->dwell_time }; results->recvn = recvn; results->lanes_n = lanes_n; @@ -340,7 +345,7 @@ margin_test_receiver(struct margin_dev *dev, u8 recvn, struct margin_args *args, if (recv.parallel_lanes > params.max_lanes + 1) recv.parallel_lanes = params.max_lanes + 1; - margin_apply_hw_quirks(&recv); + margin_apply_hw_quirks(&recv, args); margin_log_hw_quirks(&recv); results->tim_off_reported = params.timing_offset != 0; @@ -361,15 +366,16 @@ margin_test_receiver(struct margin_dev *dev, u8 recvn, struct margin_args *args, for (int i = 0; i < lanes_n; i++) { results->lanes[i].lane - = recv.lane_reversal ? dev->width - lanes_to_margin[i] - 1 : lanes_to_margin[i]; + = recv.lane_reversal ? dev->max_width - lanes_to_margin[i] - 1 : lanes_to_margin[i]; } - if (args->run_margin) + if (args->common->run_margin) { - if (args->verbosity > 0) + if (args->common->verbosity > 0) margin_log("\n"); - struct margin_lanes_data lanes_data - = { .recv = &recv, .verbosity = args->verbosity, .steps_utility = args->steps_utility }; + struct margin_lanes_data lanes_data = { .recv = &recv, + .verbosity = args->common->verbosity, + .steps_utility = &args->common->steps_utility }; enum margin_dir dir[] = { TIM_LEFT, TIM_RIGHT, VOLT_UP, VOLT_DOWN }; @@ -399,15 +405,15 @@ margin_test_receiver(struct margin_dev *dev, u8 recvn, struct margin_args *args, lanes_data.ind = timing ? params.ind_left_right_tim : params.ind_up_down_volt; lanes_data.dir = dir[i]; lanes_data.steps_lane_total = timing ? steps_t : steps_v; - if (*args->steps_utility >= lanes_data.steps_lane_total) - *args->steps_utility -= lanes_data.steps_lane_total; + if (args->common->steps_utility >= lanes_data.steps_lane_total) + args->common->steps_utility -= lanes_data.steps_lane_total; else - *args->steps_utility = 0; + args->common->steps_utility = 0; margin_test_lanes(lanes_data); } lanes_done += use_lanes; } - if (args->verbosity > 0) + if (args->common->verbosity > 0) margin_log("\n"); if (recv.lane_reversal) { @@ -426,13 +432,8 @@ margin_read_params(struct pci_access *pacc, struct pci_dev *dev, u8 recvn, struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL); if (!cap) return false; - u8 dev_dir = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_FLAGS), PCI_EXP_FLAGS_TYPE); - bool dev_down; - if (dev_dir == PCI_EXP_TYPE_ROOT_PORT || dev_dir == PCI_EXP_TYPE_DOWNSTREAM) - dev_down = true; - else - dev_down = false; + bool dev_down = margin_port_is_down(dev); if (recvn == 0) { @@ -453,25 +454,7 @@ margin_read_params(struct pci_access *pacc, struct pci_dev *dev, u8 recvn, struct pci_dev *up = NULL; struct margin_link link; - for (struct pci_dev *p = pacc->devices; p; p = p->next) - { - if (dev_down && pci_read_byte(dev, PCI_SECONDARY_BUS) == p->bus && dev->domain == p->domain - && p->func == 0) - { - down = dev; - up = p; - break; - } - else if (!dev_down && pci_read_byte(p, PCI_SECONDARY_BUS) == dev->bus - && dev->domain == p->domain) - { - down = p; - up = dev; - break; - } - } - - if (!down) + if (!margin_find_pair(pacc, dev, &down, &up)) return false; if (!margin_fill_link(down, up, &link)) @@ -499,8 +482,11 @@ margin_read_params(struct pci_access *pacc, struct pci_dev *dev, u8 recvn, } enum margin_test_status -margin_process_args(struct margin_dev *dev, struct margin_args *args) +margin_process_args(struct margin_link *link) { + struct margin_dev *dev = &link->down_port; + struct margin_link_args *args = &link->args; + u8 receivers_n = 2 + 2 * dev->retimers_n; if (!args->recvs_n) @@ -524,7 +510,7 @@ margin_process_args(struct margin_dev *dev, struct margin_args *args) if (!args->lanes_n) { - args->lanes_n = dev->width; + args->lanes_n = dev->neg_width; for (int i = 0; i < args->lanes_n; i++) args->lanes[i] = i; } @@ -532,7 +518,7 @@ margin_process_args(struct margin_dev *dev, struct margin_args *args) { for (int i = 0; i < args->lanes_n; i++) { - if (args->lanes[i] >= dev->width) + if (args->lanes[i] >= dev->neg_width) { return MARGIN_TEST_ARGS_LANES; } @@ -543,8 +529,10 @@ margin_process_args(struct margin_dev *dev, struct margin_args *args) } struct margin_results * -margin_test_link(struct margin_link *link, struct margin_args *args, u8 *recvs_n) +margin_test_link(struct margin_link *link, u8 *recvs_n) { + struct margin_link_args *args = &link->args; + bool status = margin_prep_link(link); u8 receivers_n = status ? args->recvs_n : 1; diff --git a/lmr/margin_args.c b/lmr/margin_args.c new file mode 100644 index 0000000..57a1d0a --- /dev/null +++ b/lmr/margin_args.c @@ -0,0 +1,302 @@ +/* + * The PCI Utilities -- Parse pcilmr utility arguments + * + * Copyright (c) 2024 KNS Group LLC (YADRO) + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lmr.h" + +const char *usage + = "! Utility requires preliminary preparation of the system. Refer to the pcilmr man page !\n\n" + "Brief usage (see man for all options):\n" + "pcilmr [--margin] [<common options>] <link port> [<link options>] [<link port> [<link " + "options>] ...]\n" + "pcilmr --full [<common options>]\n" + "pcilmr --scan\n\n" + "You can specify Downstream or Upstream Port of the Link.\nPort Specifier:\n" + "<device/component>:\t[<domain>:]<bus>:<dev>.<func>\n\n" + "Modes:\n" + "--margin\t\tMargin selected Links\n" + "--full\t\t\tMargin all ready for testing Links in the system (one by one)\n" + "--scan\t\t\tScan for Links available for margining\n\n" + "Margining options (see man for all options):\n\n" + "Common (for all specified links) options:\n" + "-c\t\t\tPrint Device Lane Margining Capabilities only. Do not run margining.\n\n" + "Link specific options:\n" + "-r <recvn>[,<recvn>...]\tSpecify Receivers to select margining targets.\n" + "\t\t\tDefault: all available Receivers (including Retimers).\n" + "-t <steps>\t\tSpecify maximum number of steps for Time Margining.\n" + "-v <steps>\t\tSpecify maximum number of steps for Voltage Margining.\n"; + +static struct pci_dev * +dev_for_filter(struct pci_access *pacc, char *filter) +{ + struct pci_filter pci_filter; + pci_filter_init(pacc, &pci_filter); + if (pci_filter_parse_slot(&pci_filter, filter)) + die("Invalid device ID: %s\n", filter); + + if (pci_filter.bus == -1 || pci_filter.slot == -1 || pci_filter.func == -1) + die("Invalid device ID: %s\n", filter); + + if (pci_filter.domain == -1) + pci_filter.domain = 0; + + for (struct pci_dev *p = pacc->devices; p; p = p->next) + { + if (pci_filter_match(&pci_filter, p)) + return p; + } + + die("No such PCI device: %s or you don't have enough privileges.\n", filter); +} + +static u8 +parse_csv_arg(char *arg, u8 *vals) +{ + u8 cnt = 0; + char *token = strtok(arg, ","); + while (token) + { + vals[cnt] = atoi(token); + cnt++; + token = strtok(NULL, ","); + } + return cnt; +} + +static u8 +find_ready_links(struct pci_access *pacc, struct margin_link *links, bool cnt_only) +{ + u8 cnt = 0; + for (struct pci_dev *p = pacc->devices; p; p = p->next) + { + if (pci_find_cap(p, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED) && margin_port_is_down(p)) + { + struct pci_dev *down = NULL; + struct pci_dev *up = NULL; + margin_find_pair(pacc, p, &down, &up); + + if (down && margin_verify_link(down, up) + && (margin_check_ready_bit(down) || margin_check_ready_bit(up))) + { + if (!cnt_only) + margin_fill_link(down, up, &(links[cnt])); + cnt++; + } + } + } + return cnt; +} + +static void +init_link_args(struct margin_link_args *link_args, struct margin_com_args *com_args) +{ + memset(link_args, 0, sizeof(*link_args)); + link_args->common = com_args; + link_args->parallel_lanes = 1; +} + +static void +parse_dev_args(int argc, char **argv, struct margin_link_args *args, u8 link_speed) +{ + if (argc == optind) + return; + int c; + while ((c = getopt(argc, argv, "+r:l:p:t:v:VTg:")) != -1) + { + switch (c) + { + case 't': + args->steps_t = atoi(optarg); + break; + case 'T': + args->steps_t = 63; + break; + case 'v': + args->steps_v = atoi(optarg); + break; + case 'V': + args->steps_v = 127; + break; + case 'p': + args->parallel_lanes = atoi(optarg); + break; + case 'l': + args->lanes_n = parse_csv_arg(optarg, args->lanes); + break; + case 'r': + args->recvs_n = parse_csv_arg(optarg, args->recvs); + break; + case 'g': { + char recv[2] = { 0 }; + char dir[2] = { 0 }; + char unit[4] = { 0 }; + float criteria = 0.0; + char eye[2] = { 0 }; + int cons[3] = { 0 }; + + int ret = sscanf(optarg, "%1[1-6]%1[tv]=%f%n%3[%,ps]%n%1[f]%n", recv, dir, &criteria, + &cons[0], unit, &cons[1], eye, &cons[2]); + if (ret < 3) + { + ret = sscanf(optarg, "%1[1-6]%1[tv]=%1[f]%n,%f%n%2[ps%]%n", recv, dir, eye, + &cons[0], &criteria, &cons[1], unit, &cons[2]); + if (ret < 3) + die("Invalid arguments\n\n%s", usage); + } + + int consumed = 0; + for (int i = 0; i < 3; i++) + if (cons[i] > consumed) + consumed = cons[i]; + if ((size_t)consumed != strlen(optarg)) + die("Invalid arguments\n\n%s", usage); + if (criteria < 0) + die("Invalid arguments\n\n%s", usage); + if (strstr(unit, ",") && eye[0] == 0) + die("Invalid arguments\n\n%s", usage); + + u8 recv_n = recv[0] - '0' - 1; + if (dir[0] == 'v') + { + if (unit[0] != ',' && unit[0] != 0) + die("Invalid arguments\n\n%s", usage); + args->recv_args[recv_n].v.valid = true; + args->recv_args[recv_n].v.criteria = criteria; + if (eye[0] != 0) + args->recv_args[recv_n].v.one_side_is_whole = true; + } + else + { + if (unit[0] == '%') + criteria = criteria / 100.0 * margin_ui[link_speed]; + else if (unit[0] != 0 && (unit[0] != 'p' || unit[1] != 's')) + die("Invalid arguments\n\n%s", usage); + else if (unit[0] == 0 && criteria != 0) + die("Invalid arguments\n\n%s", usage); + args->recv_args[recv_n].t.valid = true; + args->recv_args[recv_n].t.criteria = criteria; + if (eye[0] != 0) + args->recv_args[recv_n].t.one_side_is_whole = true; + } + break; + } + case '?': + die("Invalid arguments\n\n%s", usage); + break; + default: + return; + } + } +} + +struct margin_link * +margin_parse_util_args(struct pci_access *pacc, int argc, char **argv, enum margin_mode mode, + u8 *links_n) +{ + struct margin_com_args *com_args = xmalloc(sizeof(*com_args)); + com_args->error_limit = 4; + com_args->run_margin = true; + com_args->verbosity = 1; + com_args->steps_utility = 0; + com_args->dir_for_csv = NULL; + com_args->save_csv = false; + com_args->dwell_time = 1; + + int c; + while ((c = getopt(argc, argv, "+e:co:d:")) != -1) + { + switch (c) + { + case 'c': + com_args->run_margin = false; + break; + case 'e': + com_args->error_limit = atoi(optarg); + break; + case 'o': + com_args->dir_for_csv = optarg; + com_args->save_csv = true; + break; + case 'd': + com_args->dwell_time = atoi(optarg); + break; + default: + die("Invalid arguments\n\n%s", usage); + } + } + + bool status = true; + if (mode == FULL && optind != argc) + status = false; + if (mode == MARGIN && optind == argc) + status = false; + if (!status && argc > 1) + die("Invalid arguments\n\n%s", usage); + if (!status) + { + printf("%s", usage); + exit(0); + } + + u8 ports_n = 0; + struct margin_link *links = NULL; + char err[128]; + + if (mode == FULL) + { + ports_n = find_ready_links(pacc, NULL, true); + if (ports_n == 0) + die("Links not found or you don't have enough privileges.\n"); + else + { + links = xmalloc(ports_n * sizeof(*links)); + find_ready_links(pacc, links, false); + for (int i = 0; i < ports_n; i++) + init_link_args(&(links[i].args), com_args); + } + } + else if (mode == MARGIN) + { + while (optind != argc) + { + struct pci_dev *dev = dev_for_filter(pacc, argv[optind]); + optind++; + links = xrealloc(links, (ports_n + 1) * sizeof(*links)); + struct pci_dev *down; + struct pci_dev *up; + if (!margin_find_pair(pacc, dev, &down, &up)) + die("Cannot find pair for the specified device: %s\n", argv[optind - 1]); + struct pci_cap *cap = pci_find_cap(down, PCI_CAP_ID_EXP, PCI_CAP_NORMAL); + if (!cap) + die("Looks like you don't have enough privileges to access " + "Device Configuration Space.\nTry to run utility as root.\n"); + if (!margin_fill_link(down, up, &(links[ports_n]))) + { + margin_gen_bdfs(down, up, err, sizeof(err)); + die("Link %s is not ready for margining.\n" + "Link data rate must be 16 GT/s or 32 GT/s.\n" + "Downstream Component must be at D0 PM state.\n", + err); + } + init_link_args(&(links[ports_n].args), com_args); + parse_dev_args(argc, argv, &(links[ports_n].args), + links[ports_n].down_port.link_speed - 4); + ports_n++; + } + } + else + die("Bug in the args parsing!\n"); + + *links_n = ports_n; + return links; +} diff --git a/lmr/margin_hw.c b/lmr/margin_hw.c index fc427c8..c376549 100644 --- a/lmr/margin_hw.c +++ b/lmr/margin_hw.c @@ -1,13 +1,15 @@ /* * The PCI Utilities -- Verify and prepare devices before margining * - * Copyright (c) 2023 KNS Group LLC (YADRO) + * Copyright (c) 2023-2024 KNS Group LLC (YADRO) * * Can be freely distributed and used under the terms of the GNU GPL v2+. * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include <memory.h> + #include "lmr.h" static u16 special_hw[][4] = @@ -32,6 +34,51 @@ detect_unique_hw(struct pci_dev *dev) } bool +margin_port_is_down(struct pci_dev *dev) +{ + struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL); + if (!cap) + return false; + u8 type = pci_read_byte(dev, PCI_HEADER_TYPE) & 0x7F; + u8 dir = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_FLAGS), PCI_EXP_FLAGS_TYPE); + + if (type == PCI_HEADER_TYPE_BRIDGE + && (dir == PCI_EXP_TYPE_ROOT_PORT || dir == PCI_EXP_TYPE_DOWNSTREAM)) + return true; + else + return false; +} + +bool +margin_find_pair(struct pci_access *pacc, struct pci_dev *dev, struct pci_dev **down_port, + struct pci_dev **up_port) +{ + struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL); + if (!cap) + return false; + bool given_down = margin_port_is_down(dev); + + for (struct pci_dev *p = pacc->devices; p; p = p->next) + { + if (given_down && pci_read_byte(dev, PCI_SECONDARY_BUS) == p->bus && dev->domain == p->domain + && p->func == 0) + { + *down_port = dev; + *up_port = p; + return true; + } + else if (!given_down && pci_read_byte(p, PCI_SECONDARY_BUS) == dev->bus + && dev->domain == p->domain) + { + *down_port = p; + *up_port = dev; + return true; + } + } + return false; +} + +bool margin_verify_link(struct pci_dev *down_port, struct pci_dev *up_port) { struct pci_cap *cap = pci_find_cap(down_port, PCI_CAP_ID_EXP, PCI_CAP_NORMAL); @@ -42,16 +89,11 @@ margin_verify_link(struct pci_dev *down_port, struct pci_dev *up_port) if ((pci_read_word(down_port, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED) > 5) return false; - u8 down_type = pci_read_byte(down_port, PCI_HEADER_TYPE) & 0x7F; u8 down_sec = pci_read_byte(down_port, PCI_SECONDARY_BUS); - u8 down_dir - = GET_REG_MASK(pci_read_word(down_port, cap->addr + PCI_EXP_FLAGS), PCI_EXP_FLAGS_TYPE); // Verify that devices are linked, down_port is Root Port or Downstream Port of Switch, // up_port is Function 0 of a Device - if (!(down_sec == up_port->bus && down_type == PCI_HEADER_TYPE_BRIDGE - && (down_dir == PCI_EXP_TYPE_ROOT_PORT || down_dir == PCI_EXP_TYPE_DOWNSTREAM) - && up_port->func == 0)) + if (!(down_sec == up_port->bus && margin_port_is_down(down_port) && up_port->func == 0)) return false; struct pci_cap *pm = pci_find_cap(up_port, PCI_CAP_ID_PM, PCI_CAP_NORMAL); @@ -70,21 +112,24 @@ static struct margin_dev fill_dev_wrapper(struct pci_dev *dev) { struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL); - struct margin_dev res - = { .dev = dev, - .lmr_cap_addr = pci_find_cap(dev, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED)->addr, - .width = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA), PCI_EXP_LNKSTA_WIDTH), - .retimers_n - = (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_RETIMER)) - + (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_2RETIMERS)), - .link_speed = (pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED), - .hw = detect_unique_hw(dev) }; + struct margin_dev res = { + .dev = dev, + .lmr_cap_addr = pci_find_cap(dev, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED)->addr, + .neg_width = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA), PCI_EXP_LNKSTA_WIDTH), + .max_width = GET_REG_MASK(pci_read_long(dev, cap->addr + PCI_EXP_LNKCAP), PCI_EXP_LNKCAP_WIDTH), + .retimers_n + = (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_RETIMER)) + + (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_2RETIMERS)), + .link_speed = (pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED), + .hw = detect_unique_hw(dev) + }; return res; } bool margin_fill_link(struct pci_dev *down_port, struct pci_dev *up_port, struct margin_link *wrappers) { + memset(wrappers, 0, sizeof(*wrappers)); if (!margin_verify_link(down_port, up_port)) return false; wrappers->down_port = fill_dev_wrapper(down_port); diff --git a/lmr/margin_log.c b/lmr/margin_log.c index b3c4bd5..2cb01c8 100644 --- a/lmr/margin_log.c +++ b/lmr/margin_log.c @@ -1,7 +1,7 @@ /* * The PCI Utilities -- Log margining process * - * Copyright (c) 2023 KNS Group LLC (YADRO) + * Copyright (c) 2023-2024 KNS Group LLC (YADRO) * * Can be freely distributed and used under the terms of the GNU GPL v2+. * @@ -38,11 +38,22 @@ margin_log_bdfs(struct pci_dev *down, struct pci_dev *up) } void +margin_gen_bdfs(struct pci_dev *down, struct pci_dev *up, char *dest, size_t maxlen) +{ + if (margin_print_domain) + snprintf(dest, maxlen, "%x:%x:%x.%x -> %x:%x:%x.%x", down->domain, down->bus, down->dev, + down->func, up->domain, up->bus, up->dev, up->func); + else + snprintf(dest, maxlen, "%x:%x.%x -> %x:%x.%x", down->bus, down->dev, down->func, up->bus, + up->dev, up->func); +} + +void margin_log_link(struct margin_link *link) { margin_log("Link "); margin_log_bdfs(link->down_port.dev, link->up_port.dev); - margin_log("\nNegotiated Link Width: %d\n", link->down_port.width); + margin_log("\nNegotiated Link Width: %d\n", link->down_port.neg_width); margin_log("Link Speed: %d.0 GT/s = Gen %d\n", (link->down_port.link_speed - 3) * 16, link->down_port.link_speed); margin_log("Available receivers: "); @@ -77,7 +88,8 @@ void margin_log_receiver(struct margin_recv *recv) { margin_log("\nError Count Limit = %d\n", recv->error_limit); - margin_log("Parallel Lanes: %d\n\n", recv->parallel_lanes); + margin_log("Parallel Lanes: %d\n", recv->parallel_lanes); + margin_log("Margining dwell time: %d s\n\n", recv->dwell_time); margin_log_params(recv->params); @@ -132,8 +144,8 @@ margin_log_margining(struct margin_lanes_data arg) } margin_log("]"); - u64 lane_eta_s = (arg.steps_lane_total - arg.steps_lane_done) * MARGIN_STEP_MS / 1000; - u64 total_eta_s = *arg.steps_utility * MARGIN_STEP_MS / 1000 + lane_eta_s; + u64 lane_eta_s = (arg.steps_lane_total - arg.steps_lane_done) * arg.recv->dwell_time; + u64 total_eta_s = *arg.steps_utility * arg.recv->dwell_time + lane_eta_s; margin_log(" - ETA: %3ds Steps: %3d Total ETA: %3dm %2ds", lane_eta_s, arg.steps_lane_done, total_eta_s / 60, total_eta_s % 60); @@ -150,7 +162,8 @@ margin_log_hw_quirks(struct margin_recv *recv) if (recv->recvn == 1) margin_log("\nRx(A) is Intel Ice Lake RC port.\n" "Applying next quirks for margining process:\n" - " - Set MaxVoltageOffset to 12 (120 mV).\n"); + " - Set MaxVoltageOffset to 12 (120 mV);\n" + " - Force the use of 'one side is the whole' grading mode.\n"); break; default: break; diff --git a/lmr/margin_results.c b/lmr/margin_results.c index 4d28f04..b0c5c26 100644 --- a/lmr/margin_results.c +++ b/lmr/margin_results.c @@ -1,7 +1,7 @@ /* * The PCI Utilities -- Display/save margining results * - * Copyright (c) 2023 KNS Group LLC (YADRO) + * Copyright (c) 2023-2024 KNS Group LLC (YADRO) * * Can be freely distributed and used under the terms of the GNU GPL v2+. * @@ -16,25 +16,23 @@ #include "lmr.h" enum lane_rating { - BAD = 0, - OKAY, + FAIL = 0, + PASS, PERFECT, - WEIRD, INIT, }; -static char *const grades[] = { "Bad", "Okay", "Perfect", "Weird" }; +static char *const grades[] = { "Fail", "Pass", "Perfect" }; static char *const sts_strings[] = { "NAK", "LIM", "THR" }; -static const double ui[] = { 62.5 / 100, 31.25 / 100 }; static enum lane_rating rate_lane(double value, double min, double recommended, enum lane_rating cur_rate) { enum lane_rating res = PERFECT; if (value < recommended) - res = OKAY; + res = PASS; if (value < min) - res = BAD; + res = FAIL; if (cur_rate == INIT) return res; if (res < cur_rate) @@ -43,34 +41,9 @@ rate_lane(double value, double min, double recommended, enum lane_rating cur_rat return cur_rate; } -static bool -check_recv_weird(struct margin_results *results, double tim_min, double volt_min) -{ - bool result = true; - - struct margin_res_lane *lane; - for (int i = 0; i < results->lanes_n && result; i++) - { - lane = &(results->lanes[i]); - if (lane->steps[TIM_LEFT] * results->tim_coef != tim_min) - result = false; - if (results->params.ind_left_right_tim - && lane->steps[TIM_RIGHT] * results->tim_coef != tim_min) - result = false; - if (results->params.volt_support) - { - if (lane->steps[VOLT_UP] * results->volt_coef != volt_min) - result = false; - if (results->params.ind_up_down_volt - && lane->steps[VOLT_DOWN] * results->volt_coef != volt_min) - result = false; - } - } - return result; -} - void -margin_results_print_brief(struct margin_results *results, u8 recvs_n) +margin_results_print_brief(struct margin_results *results, u8 recvs_n, + struct margin_link_args *args) { struct margin_res_lane *lane; struct margin_results *res; @@ -80,6 +53,14 @@ margin_results_print_brief(struct margin_results *results, u8 recvs_n) u8 link_speed; + struct margin_recv_args grade_args; + bool spec_ref_only; + + double ew_min; + double ew_rec; + double eh_min; + double eh_rec; + char *no_test_msgs[] = { "", "Margining Ready bit is Clear", "Error during caps reading", @@ -102,6 +83,67 @@ margin_results_print_brief(struct margin_results *results, u8 recvs_n) continue; } + spec_ref_only = true; + grade_args = args->recv_args[res->recvn - 1]; + if (grade_args.t.criteria != 0) + { + spec_ref_only = false; + ew_min = grade_args.t.criteria; + ew_rec = grade_args.t.criteria; + } + else + { + ew_min = margin_ew_min[link_speed]; + ew_rec = margin_ew_rec[link_speed]; + } + + if (grade_args.v.criteria != 0) + { + spec_ref_only = false; + eh_min = grade_args.v.criteria; + eh_rec = grade_args.v.criteria; + } + else + { + eh_min = margin_eh_min[link_speed]; + eh_rec = margin_eh_rec[link_speed]; + } + + printf("Rx(%X) - Grading criteria:\n", 10 + res->recvn - 1); + if (spec_ref_only) + { + printf("\tUsing spec only:\n"); + printf("\tEW: minimum - %.2f ps; recommended - %.2f ps\n", ew_min, ew_rec); + printf("\tEH: minimum - %.2f mV; recommended - %.2f mV\n\n", eh_min, eh_rec); + } + else + { + printf("\tEW: pass - %.2f ps\n", ew_min); + printf("\tEH: pass - %.2f mV\n\n", eh_min); + } + + if (!params.ind_left_right_tim) + { + printf("Rx(%X) - EW: independent left/right timing margin is not supported:\n", + 10 + res->recvn - 1); + if (grade_args.t.one_side_is_whole) + printf("\tmanual setting - the entire margin across the eye " + "is what is reported by one side margining\n\n"); + else + printf("\tdefault - calculating EW as double one side result\n\n"); + } + + if (params.volt_support && !params.ind_up_down_volt) + { + printf("Rx(%X) - EH: independent up and down voltage margining is not supported:\n", + 10 + res->recvn - 1); + if (grade_args.v.one_side_is_whole) + printf("\tmanual setting - the entire margin across the eye " + "is what is reported by one side margining\n\n"); + else + printf("\tdefault - calculating EH as double one side result\n\n"); + } + if (res->lane_reversal) printf("Rx(%X) - Lane Reversal\n", 10 + res->recvn - 1); @@ -118,51 +160,60 @@ margin_results_print_brief(struct margin_results *results, u8 recvs_n) "reliable.\n\n", 10 + res->recvn - 1); - if (check_recv_weird(res, MARGIN_TIM_MIN, MARGIN_VOLT_MIN)) - lane_rating = WEIRD; - else - lane_rating = INIT; - - for (u8 j = 0; j < res->lanes_n; j++) + for (int j = 0; j < res->lanes_n; j++) { + if (spec_ref_only) + lane_rating = INIT; + else + lane_rating = PASS; + lane = &(res->lanes[j]); - double left_ui = lane->steps[TIM_LEFT] * res->tim_coef; - double right_ui = lane->steps[TIM_RIGHT] * res->tim_coef; + double left_ps = lane->steps[TIM_LEFT] * res->tim_coef / 100.0 * margin_ui[link_speed]; + double right_ps = lane->steps[TIM_RIGHT] * res->tim_coef / 100.0 * margin_ui[link_speed]; double up_volt = lane->steps[VOLT_UP] * res->volt_coef; double down_volt = lane->steps[VOLT_DOWN] * res->volt_coef; - if (lane_rating != WEIRD) + double ew = left_ps; + if (params.ind_left_right_tim) + ew += right_ps; + else if (!grade_args.t.one_side_is_whole) + ew *= 2.0; + + double eh = 0.0; + if (params.volt_support) { - lane_rating = rate_lane(left_ui, MARGIN_TIM_MIN, MARGIN_TIM_RECOMMEND, INIT); - if (params.ind_left_right_tim) - lane_rating - = rate_lane(right_ui, MARGIN_TIM_MIN, MARGIN_TIM_RECOMMEND, lane_rating); - if (params.volt_support) - { - lane_rating = rate_lane(up_volt, MARGIN_VOLT_MIN, MARGIN_VOLT_MIN, lane_rating); - if (params.ind_up_down_volt) - lane_rating - = rate_lane(down_volt, MARGIN_VOLT_MIN, MARGIN_VOLT_MIN, lane_rating); - } + eh += up_volt; + if (params.ind_up_down_volt) + eh += down_volt; + else if (!grade_args.v.one_side_is_whole) + eh *= 2.0; } - printf("Rx(%X) Lane %2d - %s\t", 10 + res->recvn - 1, lane->lane, grades[lane_rating]); + lane_rating = rate_lane(ew, ew_min, ew_rec, lane_rating); + if (params.volt_support) + lane_rating = rate_lane(eh, eh_min, eh_rec, lane_rating); + + printf("Rx(%X) Lane %2d: %s\t (W %4.1f%% UI - %5.2fps", 10 + res->recvn - 1, lane->lane, + grades[lane_rating], ew / margin_ui[link_speed] * 100.0, ew); + if (params.volt_support) + printf(", H %5.1f mV", eh); if (params.ind_left_right_tim) - printf("L %4.1f%% UI - %5.2fps - %2dst %s, R %4.1f%% UI - %5.2fps - %2dst %s", left_ui, - left_ui * ui[link_speed], lane->steps[TIM_LEFT], - sts_strings[lane->statuses[TIM_LEFT]], right_ui, right_ui * ui[link_speed], - lane->steps[TIM_RIGHT], sts_strings[lane->statuses[TIM_RIGHT]]); + printf(") (L %4.1f%% UI - %5.2fps - %2dst %s) (R %4.1f%% UI - %5.2fps - %2dst %s)", + left_ps / margin_ui[link_speed] * 100.0, left_ps, lane->steps[TIM_LEFT], + sts_strings[lane->statuses[TIM_LEFT]], right_ps / margin_ui[link_speed] * 100.0, + right_ps, lane->steps[TIM_RIGHT], sts_strings[lane->statuses[TIM_RIGHT]]); else - printf("T %4.1f%% UI - %5.2fps - %2dst %s", left_ui, left_ui * ui[link_speed], - lane->steps[TIM_LEFT], sts_strings[lane->statuses[TIM_LEFT]]); + printf(") (T %4.1f%% UI - %5.2fps - %2dst %s)", + left_ps / margin_ui[link_speed] * 100.0, left_ps, lane->steps[TIM_LEFT], + sts_strings[lane->statuses[TIM_LEFT]]); if (params.volt_support) { if (params.ind_up_down_volt) - printf(", U %5.1f mV - %3dst %s, D %5.1f mV - %3dst %s", up_volt, + printf(" (U %5.1f mV - %3dst %s) (D %5.1f mV - %3dst %s)", up_volt, lane->steps[VOLT_UP], sts_strings[lane->statuses[VOLT_UP]], down_volt, lane->steps[VOLT_DOWN], sts_strings[lane->statuses[VOLT_DOWN]]); else - printf(", V %5.1f mV - %3dst %s", up_volt, lane->steps[VOLT_UP], + printf(" (V %5.1f mV - %3dst %s)", up_volt, lane->steps[VOLT_UP], sts_strings[lane->statuses[VOLT_UP]]); } printf("\n"); @@ -172,13 +223,13 @@ margin_results_print_brief(struct margin_results *results, u8 recvs_n) } void -margin_results_save_csv(struct margin_results *results, u8 recvs_n, char *dir, - struct pci_dev *up_port) +margin_results_save_csv(struct margin_results *results, u8 recvs_n, struct margin_link *link) { char timestamp[64]; time_t tim = time(NULL); strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S", gmtime(&tim)); + char *dir = link->args.common->dir_for_csv; size_t pathlen = strlen(dir) + 128; char *path = xmalloc(pathlen); FILE *csv; @@ -190,6 +241,16 @@ margin_results_save_csv(struct margin_results *results, u8 recvs_n, char *dir, enum lane_rating lane_rating; u8 link_speed; + struct margin_recv_args grade_args; + bool spec_ref_only; + + double ew_min; + double ew_rec; + double eh_min; + double eh_rec; + + struct pci_dev *port; + for (int i = 0; i < recvs_n; i++) { res = &(results[i]); @@ -198,81 +259,120 @@ margin_results_save_csv(struct margin_results *results, u8 recvs_n, char *dir, if (res->test_status != MARGIN_TEST_OK) continue; + + port = res->recvn == 6 ? link->up_port.dev : link->down_port.dev; snprintf(path, pathlen, "%s/lmr_%0*x.%02x.%02x.%x_Rx%X_%s.csv", dir, - up_port->domain_16 == 0xffff ? 8 : 4, up_port->domain, up_port->bus, up_port->dev, - up_port->func, 10 + res->recvn - 1, timestamp); + port->domain_16 == 0xffff ? 8 : 4, port->domain, port->bus, port->dev, port->func, + 10 + res->recvn - 1, timestamp); csv = fopen(path, "w"); if (!csv) die("Error while saving %s\n", path); - fprintf(csv, "Lane,Lane Status,Left %% UI,Left ps,Left Steps,Left Status," - "Right %% UI,Right ps,Right Steps,Right Status," - "Time %% UI,Time ps,Time Steps,Time Status," - "Up mV,Up Steps,Up Status,Down mV,Down Steps,Down Status," - "Voltage mV,Voltage Steps,Voltage Status\n"); + fprintf(csv, "Lane,EW Min,EW Rec,EW,EH Min,EH Rec,EH,Lane Status,Left %% UI,Left " + "ps,Left Steps,Left Status,Right %% UI,Right ps,Right Steps,Right Status,Up " + "mV,Up Steps,Up Status,Down mV,Down Steps,Down Status\n"); - if (check_recv_weird(res, MARGIN_TIM_MIN, MARGIN_VOLT_MIN)) - lane_rating = WEIRD; + spec_ref_only = true; + grade_args = link->args.recv_args[res->recvn - 1]; + if (grade_args.t.criteria != 0) + { + spec_ref_only = false; + ew_min = grade_args.t.criteria; + ew_rec = grade_args.t.criteria; + } + else + { + ew_min = margin_ew_min[link_speed]; + ew_rec = margin_ew_rec[link_speed]; + } + if (grade_args.v.criteria != 0) + { + spec_ref_only = false; + eh_min = grade_args.v.criteria; + eh_rec = grade_args.v.criteria; + } else - lane_rating = INIT; + { + eh_min = margin_eh_min[link_speed]; + eh_rec = margin_eh_rec[link_speed]; + } for (int j = 0; j < res->lanes_n; j++) { + if (spec_ref_only) + lane_rating = INIT; + else + lane_rating = PASS; + lane = &(res->lanes[j]); - double left_ui = lane->steps[TIM_LEFT] * res->tim_coef; - double right_ui = lane->steps[TIM_RIGHT] * res->tim_coef; + double left_ps = lane->steps[TIM_LEFT] * res->tim_coef / 100.0 * margin_ui[link_speed]; + double right_ps = lane->steps[TIM_RIGHT] * res->tim_coef / 100.0 * margin_ui[link_speed]; double up_volt = lane->steps[VOLT_UP] * res->volt_coef; double down_volt = lane->steps[VOLT_DOWN] * res->volt_coef; - if (lane_rating != WEIRD) + double ew = left_ps; + if (params.ind_left_right_tim) + ew += right_ps; + else if (!grade_args.t.one_side_is_whole) + ew *= 2.0; + + double eh = 0.0; + if (params.volt_support) { - lane_rating = rate_lane(left_ui, MARGIN_TIM_MIN, MARGIN_TIM_RECOMMEND, INIT); - if (params.ind_left_right_tim) - lane_rating - = rate_lane(right_ui, MARGIN_TIM_MIN, MARGIN_TIM_RECOMMEND, lane_rating); - if (params.volt_support) - { - lane_rating = rate_lane(up_volt, MARGIN_VOLT_MIN, MARGIN_VOLT_MIN, lane_rating); - if (params.ind_up_down_volt) - lane_rating - = rate_lane(down_volt, MARGIN_VOLT_MIN, MARGIN_VOLT_MIN, lane_rating); - } + eh += up_volt; + if (params.ind_up_down_volt) + eh += down_volt; + else if (!grade_args.v.one_side_is_whole) + eh *= 2.0; } - fprintf(csv, "%d,%s,", lane->lane, grades[lane_rating]); - if (params.ind_left_right_tim) + lane_rating = rate_lane(ew, ew_min, ew_rec, lane_rating); + if (params.volt_support) + lane_rating = rate_lane(eh, eh_min, eh_rec, lane_rating); + + fprintf(csv, "%d,%f,", lane->lane, ew_min); + if (spec_ref_only) + fprintf(csv, "%f,", ew_rec); + else + fprintf(csv, "NA,"); + fprintf(csv, "%f,", ew); + if (params.volt_support) { - fprintf(csv, "%f,%f,%d,%s,%f,%f,%d,%s,NA,NA,NA,NA,", left_ui, - left_ui * ui[link_speed], lane->steps[TIM_LEFT], - sts_strings[lane->statuses[TIM_LEFT]], right_ui, right_ui * ui[link_speed], - lane->steps[TIM_RIGHT], sts_strings[lane->statuses[TIM_RIGHT]]); + fprintf(csv, "%f,", eh_min); + if (spec_ref_only) + fprintf(csv, "%f,", eh_rec); + else + fprintf(csv, "NA,"); + fprintf(csv, "%f,", eh); } else + fprintf(csv, "NA,NA,NA,"); + fprintf(csv, "%s,", grades[lane_rating]); + + fprintf(csv, "%f,%f,%d,%s,", left_ps * 100.0 / margin_ui[link_speed], left_ps, + lane->steps[TIM_LEFT], sts_strings[lane->statuses[TIM_LEFT]]); + + if (params.ind_left_right_tim) + fprintf(csv, "%f,%f,%d,%s,", right_ps * 100.0 / margin_ui[link_speed], right_ps, + lane->steps[TIM_RIGHT], sts_strings[lane->statuses[TIM_RIGHT]]); + else { - for (int k = 0; k < 8; k++) + for (int k = 0; k < 4; k++) fprintf(csv, "NA,"); - fprintf(csv, "%f,%f,%d,%s,", left_ui, left_ui * ui[link_speed], lane->steps[TIM_LEFT], - sts_strings[lane->statuses[TIM_LEFT]]); } if (params.volt_support) { + fprintf(csv, "%f,%d,%s,", up_volt, lane->steps[VOLT_UP], + sts_strings[lane->statuses[VOLT_UP]]); if (params.ind_up_down_volt) - { - fprintf(csv, "%f,%d,%s,%f,%d,%s,NA,NA,NA\n", up_volt, lane->steps[VOLT_UP], - sts_strings[lane->statuses[VOLT_UP]], down_volt, lane->steps[VOLT_DOWN], - sts_strings[lane->statuses[VOLT_DOWN]]); - } + fprintf(csv, "%f,%d,%s\n", down_volt, lane->steps[VOLT_DOWN], + sts_strings[lane->statuses[VOLT_DOWN]]); else - { - for (int k = 0; k < 6; k++) - fprintf(csv, "NA,"); - fprintf(csv, "%f,%d,%s\n", up_volt, lane->steps[VOLT_UP], - sts_strings[lane->statuses[VOLT_UP]]); - } + fprintf(csv, "NA,NA,NA\n"); } else { - for (int k = 0; k < 8; k++) + for (int k = 0; k < 5; k++) fprintf(csv, "NA,"); fprintf(csv, "NA\n"); } |