| /* |
| * The PCI Utilities -- Log margining process |
| * |
| * 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 <stdarg.h> |
| #include <stdio.h> |
| |
| #include "lmr.h" |
| |
| bool margin_global_logging = false; |
| bool margin_print_domain = true; |
| |
| void |
| margin_log(char *format, ...) |
| { |
| va_list arg; |
| va_start(arg, format); |
| if (margin_global_logging) |
| vprintf(format, arg); |
| va_end(arg); |
| } |
| |
| void |
| margin_log_bdfs(struct pci_dev *down, struct pci_dev *up) |
| { |
| if (margin_print_domain) |
| margin_log("%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 |
| margin_log("%x:%x.%x -> %x:%x.%x", down->bus, down->dev, down->func, up->bus, up->dev, |
| up->func); |
| } |
| |
| 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.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: "); |
| int receivers_n = 2 + 2 * link->down_port.retimers_n; |
| for (int i = 1; i < receivers_n; i++) |
| margin_log("Rx(%X) - %d, ", 10 + i - 1, i); |
| margin_log("Rx(F) - 6\n"); |
| } |
| |
| void |
| margin_log_params(struct margin_params *params) |
| { |
| margin_log("Independent Error Sampler: %d\n", params->ind_error_sampler); |
| margin_log("Sample Reporting Method: %d\n", params->sample_report_method); |
| margin_log("Independent Left and Right Timing Margining: %d\n", params->ind_left_right_tim); |
| margin_log("Voltage Margining Supported: %d\n", params->volt_support); |
| margin_log("Independent Up and Down Voltage Margining: %d\n", params->ind_up_down_volt); |
| margin_log("Number of Timing Steps: %d\n", params->timing_steps); |
| margin_log("Number of Voltage Steps: %d\n", params->volt_steps); |
| margin_log("Max Timing Offset: %d\n", params->timing_offset); |
| margin_log("Max Voltage Offset: %d\n", params->volt_offset); |
| margin_log("Max Lanes: %d\n", params->max_lanes); |
| } |
| |
| void |
| margin_log_recvn(struct margin_recv *recv) |
| { |
| margin_log("\nReceiver = Rx(%X)\n", 10 + recv->recvn - 1); |
| } |
| |
| void |
| margin_log_receiver(struct margin_recv *recv) |
| { |
| margin_log("\nError Count Limit = %d\n", recv->error_limit); |
| 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); |
| |
| if (recv->lane_reversal) |
| { |
| margin_log("\nWarning: device uses Lane Reversal.\n"); |
| margin_log("However, utility uses logical lane numbers in arguments and for logging.\n"); |
| } |
| |
| if (recv->params->timing_offset == 0) |
| margin_log("\nWarning: Vendor chose not to report the Max Timing Offset.\n" |
| "Utility will use its max possible value - 50 (50%% UI).\n"); |
| if (recv->params->volt_support && recv->params->volt_offset == 0) |
| margin_log("\nWarning: Vendor chose not to report the Max Voltage Offset.\n" |
| "Utility will use its max possible value - 50 (500 mV).\n"); |
| } |
| |
| void |
| margin_log_margining(struct margin_lanes_data arg) |
| { |
| char *ind_dirs[] = { "Up", "Down", "Left", "Right" }; |
| char *non_ind_dirs[] = { "Voltage", "", "Timing" }; |
| |
| if (arg.verbosity > 0) |
| { |
| margin_log("\033[2K\rMargining - "); |
| if (arg.ind) |
| margin_log("%s", ind_dirs[arg.dir]); |
| else |
| margin_log("%s", non_ind_dirs[arg.dir]); |
| |
| u8 lanes_counter = 0; |
| margin_log(" - Lanes "); |
| margin_log("[%d", arg.lanes_numbers[0]); |
| for (int i = 1; i < arg.lanes_n; i++) |
| { |
| if (arg.lanes_numbers[i] - 1 == arg.lanes_numbers[i - 1]) |
| { |
| lanes_counter++; |
| if (lanes_counter == 1) |
| margin_log("-"); |
| if (i + 1 == arg.lanes_n) |
| margin_log("%d", arg.lanes_numbers[i]); |
| } |
| else |
| { |
| if (lanes_counter > 0) |
| margin_log("%d", arg.lanes_numbers[i - 1]); |
| margin_log(",%d", arg.lanes_numbers[i]); |
| lanes_counter = 0; |
| } |
| } |
| margin_log("]"); |
| |
| 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); |
| |
| fflush(stdout); |
| } |
| } |
| |
| void |
| margin_log_hw_quirks(struct margin_recv *recv) |
| { |
| switch (recv->dev->hw) |
| { |
| case MARGIN_ICE_LAKE_RC: |
| 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" |
| " - Force the use of 'one side is the whole' grading mode.\n"); |
| break; |
| default: |
| break; |
| } |
| } |