diff options
Diffstat (limited to 'lmr/margin_args.c')
-rw-r--r-- | lmr/margin_args.c | 302 |
1 files changed, 302 insertions, 0 deletions
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; +} |