/* * 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 #include #include #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] [] [] [ [] ...]\n" "pcilmr --full []\n" "pcilmr --scan\n\n" "You can specify Downstream or Upstream Port of the Link.\nPort Specifier:\n" ":\t[:]:.\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 [,...]\tSpecify Receivers to select margining targets.\n" "\t\t\tDefault: all available Receivers (including Retimers).\n" "-t \t\tSpecify maximum number of steps for Time Margining.\n" "-v \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; }