#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <ofmt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/debug.h>
#include <pcieb_ioctl.h>
#include "pcieadm.h"
static int
pcieadm_link_pcieb_open(pcieadm_t *pcip, const char *device, bool write)
{
const char *drv;
VERIFY0(priv_addset(pcip->pia_priv_eff, PRIV_SYS_DEVICES));
VERIFY0(priv_addset(pcip->pia_priv_eff, PRIV_FILE_DAC_READ));
VERIFY0(priv_addset(pcip->pia_priv_eff, PRIV_FILE_DAC_SEARCH));
if (write) {
VERIFY0(priv_addset(pcip->pia_priv_eff, PRIV_FILE_WRITE));
VERIFY0(priv_addset(pcip->pia_priv_eff, PRIV_FILE_DAC_WRITE));
}
pcieadm_init_privs(pcip);
pcieadm_find_dip(pcip, device);
drv = di_driver_name(pcip->pia_devi);
if (drv == NULL || strcmp(drv, "pcieb") != 0) {
errx(EXIT_FAILURE, "device %s is not a PCIe bridge: found "
"driver %s, but expected pcieb", device,
drv == NULL ? "<none>" : drv);
}
for (di_minor_t m = di_minor_next(pcip->pia_devi, DI_MINOR_NIL);
m != NULL; m = di_minor_next(pcip->pia_devi, m)) {
if (strcmp(di_minor_name(m), "devctl") == 0) {
char buf[PATH_MAX], *mp;
int fd;
mp = di_devfs_minor_path(m);
if (mp == NULL) {
err(EXIT_FAILURE, "failed to get devfs path "
"for %s devctl minor", device);
}
if (snprintf(buf, sizeof (buf), "/devices%s", mp) >=
sizeof (buf)) {
errx(EXIT_FAILURE, "failed to construct devfs "
"minor path for %s devctl minor: internal "
"path buffer would have overflowed",
device);
}
di_devfs_path_free(mp);
if (setppriv(PRIV_SET, PRIV_EFFECTIVE,
pcip->pia_priv_eff) != 0) {
err(EXIT_FAILURE, "failed to raise privileges");
}
fd = open(buf, write ? O_RDWR : O_RDONLY);
if (fd < 0) {
err(EXIT_FAILURE,
"failed to open %s devctl minor %s",
device, buf);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE,
pcip->pia_priv_min) != 0) {
err(EXIT_FAILURE,
"failed to reduce privileges");
}
return (fd);
}
}
errx(EXIT_FAILURE, "failed to find devctl minor for %s", device);
}
static void
pcieadm_link_limit_usage(FILE *f)
{
(void) fprintf(f, "\tlink limit\t[-s speed] -d device\n");
}
static void
pcieadm_link_limit_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 link limit [-s speed] -d device\n",
pcieadm_progname);
(void) fprintf(stderr, "Print or set the administrative limit on the "
"corresponding PCIe link\n\n"
"\t-d device\tthe PCIe bridge to operate on (driver instance,"
"\n\t\t\t/devices path, or b/d/f)\n"
"\t-s speed\tlimit the device to the specified PCIe gen/speed "
"(e.g.\n\t\t\t2.5, 32, gen2, gen3, etc.)\n");
}
typedef struct {
uint32_t pls_speed;
const char *pls_gts;
const char *pls_gen;
const char *pls_alt;
} pcieadm_link_speed_t;
static const pcieadm_link_speed_t pcieadm_link_speeds[] = {
{ PCIEB_LINK_SPEED_GEN1, "2.5", "gen1", NULL },
{ PCIEB_LINK_SPEED_GEN2, "5.0", "gen2", "5" },
{ PCIEB_LINK_SPEED_GEN3, "8.0", "gen3", "8" },
{ PCIEB_LINK_SPEED_GEN4, "16.0", "gen4", "16" },
{ PCIEB_LINK_SPEED_GEN5, "32.0", "gen5", "32" },
{ PCIEB_LINK_SPEED_GEN6, "64.0", "gen6", "64" }
};
static uint32_t
pcieadm_parse_pcieb_speed(const char *str)
{
for (uint_t i = 0; i < ARRAY_SIZE(pcieadm_link_speeds); i++) {
const pcieadm_link_speed_t *pls = &pcieadm_link_speeds[i];
if (strcasecmp(str, pls->pls_gts) == 0 ||
strcasecmp(str, pls->pls_gen) == 0 ||
(pls->pls_alt != NULL &&
strcasecmp(str, pls->pls_alt) == 0)) {
return (pls->pls_speed);
}
}
errx(EXIT_FAILURE, "failed to parse speed: %s", str);
}
static const pcieadm_link_speed_t *
pcieadm_link_speed_lookup(uint32_t speed)
{
for (uint_t i = 0; i < ARRAY_SIZE(pcieadm_link_speeds); i++) {
if (pcieadm_link_speeds[i].pls_speed == speed)
return (&pcieadm_link_speeds[i]);
}
return (NULL);
}
static int
pcieadm_link_limit(pcieadm_t *pcip, int argc, char *argv[])
{
int c, ret = EXIT_SUCCESS;
const char *device = NULL, *speed = NULL;
pcieb_ioctl_target_speed_t pits;
while ((c = getopt(argc, argv, ":d:s:")) != -1) {
switch (c) {
case 'd':
device = optarg;
break;
case 's':
speed = optarg;
break;
case ':':
pcieadm_link_limit_help("Option -%c requires an "
"argument", optopt);
exit(EXIT_USAGE);
case '?':
default:
pcieadm_link_limit_help("unknown option: -%c",
optopt);
exit(EXIT_USAGE);
}
}
if (device == NULL) {
pcieadm_link_limit_help("missing required device argument "
"(-d)");
exit(EXIT_USAGE);
}
argc -= optind;
argv += optind;
if (argc != 0) {
errx(EXIT_USAGE, "encountered extraneous arguments starting "
"with %s", argv[0]);
}
int fd = pcieadm_link_pcieb_open(pcip, device, speed != NULL);
bzero(&pits, sizeof (pits));
if (speed != NULL) {
pits.pits_speed = pcieadm_parse_pcieb_speed(speed);
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) != 0)
err(EXIT_FAILURE, "failed to raise privileges");
if (ioctl(fd, PCIEB_IOCTL_SET_TARGET_SPEED, &pits) != 0) {
err(EXIT_FAILURE, "failed to set %s target speed",
device);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) != 0)
err(EXIT_FAILURE, "failed to reduce privileges");
} else {
const pcieadm_link_speed_t *pls;
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) != 0)
err(EXIT_FAILURE, "failed to raise privileges");
if (ioctl(fd, PCIEB_IOCTL_GET_TARGET_SPEED, &pits) != 0) {
err(EXIT_FAILURE, "failed to get %s target speed",
device);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) != 0)
err(EXIT_FAILURE, "failed to reduce privileges");
pls = pcieadm_link_speed_lookup(pits.pits_speed);
if (pls != NULL) {
(void) printf("Target speed: %s GT/s (%s)\n",
pls->pls_gts, pls->pls_gen);
} else {
(void) printf("Target speed: unknown speed value: "
"0x%x\n", pits.pits_speed);
}
if ((pits.pits_flags & ~PCIEB_FLAGS_ADMIN_SET) != 0) {
(void) printf("Unknown flags: 0x%x\n", pits.pits_flags);
} else if ((pits.pits_flags & PCIEB_FLAGS_ADMIN_SET) != 0) {
(void) printf("Flags: Admin Set Speed\n");
}
}
VERIFY0(close(fd));
return (ret);
}
static void
pcieadm_link_retrain_usage(FILE *f)
{
(void) fprintf(f, "\tlink retrain\t-d device\n");
}
static void
pcieadm_link_retrain_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 link retrain -d device\n",
pcieadm_progname);
(void) fprintf(stderr,
"Retrain the link on the specified upstream port by requesting\n"
"it via the PCIe Link Control register.\n\n"
"\t-d device\tthe PCIe bridge to operate on (driver instance,\n"
"\t\t\t/devices path, or b/d/f)\n");
}
static int
pcieadm_link_retrain(pcieadm_t *pcip, int argc, char *argv[])
{
int c, ret = EXIT_SUCCESS;
const char *device = NULL;
while ((c = getopt(argc, argv, ":d:")) != -1) {
switch (c) {
case 'd':
device = optarg;
break;
case ':':
pcieadm_link_retrain_help("Option -%c requires an "
"argument", optopt);
exit(EXIT_USAGE);
case '?':
default:
pcieadm_link_retrain_help("unknown option: -%c",
optopt);
exit(EXIT_USAGE);
}
}
if (device == NULL) {
pcieadm_link_retrain_help("missing required device argument "
"(-d)");
exit(EXIT_USAGE);
}
argc -= optind;
argv += optind;
if (argc != 0) {
errx(EXIT_USAGE, "encountered extraneous arguments starting "
"with %s", argv[0]);
}
int fd = pcieadm_link_pcieb_open(pcip, device, true);
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) != 0)
err(EXIT_FAILURE, "failed to raise privileges");
if (ioctl(fd, PCIEB_IOCTL_RETRAIN) != 0) {
err(EXIT_FAILURE, "failed to retrain link %s", device);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) != 0)
err(EXIT_FAILURE, "failed to reduce privileges");
VERIFY0(close(fd));
return (ret);
}
static const pcieadm_cmdtab_t pcieadm_cmds_link[] = {
{ "limit", pcieadm_link_limit, pcieadm_link_limit_usage },
{ "retrain", pcieadm_link_retrain, pcieadm_link_retrain_usage },
{ NULL }
};
int
pcieadm_link(pcieadm_t *pcip, int argc, char *argv[])
{
return (pcieadm_walk_tab(pcip, pcieadm_cmds_link, argc, argv));
}
void
pcieadm_link_usage(FILE *f)
{
pcieadm_walk_usage(pcieadm_cmds_link, f);
}