mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
util_scandir doesn't work with "/" at the end of the format string. Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
935 lines
25 KiB
C
935 lines
25 KiB
C
/*
|
|
* lscss - Tool to list information about subchannels
|
|
*
|
|
* Copyright IBM Corp. 2003, 2017
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "lib/ccw.h"
|
|
#include "lib/util_base.h"
|
|
#include "lib/util_file.h"
|
|
#include "lib/util_libc.h"
|
|
#include "lib/util_list.h"
|
|
#include "lib/util_opt.h"
|
|
#include "lib/util_path.h"
|
|
#include "lib/util_prg.h"
|
|
#include "lib/util_rec.h"
|
|
#include "lib/util_scandir.h"
|
|
#include "lib/util_panic.h"
|
|
#include "lib/zt_common.h"
|
|
|
|
#include "misc.h"
|
|
|
|
/*
|
|
* Numbers for lscss command options that do not have a short form
|
|
*/
|
|
#define OPT_AVAIL 256 /* --avail */
|
|
#define OPT_VPM 257 /* --vpm */
|
|
#define OPT_IO 258 /* --io */
|
|
#define OPT_CHSC 259 /* --chsc */
|
|
#define OPT_EADM 260 /* --eadm */
|
|
#define OPT_VFIO 261 /* --vfio */
|
|
|
|
/* Bus_id format for subchannel or device id */
|
|
#define ID_FORMAT "^[[:xdigit:]]{1,2}[.][[:xdigit:]][.][[:xdigit:]]{4}$"
|
|
/* UUID format */
|
|
#define UUID_FORMAT "^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$"
|
|
|
|
/* Misc constants */
|
|
#define MAX_BUF_SIZE 256
|
|
#define PREFIX_ID_LENGTH 4
|
|
#define SHORT_ID_LENGTH 4
|
|
#define CHPIDS_SEGMENT_LENGTH 8
|
|
|
|
/* Range of subchannel or device identifiers */
|
|
struct range {
|
|
struct util_list_node node; /* Pointers to previous and next range */
|
|
struct ccw_devid lower; /* Lower sch_id or bus_id */
|
|
struct ccw_devid upper; /* Upper sch_id or bus_id */
|
|
};
|
|
|
|
/* Device type and model */
|
|
struct devtype {
|
|
struct util_list_node node; /* Pointers to previous and next devtype */
|
|
bool no_model; /* <type> format input */
|
|
unsigned int type; /* Device type */
|
|
unsigned int model; /* Specific model of the specified device type */
|
|
};
|
|
|
|
/* Subchannel types */
|
|
enum sch_type {
|
|
SUBCHANNEL_TYPE_IO = 0, /* I/O subchannels */
|
|
SUBCHANNEL_TYPE_CHSC = 1, /* CHSC subchannels */
|
|
SUBCHANNEL_TYPE_EADM = 3, /* EADM subchannels */
|
|
};
|
|
|
|
/*
|
|
* Private data
|
|
*/
|
|
static struct lscss_cmd_opts {
|
|
/* Boolean flags to indicate wich command options are in effect */
|
|
bool opt_short; /* -s or --short */
|
|
bool opt_devtype; /* -t or --devtype */
|
|
bool opt_devrange; /* -d or --devrange */
|
|
bool opt_avail; /* --avail */
|
|
bool opt_vpm; /* --vpm */
|
|
bool opt_uppercase; /* -u or --uppercase */
|
|
bool opt_io; /* --io */
|
|
bool opt_chsc; /* --chsc */
|
|
bool opt_eadm; /* --eadm */
|
|
bool opt_vfio; /* --vfio */
|
|
/* List of device types for the output limitation */
|
|
int dev_count;
|
|
struct util_list *devtypes;
|
|
/* List of sch_id or bus_id ranges for the output limitation */
|
|
int rng_count;
|
|
struct util_list *ranges;
|
|
} cmd;
|
|
|
|
/*
|
|
* Program configuration
|
|
*/
|
|
static const struct util_prg prg = {
|
|
.desc = "List information about available subchannels.\n"
|
|
"\nRANGE\n"
|
|
" ID Select single subchannel by ID, e.g. 0.0.004f or 4f\n"
|
|
" FROM-TO Select range of subchannels between FROM and TO\n"
|
|
" ID1,ID2-ID3,... Select list of subchannels or subchannel ranges",
|
|
.args = "[RANGE]",
|
|
.copyright_vec = {
|
|
{
|
|
.owner = "IBM Corp.",
|
|
.pub_first = 2017,
|
|
.pub_last = 2017,
|
|
},
|
|
UTIL_PRG_COPYRIGHT_END
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Command line options
|
|
*/
|
|
static struct util_opt opt_vec[] = {
|
|
UTIL_OPT_SECTION("OPTIONS"),
|
|
{
|
|
.option = { "short", no_argument, NULL, 's'},
|
|
.desc = "Shorten IDs by removing leading \"0.0.\" "
|
|
"Note: only IDs beginning with \"0.0.\" "
|
|
"will be displayed in this case.",
|
|
},
|
|
{
|
|
.option = { "devtype", required_argument, NULL, 't'},
|
|
.argument = "TYPE,..",
|
|
.desc = "For IO subchannels, limit output to devices of "
|
|
"the given TYPE (DEVTYPE[/MODEL])",
|
|
},
|
|
{
|
|
.option = { "devrange", no_argument, NULL, 'd'},
|
|
.desc = "Indicate that RANGE refers to device identifiers",
|
|
},
|
|
{
|
|
.option = { "avail", no_argument, NULL, OPT_AVAIL},
|
|
.desc = "Show availability attribute of IO devices",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
},
|
|
{
|
|
.option = { "vpm", no_argument, NULL, OPT_VPM},
|
|
.desc = "Show verified path mask",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
},
|
|
{
|
|
.option = { "uppercase", no_argument, NULL, 'u'},
|
|
.desc = "Print values using uppercase",
|
|
},
|
|
{
|
|
.option = { "io", no_argument, NULL, OPT_IO},
|
|
.desc = "Show IO subchannels (default)",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
},
|
|
{
|
|
.option = { "chsc", no_argument, NULL, OPT_CHSC},
|
|
.desc = "Show CHSC subchannels",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
},
|
|
{
|
|
.option = { "eadm", no_argument, NULL, OPT_EADM},
|
|
.desc = "Show EADM subchannels",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
},
|
|
{
|
|
.option = { "vfio", no_argument, NULL, OPT_VFIO},
|
|
.desc = "Show VFIO subchannel information",
|
|
.flags = UTIL_OPT_FLAG_NOSHORT,
|
|
},
|
|
{
|
|
.option = { "all", no_argument, NULL, 'a'},
|
|
.desc = "Show subchannels of all types",
|
|
},
|
|
UTIL_OPT_HELP,
|
|
UTIL_OPT_VERSION,
|
|
UTIL_OPT_END
|
|
};
|
|
|
|
/*
|
|
* Add new subchannel or device range to the ranges list for further output
|
|
* limitation
|
|
*/
|
|
static void add_new_range(const char *lower_id, const char *upper_id)
|
|
{
|
|
struct range *rng;
|
|
|
|
rng = util_malloc(sizeof(*rng));
|
|
if (!ccw_parse_str(&rng->lower, lower_id))
|
|
errx(EXIT_FAILURE, "Invalid ID specified: %s", lower_id);
|
|
if (!ccw_parse_str(&rng->upper, upper_id))
|
|
errx(EXIT_FAILURE, "Invalid ID specified: %s", upper_id);
|
|
util_list_add_tail(cmd.ranges, rng);
|
|
}
|
|
|
|
/*
|
|
* Check if subchannel or device bus_id is within the provided range
|
|
*/
|
|
static bool id_in_range(struct range *rng, struct ccw_devid *id)
|
|
{
|
|
/* First compare cssid, then ssid and finally devno */
|
|
if (id->cssid == rng->lower.cssid) {
|
|
if (id->ssid == rng->lower.ssid) {
|
|
if (id->devno < rng->lower.devno)
|
|
return false;
|
|
} else if (id->ssid < rng->lower.ssid) {
|
|
return false;
|
|
}
|
|
} else if (id->cssid < rng->lower.cssid) {
|
|
return false;
|
|
}
|
|
|
|
if (id->cssid == rng->upper.cssid) {
|
|
if (id->ssid == rng->upper.ssid) {
|
|
if (id->devno > rng->upper.devno)
|
|
return false;
|
|
} else if (id->ssid > rng->upper.ssid) {
|
|
return false;
|
|
}
|
|
} else if (id->cssid > rng->upper.cssid) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Check if subchannel or device id matches any range in the ranges list
|
|
*/
|
|
static bool id_in_ranges_list(const char *id)
|
|
{
|
|
struct ccw_devid ccw_id;
|
|
struct range *rng;
|
|
|
|
if (!ccw_parse_str(&ccw_id, id))
|
|
errx(EXIT_FAILURE, "Invalid subchannel directory '%s'", id);
|
|
util_list_iterate(cmd.ranges, rng) {
|
|
if (id_in_range(rng, &ccw_id))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Parse a string into a devtype structure
|
|
*
|
|
* @param[in,out] dt Pointer to devtype structure to be initialized
|
|
* @parm[in] id String to parse
|
|
*
|
|
* @returns true if the input string has been parsed successfuly;
|
|
* otherwise false
|
|
*/
|
|
static bool parse_devtype_str(struct devtype *dt, const char *id)
|
|
{
|
|
char d;
|
|
|
|
if (strncasecmp(id, "0x", 2) == 0)
|
|
return false;
|
|
dt->no_model = false;
|
|
if (sscanf(id, "%4x %c", &dt->type, &d) == 1) {
|
|
/* Process <type> input format (maximum length of 4 characters) */
|
|
dt->no_model = true;
|
|
} else if (sscanf(id, "%4x/%2x %c", &dt->type, &dt->model,
|
|
&d) != 2) {
|
|
/*
|
|
* Process <type>/<model> input format (maximum lengths of 4 and 2
|
|
* characters respectively)
|
|
*/
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Add new device type to the devtypes list for further output limitation
|
|
*/
|
|
static void add_new_devtype(const char *dtype)
|
|
{
|
|
struct devtype *dt;
|
|
|
|
dt = util_malloc(sizeof(struct devtype));
|
|
if (!parse_devtype_str(dt, dtype))
|
|
errx(EXIT_FAILURE, "Invalid device type specified: %s", dtype);
|
|
util_list_add_tail(cmd.devtypes, dt);
|
|
}
|
|
|
|
/*
|
|
* Check if provided device type matches any from the device types list
|
|
*/
|
|
static bool in_devtypes_list(char *dtype)
|
|
{
|
|
struct devtype arg_dt;
|
|
struct devtype *dt;
|
|
|
|
if (!parse_devtype_str(&arg_dt, dtype))
|
|
errx(EXIT_FAILURE, "Invalid device type detected: %s", dtype);
|
|
util_list_iterate(cmd.devtypes, dt) {
|
|
if (arg_dt.type == dt->type) {
|
|
if (dt->no_model || arg_dt.model == dt->model)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Fill in the device related entry fields (devtyp, cutype, use, avail)
|
|
*
|
|
* @returns 0 - devtype matches devtypes list, device information filled in
|
|
* 1 - devtype does not match devtypes list, skip entry
|
|
*/
|
|
static int fill_device_info(struct util_rec *rec, char *path, char *device)
|
|
{
|
|
unsigned long int val_ul;
|
|
char buf[MAX_BUF_SIZE];
|
|
|
|
if (!path || !device) {
|
|
if (cmd.opt_devtype && cmd.dev_count > 0)
|
|
return 1;
|
|
util_rec_set(rec, "devtyp", "");
|
|
util_rec_set(rec, "cutype", "");
|
|
util_rec_set(rec, "use", "");
|
|
if (cmd.opt_avail)
|
|
util_rec_set(rec, "avail", "");
|
|
return 0;
|
|
}
|
|
|
|
if (util_file_read_line(buf, sizeof(buf), "%s/%s/devtype",
|
|
path, device) == 0) {
|
|
if (strcmp(buf, "n/a") == 0)
|
|
/* Special case for 'n/a' devtype */
|
|
strncpy(buf, "0000/00", sizeof(buf));
|
|
if (cmd.opt_devtype && cmd.dev_count > 0 &&
|
|
!in_devtypes_list(buf))
|
|
return 1;
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "devtyp", "%s", buf);
|
|
} else {
|
|
if (cmd.opt_devtype && cmd.dev_count > 0)
|
|
return 1;
|
|
util_rec_set(rec, "devtyp", "");
|
|
}
|
|
|
|
if (util_file_read_line(buf, sizeof(buf), "%s/%s/cutype",
|
|
path, device) == 0) {
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "cutype", "%s", buf);
|
|
} else {
|
|
util_rec_set(rec, "cutype", "");
|
|
}
|
|
|
|
if (util_file_read_ul(&val_ul, 10, "%s/%s/online", path, device) == 0) {
|
|
if (val_ul == 1) {
|
|
snprintf(buf, sizeof(buf), "yes");
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "use", "%s", buf);
|
|
} else {
|
|
util_rec_set(rec, "use", "");
|
|
}
|
|
} else {
|
|
util_rec_set(rec, "use", "");
|
|
}
|
|
|
|
if (cmd.opt_avail) {
|
|
if (util_file_read_line(buf, sizeof(buf), "%s/%s/availability",
|
|
path, device) == 0) {
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "avail", "%s", buf);
|
|
} else {
|
|
util_rec_set(rec, "avail", "");
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool is_sch_vfio(char *path)
|
|
{
|
|
char lnk[PATH_MAX], driver_path[PATH_MAX];
|
|
ssize_t rc;
|
|
|
|
snprintf(lnk, PATH_MAX, "%s/driver", path);
|
|
rc = readlink(lnk, driver_path, PATH_MAX);
|
|
if (rc < 0)
|
|
return false;
|
|
|
|
util_assert(rc < (PATH_MAX - 1),
|
|
"Internal error: Symlink name too long");
|
|
driver_path[rc] = '\0';
|
|
|
|
if (strcmp(basename(driver_path), "vfio_ccw") == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Fill in the MDEV id entry field
|
|
*
|
|
* @returns 0 - MDEV id information filled in
|
|
* 1 - skip entry
|
|
*/
|
|
static int fill_vfio_devid(struct util_rec *rec, char *path)
|
|
{
|
|
char *device, buf[MAX_BUF_SIZE];
|
|
struct dirent **de_vec;
|
|
int count;
|
|
|
|
if (!is_sch_vfio(path))
|
|
return 1;
|
|
|
|
/* Find and process mdev device directory */
|
|
count = util_scandir(&de_vec, alphasort, path, "%s", UUID_FORMAT);
|
|
if (count > 0) {
|
|
device = de_vec[0]->d_name;
|
|
snprintf(buf, sizeof(buf), "%s", device);
|
|
} else {
|
|
strncpy(buf, "none", sizeof(buf));
|
|
}
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "mdev", "%s", buf);
|
|
util_scandir_free(de_vec, count);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Fill in the CCW device id entry field
|
|
*
|
|
* @returns 0 - CCW device id information filled in
|
|
* 1 - skip entry
|
|
*/
|
|
static int fill_io_devid(struct util_rec *rec, char *path)
|
|
{
|
|
char *device, buf[MAX_BUF_SIZE];
|
|
struct dirent **de_vec;
|
|
int count;
|
|
|
|
/* Find and process device directory */
|
|
count = util_scandir(&de_vec, alphasort, path, "%s", ID_FORMAT);
|
|
if (count > 0) {
|
|
device = de_vec[0]->d_name;
|
|
if (cmd.opt_short) {
|
|
/* Display only 0.0.xxxx devices for --short */
|
|
if (strncmp(device, "0.0.", PREFIX_ID_LENGTH) != 0)
|
|
return 1;
|
|
snprintf(buf, sizeof(buf), "%s", device +
|
|
strlen(device) - SHORT_ID_LENGTH);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s", device);
|
|
}
|
|
if (cmd.opt_devrange && cmd.rng_count > 0 &&
|
|
!id_in_ranges_list(device)) {
|
|
util_scandir_free(de_vec, count);
|
|
return 1;
|
|
}
|
|
if (fill_device_info(rec, path, device) != 0) {
|
|
util_scandir_free(de_vec, count);
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (cmd.opt_devrange && cmd.rng_count > 0) {
|
|
util_scandir_free(de_vec, count);
|
|
return 1;
|
|
}
|
|
strncpy(buf, "none", sizeof(buf));
|
|
fill_device_info(rec, NULL, NULL);
|
|
}
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "device", "%s", buf);
|
|
util_scandir_free(de_vec, count);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Print IO subchannel entry
|
|
*/
|
|
static void print_sch_io(struct util_rec *rec, char *path, char *sch_dir)
|
|
{
|
|
unsigned int pim, pam, pom;
|
|
char buf[MAX_BUF_SIZE];
|
|
|
|
/* Fill in subchannel ID */
|
|
if (cmd.opt_short) {
|
|
/* Display only 0.0.xxxx subchannels for --short */
|
|
if (strncmp(sch_dir, "0.0.", PREFIX_ID_LENGTH) != 0)
|
|
return;
|
|
snprintf(buf, sizeof(buf), "%s", sch_dir +
|
|
strlen(sch_dir) - SHORT_ID_LENGTH);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s", sch_dir);
|
|
}
|
|
if (!cmd.opt_devrange && cmd.rng_count > 0 &&
|
|
!id_in_ranges_list(sch_dir))
|
|
return;
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "subch", "%s", buf);
|
|
if (cmd.opt_vfio) {
|
|
if (fill_vfio_devid(rec, path) != 0)
|
|
return;
|
|
} else if (fill_io_devid(rec, path) != 0)
|
|
return;
|
|
/* Fill in PIM-PAM-POM data */
|
|
if (util_file_read_line(buf, sizeof(buf), "%s/pimpampom", path) == 0) {
|
|
if (sscanf(buf, "%x %x %x", &pim, &pam, &pom) == 3) {
|
|
if (cmd.opt_uppercase) {
|
|
util_rec_set(rec, "pim", "%02X", pim);
|
|
util_rec_set(rec, "pam", "%02X", pam);
|
|
util_rec_set(rec, "pom", "%02X", pom);
|
|
} else {
|
|
util_rec_set(rec, "pim", "%02x", pim);
|
|
util_rec_set(rec, "pam", "%02x", pam);
|
|
util_rec_set(rec, "pom", "%02x", pom);
|
|
}
|
|
} else {
|
|
util_rec_set(rec, "pim", "");
|
|
util_rec_set(rec, "pam", "");
|
|
util_rec_set(rec, "pom", "");
|
|
}
|
|
} else {
|
|
util_rec_set(rec, "pim", "");
|
|
util_rec_set(rec, "pam", "");
|
|
util_rec_set(rec, "pom", "");
|
|
}
|
|
/* Fill in VPM data */
|
|
if (cmd.opt_vpm) {
|
|
if (util_file_read_line(buf, sizeof(buf),
|
|
"%s/vpm", path) == 0) {
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "vpm", "%s", buf);
|
|
} else {
|
|
util_rec_set(rec, "vpm", "");
|
|
}
|
|
}
|
|
/*
|
|
* Fill in CHPIDs data.
|
|
* Since chpids are stored as a list of two digit hexadecimal numbers,
|
|
* we first read it as a single string, then eliminate blanks from it
|
|
* and then break in two 8-char segments.
|
|
*/
|
|
if (util_file_read_line(buf, sizeof(buf), "%s/chpids", path) == 0) {
|
|
misc_str_remove_symbol(buf, ' ');
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "chpids", "%.8s %.8s", buf,
|
|
buf + CHPIDS_SEGMENT_LENGTH);
|
|
} else {
|
|
util_rec_set(rec, "chpids", "");
|
|
}
|
|
|
|
util_rec_print(rec);
|
|
}
|
|
|
|
/*
|
|
* Print CHSC subchannel entry
|
|
*/
|
|
static void print_sch_chsc(struct util_rec *rec, char *sch_dir)
|
|
{
|
|
char buf[MAX_BUF_SIZE];
|
|
|
|
/* Skip entry if devrange or devtype option is active */
|
|
if (cmd.opt_devrange && cmd.rng_count > 0)
|
|
return;
|
|
if (cmd.opt_devtype && cmd.dev_count > 0)
|
|
return;
|
|
/* Fill in subchannel ID */
|
|
if (cmd.opt_short) {
|
|
/* Display only 0.0.xxxx subchannels for --short */
|
|
if (strncmp(sch_dir, "0.0.", PREFIX_ID_LENGTH) != 0)
|
|
return;
|
|
snprintf(buf, sizeof(buf), "%s", sch_dir +
|
|
strlen(sch_dir) - SHORT_ID_LENGTH);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s", sch_dir);
|
|
}
|
|
if (cmd.rng_count > 0 && !id_in_ranges_list(sch_dir))
|
|
return;
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "subch", "%s", buf);
|
|
/* Device field is always 'n/a' for CHSC */
|
|
strncpy(buf, "n/a", sizeof(buf));
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "device", "%s", buf);
|
|
|
|
util_rec_print(rec);
|
|
}
|
|
|
|
/*
|
|
* Print EADM subchannel entry
|
|
*/
|
|
static void print_sch_eadm(struct util_rec *rec, char *sch_dir)
|
|
{
|
|
char buf[MAX_BUF_SIZE];
|
|
|
|
/* Skip entry if devrange or devtype option is active */
|
|
if (cmd.opt_devrange && cmd.rng_count > 0)
|
|
return;
|
|
if (cmd.opt_devtype && cmd.dev_count > 0)
|
|
return;
|
|
/* Fill in subchannel ID */
|
|
if (cmd.opt_short) {
|
|
/* Display only 0.0.xxxx subchannels for --short */
|
|
if (strncmp(sch_dir, "0.0.", PREFIX_ID_LENGTH) != 0)
|
|
return;
|
|
snprintf(buf, sizeof(buf), "%s", sch_dir +
|
|
strlen(sch_dir) - SHORT_ID_LENGTH);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s", sch_dir);
|
|
}
|
|
if (cmd.rng_count > 0 && !id_in_ranges_list(sch_dir))
|
|
return;
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "subch", "%s", buf);
|
|
/* Device field is always 'n/a' for EADM */
|
|
strncpy(buf, "n/a", sizeof(buf));
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "device", "%s", buf);
|
|
|
|
util_rec_print(rec);
|
|
}
|
|
|
|
/*
|
|
* Print information for all defunct devices in one css
|
|
*/
|
|
static void print_defunct_devices(struct util_rec *rec, char *path)
|
|
{
|
|
char *device, buf[MAX_BUF_SIZE];
|
|
struct dirent **de_vec;
|
|
int i, count;
|
|
|
|
/* Process all the devices within defunct directory */
|
|
count = util_scandir(&de_vec, alphasort, path, "%s", ID_FORMAT);
|
|
for (i = 0; i < count; i++) {
|
|
device = de_vec[i]->d_name;
|
|
if (cmd.opt_short) {
|
|
/* Display only 0.0.xxxx devices for --short */
|
|
if (strncmp(device, "0.0.", PREFIX_ID_LENGTH) != 0)
|
|
return;
|
|
snprintf(buf, sizeof(buf), "%s", device +
|
|
strlen(device) - SHORT_ID_LENGTH);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s", device);
|
|
}
|
|
if (cmd.opt_devrange && cmd.rng_count > 0 &&
|
|
!id_in_ranges_list(device))
|
|
continue;
|
|
|
|
if (fill_device_info(rec, path, device) != 0)
|
|
continue;
|
|
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "device", "%s", buf);
|
|
/* Subchannel field is always 'n/a' for defunct devices */
|
|
strncpy(buf, "n/a", sizeof(buf));
|
|
if (cmd.opt_uppercase)
|
|
util_str_toupper(buf);
|
|
util_rec_set(rec, "subch", "%s", buf);
|
|
/* Other fields are blank */
|
|
util_rec_set(rec, "pim", "");
|
|
util_rec_set(rec, "pam", "");
|
|
util_rec_set(rec, "pom", "");
|
|
if (cmd.opt_vpm)
|
|
util_rec_set(rec, "vpm", "");
|
|
util_rec_set(rec, "chpids", "");
|
|
|
|
util_rec_print(rec);
|
|
}
|
|
util_scandir_free(de_vec, count);
|
|
}
|
|
|
|
/*
|
|
* Loop through subchannel directories and print entries of specified type
|
|
*/
|
|
static void print_subchannels_of_type(enum sch_type type_requested,
|
|
struct util_rec *rec)
|
|
{
|
|
unsigned long int type_ul;
|
|
struct dirent **de_vec;
|
|
char *path, *sch_dir;
|
|
int i, count;
|
|
|
|
path = util_path_sysfs("bus/css/devices");
|
|
count = util_scandir(&de_vec, alphasort, path, "%s", ID_FORMAT);
|
|
free(path);
|
|
for (i = 0; i < count; i++) {
|
|
sch_dir = de_vec[i]->d_name;
|
|
path = util_path_sysfs("bus/css/devices/%s", sch_dir);
|
|
if (util_file_read_ul(&type_ul, 10, "%s/type", path) == 0) {
|
|
if (type_ul != type_requested)
|
|
continue;
|
|
if (type_ul == SUBCHANNEL_TYPE_IO)
|
|
print_sch_io(rec, path, sch_dir);
|
|
else if (type_ul == SUBCHANNEL_TYPE_CHSC)
|
|
print_sch_chsc(rec, sch_dir);
|
|
else if (type_ul == SUBCHANNEL_TYPE_EADM)
|
|
print_sch_eadm(rec, sch_dir);
|
|
} else {
|
|
/*
|
|
* Subchannels with no type identifier treated as
|
|
* IO subchannels
|
|
*/
|
|
if (type_requested == SUBCHANNEL_TYPE_IO)
|
|
print_sch_io(rec, path, sch_dir);
|
|
}
|
|
free(path);
|
|
}
|
|
util_scandir_free(de_vec, count);
|
|
/* Process defunct devices (if no subchannel range is specified) */
|
|
if (!cmd.opt_devrange && cmd.rng_count > 0)
|
|
return;
|
|
path = util_path_sysfs("devices");
|
|
count = util_scandir(&de_vec, alphasort, path, "css.*");
|
|
free(path);
|
|
for (i = 0; i < count; i++) {
|
|
path = util_path_sysfs("devices/%s/defunct", de_vec[i]->d_name);
|
|
print_defunct_devices(rec, path);
|
|
free(path);
|
|
}
|
|
util_scandir_free(de_vec, count);
|
|
}
|
|
|
|
/*
|
|
* Print subchannels table
|
|
*/
|
|
static void cmd_lscss(void)
|
|
{
|
|
struct util_rec *rec;
|
|
|
|
if (cmd.opt_io) {
|
|
rec = util_rec_new_wide("-");
|
|
util_rec_def(rec, "device", UTIL_REC_ALIGN_LEFT, 8, "Device");
|
|
util_rec_def(rec, "subch", UTIL_REC_ALIGN_LEFT, 9, "Subchan.");
|
|
util_rec_def(rec, "devtyp", UTIL_REC_ALIGN_LEFT, 7, "DevType");
|
|
util_rec_def(rec, "cutype", UTIL_REC_ALIGN_LEFT, 7, "CU Type");
|
|
util_rec_def(rec, "use", UTIL_REC_ALIGN_LEFT, 4, "Use");
|
|
util_rec_def(rec, "pim", UTIL_REC_ALIGN_LEFT, 3, "PIM");
|
|
util_rec_def(rec, "pam", UTIL_REC_ALIGN_LEFT, 3, "PAM");
|
|
if (cmd.opt_vpm) {
|
|
util_rec_def(rec, "pom", UTIL_REC_ALIGN_LEFT, 3, "POM");
|
|
util_rec_def(rec, "vpm", UTIL_REC_ALIGN_LEFT, 3, "VPM");
|
|
} else
|
|
util_rec_def(rec, "pom", UTIL_REC_ALIGN_LEFT, 4, "POM");
|
|
util_rec_def(rec, "chpids", UTIL_REC_ALIGN_LEFT, 17, "CHPIDs");
|
|
if (cmd.opt_avail)
|
|
util_rec_def(rec, "avail", UTIL_REC_ALIGN_LEFT, 6,
|
|
"Avail.");
|
|
/* Print header only if other subchannel types are requested */
|
|
if (cmd.opt_chsc || cmd.opt_eadm || cmd.opt_vfio)
|
|
printf("IO Subchannels and Devices:\n");
|
|
util_rec_print_hdr(rec);
|
|
print_subchannels_of_type(SUBCHANNEL_TYPE_IO, rec);
|
|
util_rec_free(rec);
|
|
}
|
|
|
|
if (cmd.opt_chsc) {
|
|
rec = util_rec_new_wide("-");
|
|
util_rec_def(rec, "device", UTIL_REC_ALIGN_LEFT, 8, "Device");
|
|
util_rec_def(rec, "subch", UTIL_REC_ALIGN_LEFT, 9, "Subchan.");
|
|
/* Print header only if other subchannel types also requested */
|
|
if (cmd.opt_io || cmd.opt_eadm || cmd.opt_vfio)
|
|
printf("\nCHSC Subchannels:\n");
|
|
util_rec_print_hdr(rec);
|
|
print_subchannels_of_type(SUBCHANNEL_TYPE_CHSC, rec);
|
|
util_rec_free(rec);
|
|
}
|
|
|
|
if (cmd.opt_eadm) {
|
|
rec = util_rec_new_wide("-");
|
|
util_rec_def(rec, "device", UTIL_REC_ALIGN_LEFT, 8, "Device");
|
|
util_rec_def(rec, "subch", UTIL_REC_ALIGN_LEFT, 9, "Subchan.");
|
|
/* Print header only if other subchannel types also requested */
|
|
if (cmd.opt_chsc || cmd.opt_io || cmd.opt_vfio)
|
|
printf("\nEADM Subchannels:\n");
|
|
util_rec_print_hdr(rec);
|
|
print_subchannels_of_type(SUBCHANNEL_TYPE_EADM, rec);
|
|
util_rec_free(rec);
|
|
}
|
|
|
|
if (cmd.opt_vfio) {
|
|
rec = util_rec_new_wide("-");
|
|
util_rec_def(rec, "mdev", UTIL_REC_ALIGN_LEFT, 37, "MDEV");
|
|
util_rec_def(rec, "subch", UTIL_REC_ALIGN_LEFT, 9, "Subchan.");
|
|
util_rec_def(rec, "pim", UTIL_REC_ALIGN_LEFT, 3, "PIM");
|
|
util_rec_def(rec, "pam", UTIL_REC_ALIGN_LEFT, 3, "PAM");
|
|
if (cmd.opt_vpm) {
|
|
util_rec_def(rec, "pom", UTIL_REC_ALIGN_LEFT, 3, "POM");
|
|
util_rec_def(rec, "vpm", UTIL_REC_ALIGN_LEFT, 3, "VPM");
|
|
} else
|
|
util_rec_def(rec, "pom", UTIL_REC_ALIGN_LEFT, 4, "POM");
|
|
util_rec_def(rec, "chpids", UTIL_REC_ALIGN_LEFT, 17, "CHPIDs");
|
|
/* Print header only if other subchannel types are requested */
|
|
if (cmd.opt_io || cmd.opt_chsc || cmd.opt_eadm)
|
|
printf("\nI/O Subchannels used for VFIO:\n");
|
|
util_rec_print_hdr(rec);
|
|
print_subchannels_of_type(SUBCHANNEL_TYPE_IO, rec);
|
|
util_rec_free(rec);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse options and execute the command
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char *id_from, *id_to, *id_list = NULL;
|
|
char *dtype, *dtype_list = NULL;
|
|
int c, i;
|
|
|
|
util_prg_init(&prg);
|
|
util_opt_init(opt_vec, NULL);
|
|
|
|
while (1) {
|
|
c = util_opt_getopt_long(argc, argv);
|
|
if (c == -1)
|
|
break;
|
|
switch (c) {
|
|
case 'h':
|
|
util_prg_print_help();
|
|
util_opt_print_help();
|
|
return EXIT_SUCCESS;
|
|
case 'v':
|
|
util_prg_print_version();
|
|
return EXIT_SUCCESS;
|
|
case 's':
|
|
cmd.opt_short = true;
|
|
break;
|
|
case 't':
|
|
cmd.opt_devtype = true;
|
|
dtype_list = optarg;
|
|
break;
|
|
case 'd':
|
|
cmd.opt_devrange = true;
|
|
break;
|
|
case OPT_AVAIL:
|
|
cmd.opt_avail = true;
|
|
break;
|
|
case OPT_VPM:
|
|
cmd.opt_vpm = true;
|
|
break;
|
|
case 'u':
|
|
cmd.opt_uppercase = true;
|
|
break;
|
|
case OPT_IO:
|
|
cmd.opt_io = true;
|
|
break;
|
|
case OPT_CHSC:
|
|
cmd.opt_chsc = true;
|
|
break;
|
|
case OPT_EADM:
|
|
cmd.opt_eadm = true;
|
|
break;
|
|
case OPT_VFIO:
|
|
cmd.opt_vfio = true;
|
|
break;
|
|
case 'a':
|
|
cmd.opt_io = true;
|
|
cmd.opt_chsc = true;
|
|
cmd.opt_eadm = true;
|
|
break;
|
|
default:
|
|
util_opt_print_parse_error(c, argv);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
/*
|
|
* VFIO subchannel view and IO subchannel view are mutual exclusion.
|
|
* And it does not make sense to use the -d, -t, and --avail options
|
|
* together with --vfio.
|
|
*/
|
|
if (cmd.opt_vfio &&
|
|
(cmd.opt_io || cmd.opt_devrange ||
|
|
cmd.opt_devtype || cmd.opt_avail)) {
|
|
errx(EXIT_FAILURE, "Invalid option combination: "
|
|
"--vfio can not be used with --io, -d, -t or --avail");
|
|
}
|
|
/* Display IO subchannels by default */
|
|
if (!cmd.opt_chsc && !cmd.opt_eadm && !cmd.opt_vfio)
|
|
cmd.opt_io = true;
|
|
|
|
/* Process the list of specified device types */
|
|
if (dtype_list != NULL) {
|
|
cmd.devtypes = util_list_new(struct devtype, node);
|
|
cmd.dev_count = 0;
|
|
/* Loop over comma-separated list */
|
|
dtype = strtok(dtype_list, ",");
|
|
while (dtype != NULL) {
|
|
add_new_devtype(dtype);
|
|
cmd.dev_count++;
|
|
dtype = strtok(NULL, ",");
|
|
}
|
|
}
|
|
|
|
/* Scan RANGE parameters appending each argument with comma */
|
|
for (i = optind; i < argc; i++) {
|
|
if (i > optind)
|
|
id_list = util_strcat_realloc(id_list, ",");
|
|
id_list = util_strcat_realloc(id_list, argv[i]);
|
|
}
|
|
|
|
/* Process the list of specified ranges */
|
|
if (id_list != NULL) {
|
|
cmd.ranges = util_list_new(struct range, node);
|
|
cmd.rng_count = 0;
|
|
/* Loop over comma-separated list */
|
|
id_from = strtok(id_list, ",");
|
|
while (id_from != NULL) {
|
|
id_to = strchr(id_from, '-');
|
|
if (id_to == NULL)
|
|
id_to = id_from;
|
|
else
|
|
*id_to++ = '\0';
|
|
if (*id_to == '\0')
|
|
errx(EXIT_FAILURE, "Invalid ID specified: %s", id_from);
|
|
add_new_range(id_from, id_to);
|
|
cmd.rng_count++;
|
|
id_from = strtok(NULL, ",");
|
|
}
|
|
free(id_list);
|
|
}
|
|
|
|
/* Process lscss command with provided options and attributes */
|
|
cmd_lscss();
|
|
util_list_free(cmd.devtypes);
|
|
util_list_free(cmd.ranges);
|
|
return EXIT_SUCCESS;
|
|
}
|