/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2022 Oxide Computer Company */ #include #include #include #include #include #include #include "pcieadm.h" typedef struct pcieadm_show_devs { pcieadm_t *psd_pia; ofmt_handle_t psd_ofmt; boolean_t psd_funcs; int psd_nfilts; char **psd_filts; boolean_t *psd_used; uint_t psd_nprint; } pcieadm_show_devs_t; typedef enum pcieadm_show_devs_otype { PCIEADM_SDO_VID, PCIEADM_SDO_DID, PCIEADM_SDO_BDF, PCIEADM_SDO_BDF_BUS, PCIEADM_SDO_BDF_DEV, PCIEADM_SDO_BDF_FUNC, PCIEADM_SDO_DRIVER, PCIEADM_SDO_TYPE, PCIEADM_SDO_VENDOR, PCIEADM_SDO_DEVICE, PCIEADM_SDO_PATH, PCIEADM_SDO_MAXSPEED, PCIEADM_SDO_MAXWIDTH, PCIEADM_SDO_CURSPEED, PCIEADM_SDO_CURWIDTH, PCIEADM_SDO_SUPSPEEDS } pcieadm_show_devs_otype_t; typedef struct pcieadm_show_devs_ofmt { int psdo_vid; int psdo_did; uint_t psdo_bus; uint_t psdo_dev; uint_t psdo_func; const char *psdo_path; const char *psdo_vendor; const char *psdo_device; const char *psdo_driver; int psdo_instance; int psdo_mwidth; int psdo_cwidth; int64_t psdo_mspeed; int64_t psdo_cspeed; int psdo_nspeeds; int64_t *psdo_sspeeds; } pcieadm_show_devs_ofmt_t; static uint_t pcieadm_speed2gen(int64_t speed) { if (speed == 2500000000LL) { return (1); } else if (speed == 5000000000LL) { return (2); } else if (speed == 8000000000LL) { return (3); } else if (speed == 16000000000LL) { return (4); } else if (speed == 32000000000LL) { return (5); } else { return (0); } } static const char * pcieadm_speed2str(int64_t speed) { if (speed == 2500000000LL) { return ("2.5"); } else if (speed == 5000000000LL) { return ("5.0"); } else if (speed == 8000000000LL) { return ("8.0"); } else if (speed == 16000000000LL) { return ("16.0"); } else if (speed == 32000000000LL) { return ("32.0"); } else { return (NULL); } } static boolean_t pcieadm_show_devs_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { const char *str; pcieadm_show_devs_ofmt_t *psdo = ofarg->ofmt_cbarg; boolean_t first = B_TRUE; switch (ofarg->ofmt_id) { case PCIEADM_SDO_BDF: if (snprintf(buf, buflen, "%x/%x/%x", psdo->psdo_bus, psdo->psdo_dev, psdo->psdo_func) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_BDF_BUS: if (snprintf(buf, buflen, "%x", psdo->psdo_bus) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_BDF_DEV: if (snprintf(buf, buflen, "%x", psdo->psdo_dev) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_BDF_FUNC: if (snprintf(buf, buflen, "%x", psdo->psdo_func) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_DRIVER: if (psdo->psdo_driver == NULL || psdo->psdo_instance == -1) { (void) snprintf(buf, buflen, "--"); } else if (snprintf(buf, buflen, "%s%d", psdo->psdo_driver, psdo->psdo_instance) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_PATH: if (strlcat(buf, psdo->psdo_path, buflen) >= buflen) { return (B_TRUE); } break; case PCIEADM_SDO_VID: if (psdo->psdo_vid == -1) { (void) strlcat(buf, "--", buflen); } else if (snprintf(buf, buflen, "%x", psdo->psdo_vid) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_DID: if (psdo->psdo_did == -1) { (void) strlcat(buf, "--", buflen); } else if (snprintf(buf, buflen, "%x", psdo->psdo_did) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_VENDOR: if (strlcat(buf, psdo->psdo_vendor, buflen) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_DEVICE: if (strlcat(buf, psdo->psdo_device, buflen) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_MAXWIDTH: if (psdo->psdo_mwidth <= 0) { (void) strlcat(buf, "--", buflen); } else if (snprintf(buf, buflen, "x%u", psdo->psdo_mwidth) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_CURWIDTH: if (psdo->psdo_cwidth <= 0) { (void) strlcat(buf, "--", buflen); } else if (snprintf(buf, buflen, "x%u", psdo->psdo_cwidth) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_MAXSPEED: str = pcieadm_speed2str(psdo->psdo_mspeed); if (str == NULL) { (void) strlcat(buf, "--", buflen); } else if (snprintf(buf, buflen, "%s GT/s", str) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_CURSPEED: str = pcieadm_speed2str(psdo->psdo_cspeed); if (str == NULL) { (void) strlcat(buf, "--", buflen); } else if (snprintf(buf, buflen, "%s GT/s", str) >= buflen) { return (B_FALSE); } break; case PCIEADM_SDO_SUPSPEEDS: buf[0] = 0; for (int i = 0; i < psdo->psdo_nspeeds; i++) { const char *str; str = pcieadm_speed2str(psdo->psdo_sspeeds[i]); if (str == NULL) { continue; } if (!first) { if (strlcat(buf, ",", buflen) >= buflen) { return (B_FALSE); } } first = B_FALSE; if (strlcat(buf, str, buflen) >= buflen) { return (B_FALSE); } } break; case PCIEADM_SDO_TYPE: if (pcieadm_speed2gen(psdo->psdo_mspeed) == 0 || psdo->psdo_mwidth == -1) { if (strlcat(buf, "PCI", buflen) >= buflen) { return (B_FALSE); } } else { if (snprintf(buf, buflen, "PCIe Gen %ux%u", pcieadm_speed2gen(psdo->psdo_mspeed), psdo->psdo_mwidth) >= buflen) { return (B_FALSE); } } break; default: abort(); } return (B_TRUE); } static const char *pcieadm_show_dev_fields = "bdf,type,driver,device"; static const char *pcieadm_show_dev_speeds = "bdf,driver,maxspeed,curspeed,maxwidth,curwidth,supspeeds"; static const ofmt_field_t pcieadm_show_dev_ofmt[] = { { "VID", 6, PCIEADM_SDO_VID, pcieadm_show_devs_ofmt_cb }, { "DID", 6, PCIEADM_SDO_DID, pcieadm_show_devs_ofmt_cb }, { "BDF", 8, PCIEADM_SDO_BDF, pcieadm_show_devs_ofmt_cb }, { "DRIVER", 15, PCIEADM_SDO_DRIVER, pcieadm_show_devs_ofmt_cb }, { "TYPE", 15, PCIEADM_SDO_TYPE, pcieadm_show_devs_ofmt_cb }, { "VENDOR", 30, PCIEADM_SDO_VENDOR, pcieadm_show_devs_ofmt_cb }, { "DEVICE", 30, PCIEADM_SDO_DEVICE, pcieadm_show_devs_ofmt_cb }, { "PATH", 30, PCIEADM_SDO_PATH, pcieadm_show_devs_ofmt_cb }, { "BUS", 4, PCIEADM_SDO_BDF_BUS, pcieadm_show_devs_ofmt_cb }, { "DEV", 4, PCIEADM_SDO_BDF_DEV, pcieadm_show_devs_ofmt_cb }, { "FUNC", 4, PCIEADM_SDO_BDF_FUNC, pcieadm_show_devs_ofmt_cb }, { "MAXSPEED", 10, PCIEADM_SDO_MAXSPEED, pcieadm_show_devs_ofmt_cb }, { "MAXWIDTH", 10, PCIEADM_SDO_MAXWIDTH, pcieadm_show_devs_ofmt_cb }, { "CURSPEED", 10, PCIEADM_SDO_CURSPEED, pcieadm_show_devs_ofmt_cb }, { "CURWIDTH", 10, PCIEADM_SDO_CURWIDTH, pcieadm_show_devs_ofmt_cb }, { "SUPSPEEDS", 20, PCIEADM_SDO_SUPSPEEDS, pcieadm_show_devs_ofmt_cb }, { NULL, 0, 0, NULL } }; static boolean_t pcieadm_show_devs_match(pcieadm_show_devs_t *psd, pcieadm_show_devs_ofmt_t *psdo) { char dinst[128], bdf[128]; if (psd->psd_nfilts == 0) { return (B_TRUE); } if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1) { (void) snprintf(dinst, sizeof (dinst), "%s%d", psdo->psdo_driver, psdo->psdo_instance); } (void) snprintf(bdf, sizeof (bdf), "%x/%x/%x", psdo->psdo_bus, psdo->psdo_dev, psdo->psdo_func); for (uint_t i = 0; i < psd->psd_nfilts; i++) { const char *filt = psd->psd_filts[i]; if (strcmp(filt, psdo->psdo_path) == 0) { psd->psd_used[i] = B_TRUE; return (B_TRUE); } if (strcmp(filt, bdf) == 0) { psd->psd_used[i] = B_TRUE; return (B_TRUE); } if (psdo->psdo_driver != NULL && strcmp(filt, psdo->psdo_driver) == 0) { psd->psd_used[i] = B_TRUE; return (B_TRUE); } if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1 && strcmp(filt, dinst) == 0) { psd->psd_used[i] = B_TRUE; return (B_TRUE); } if (strncmp("/devices", filt, strlen("/devices")) == 0) { filt += strlen("/devices"); } if (strcmp(filt, psdo->psdo_path) == 0) { psd->psd_used[i] = B_TRUE; return (B_TRUE); } } return (B_FALSE); } static int pcieadm_show_devs_walk_cb(di_node_t node, void *arg) { int nprop, *regs = NULL, *did, *vid, *mwidth, *cwidth; int64_t *mspeed, *cspeed, *sspeeds; char *path = NULL; pcieadm_show_devs_t *psd = arg; int ret = DI_WALK_CONTINUE; char venstr[64], devstr[64]; pcieadm_show_devs_ofmt_t oarg; pcidb_hdl_t *pcidb = psd->psd_pia->pia_pcidb; bzero(&oarg, sizeof (oarg)); path = di_devfs_path(node); if (path == NULL) { err(EXIT_FAILURE, "failed to construct devfs path for node: " "%s (%s)", di_node_name(node)); } nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®s); if (nprop <= 0) { errx(EXIT_FAILURE, "failed to lookup regs array for %s", path); } oarg.psdo_path = path; oarg.psdo_bus = PCI_REG_BUS_G(regs[0]); oarg.psdo_dev = PCI_REG_DEV_G(regs[0]); oarg.psdo_func = PCI_REG_FUNC_G(regs[0]); if (oarg.psdo_func != 0 && !psd->psd_funcs) { goto done; } oarg.psdo_driver = di_driver_name(node); oarg.psdo_instance = di_instance(node); nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did); if (nprop != 1) { oarg.psdo_did = -1; } else { oarg.psdo_did = (uint16_t)*did; } nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid); if (nprop != 1) { oarg.psdo_vid = -1; } else { oarg.psdo_vid = (uint16_t)*vid; } oarg.psdo_vendor = "--"; if (oarg.psdo_vid != -1) { pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb, oarg.psdo_vid); if (vend != NULL) { oarg.psdo_vendor = pcidb_vendor_name(vend); } else { (void) snprintf(venstr, sizeof (venstr), "Unknown vendor: 0x%x", oarg.psdo_vid); oarg.psdo_vendor = venstr; } } oarg.psdo_device = "--"; if (oarg.psdo_vid != -1 && oarg.psdo_did != -1) { pcidb_device_t *dev = pcidb_lookup_device(pcidb, oarg.psdo_vid, oarg.psdo_did); if (dev != NULL) { oarg.psdo_device = pcidb_device_name(dev); } else { (void) snprintf(devstr, sizeof (devstr), "Unknown device: 0x%x", oarg.psdo_did); oarg.psdo_device = devstr; } } nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "pcie-link-maximum-width", &mwidth); if (nprop != 1) { oarg.psdo_mwidth = -1; } else { oarg.psdo_mwidth = *mwidth; } nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "pcie-link-current-width", &cwidth); if (nprop != 1) { oarg.psdo_cwidth = -1; } else { oarg.psdo_cwidth = *cwidth; } nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, "pcie-link-maximum-speed", &mspeed); if (nprop != 1) { oarg.psdo_mspeed = -1; } else { oarg.psdo_mspeed = *mspeed; } nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, "pcie-link-current-speed", &cspeed); if (nprop != 1) { oarg.psdo_cspeed = -1; } else { oarg.psdo_cspeed = *cspeed; } nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, "pcie-link-supported-speeds", &sspeeds); if (nprop > 0) { oarg.psdo_nspeeds = nprop; oarg.psdo_sspeeds = sspeeds; } else { oarg.psdo_nspeeds = 0; oarg.psdo_sspeeds = NULL; } if (pcieadm_show_devs_match(psd, &oarg)) { ofmt_print(psd->psd_ofmt, &oarg); psd->psd_nprint++; } done: if (path != NULL) { di_devfs_path_free(path); } return (ret); } void pcieadm_show_devs_usage(FILE *f) { (void) fprintf(f, "\tshow-devs\t[-F] [-H] [-s | -o field[,...] [-p]] " "[filter...]\n"); } static void pcieadm_show_devs_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); (void) fprintf(stderr, "\n"); } (void) fprintf(stderr, "Usage: %s show-devs [-F] [-H] [-s | -o " "field[,...] [-p]] [filter...]\n", pcieadm_progname); (void) fprintf(stderr, "\nList PCI devices and functions in the " "system. Each selects a set\nof devices to show and " "can be a driver name, instance, /devices path, or\nb/d/f.\n\n" "\t-F\t\tdo not display PCI functions\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparsable output (requires -o)\n" "\t-s\t\tlist speeds and widths\n"); } int pcieadm_show_devs(pcieadm_t *pcip, int argc, char *argv[]) { int c, ret; uint_t flags = 0; const char *fields = NULL; pcieadm_show_devs_t psd; pcieadm_di_walk_t walk; ofmt_status_t oferr; boolean_t parse = B_FALSE; boolean_t speeds = B_FALSE; /* * show-devs relies solely on the devinfo snapshot we already took. * Formalize our privs immediately. */ pcieadm_init_privs(pcip); bzero(&psd, sizeof (psd)); psd.psd_pia = pcip; psd.psd_funcs = B_TRUE; while ((c = getopt(argc, argv, ":FHo:ps")) != -1) { switch (c) { case 'F': psd.psd_funcs = B_FALSE; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case 'H': flags |= OFMT_NOHEADER; break; case 's': speeds = B_TRUE; break; case 'o': fields = optarg; break; case ':': pcieadm_show_devs_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': pcieadm_show_devs_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (fields != NULL && speeds) { errx(EXIT_USAGE, "-s cannot be used with with -o"); } if (fields == NULL) { if (speeds) { fields = pcieadm_show_dev_speeds; } else { fields = pcieadm_show_dev_fields; } } argc -= optind; argv += optind; if (argc > 0) { psd.psd_nfilts = argc; psd.psd_filts = argv; psd.psd_used = calloc(argc, sizeof (boolean_t)); if (psd.psd_used == NULL) { err(EXIT_FAILURE, "failed to allocate filter tracking " "memory"); } } oferr = ofmt_open(fields, pcieadm_show_dev_ofmt, flags, 0, &psd.psd_ofmt); ofmt_check(oferr, parse, psd.psd_ofmt, pcieadm_ofmt_errx, warnx); walk.pdw_arg = &psd; walk.pdw_func = pcieadm_show_devs_walk_cb; pcieadm_di_walk(pcip, &walk); ret = EXIT_SUCCESS; for (int i = 0; i < psd.psd_nfilts; i++) { if (!psd.psd_used[i]) { warnx("filter '%s' did not match any devices", psd.psd_filts[i]); ret = EXIT_FAILURE; } } if (psd.psd_nprint == 0) { ret = EXIT_FAILURE; } return (ret); }