Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:15.5:Update
s390-tools.28248
s390-tools-sles15sp4-05-ap_tools-Introduce-ap_t...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File s390-tools-sles15sp4-05-ap_tools-Introduce-ap_tools-and-the-ap-check-tool.patch of Package s390-tools.28248
Subject: [PATCH] [FEAT VS1822] ap_tools: Introduce ap_tools and the ap-check tool From: Matthew Rosato <mjrosato@linux.ibm.com> Summary: ap_tools: add ap-check and the ap device type to zdev Description: This feature adds multiple components in support of persistent configuration of vfio-ap mediated devices. The ap-check utility is added as a callout function for the mdevctl utility. This allows for meaningful error messages to be presented to end-users when vfio-ap configuration errors are detected while using mdevctl to create/modify vfio-ap mediated devices. Additionally, the 'ap' device type is added to zdev, providing a command-line interface for managing the apmask and aqmask, which determine what AP resources are available for vfio-ap usage. 'chzdev' is updated to allow for modifying the active masks as well as to specify persistent mask values via a udev rule. 'lszdev' is updated to allow for querying of the active and persistent mask values. Upstream-ID: 2da206f5a6b3e0340027910a67dc48d8d9c83020 Problem-ID: VS1822 Upstream-Description: ap_tools: Introduce ap_tools and the ap-check tool The ap_tools utilities are intended to be used in conjunction with the mdevctl utility for safely managing and inspecting vfio-ap mediated devices. For now, this will consist of the ap-check tool which will be driven via a call-out from mdevctl to validate a proposed vfio-ap mediated device change. Reviewed-by: Jan Hoeppner <hoeppner@linux.ibm.com> Reviewed-by: Tony Krowiak <akrowiak@linux.ibm.com> Reviewed-by: Jason J. Herne <jjherne@linux.ibm.com> Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com> Signed-off-by: Jan Hoeppner <hoeppner@linux.ibm.com> Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com> --- .gitignore | 1 Makefile | 2 README.md | 12 ap_tools/Makefile | 47 ++ ap_tools/ap-check.c | 911 ++++++++++++++++++++++++++++++++++++++++++++++++++++ ap_tools/ap-check.h | 74 ++++ 6 files changed, 1046 insertions(+), 1 deletion(-) --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ TAGS # # Ignore generated executables and other generated files # +ap_tools/ap-check cmsfs-fuse/cmsfs-fuse cpacfstats/cpacfstats cpacfstats/cpacfstatsd --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt das vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \ ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \ systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \ - genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath pvattest + genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath pvattest ap_tools SUB_DIRS = $(BASELIB_DIRS) $(LIB_DIRS) $(TOOL_DIRS) --- a/README.md +++ b/README.md @@ -277,6 +277,10 @@ Package contents Use multipath information to change the configured FCP re-IPL path on detecting issues with the current path. + * ap-check: + A utility called by mdevctl to assist in managing vfio_ap-passthrough + devices. + For more information refer to the following publications: * "Device Drivers, Features, and Commands" chapter "Useful Linux commands" @@ -312,6 +316,7 @@ build options: | | | pvattest | | libxml2 | `HAVE_LIBXML2` | libkmipclient | | systemd | `HAVE_SYSTEMD` | hsavmcore | +| liblockfile | `HAVE_LOCKFILE` | ap-check | This table lists additional build or install options: @@ -494,3 +499,10 @@ the different tools are provided: make invocation, it also requires dracut. When using `ENABLE_DOC=1` with the make invocation to build a man page and render the README.md as HTML, make further requires pandoc and GNU awk for the build process. + +* ap-check: + For building the ap-check mdevctl callout utility you need liblockfile + version 1.14 or newer installed (liblockfile-devel.rpm). Also required is + json-c version 0.13 or newer (json-c-devel.rpm). + Tip: you may skip ap-check build by adding `HAVE_LOCKFILE=0` or `HAVE_JSONC=0` + to the make invocation. --- /dev/null +++ b/ap_tools/Makefile @@ -0,0 +1,47 @@ +include ../common.mak + +MDEVCTL_DIR = /etc/mdevctl.d/ +MDEVCTL_SCRIPTS = /etc/mdevctl.d/scripts.d/ +MDEVCTL_CALLOUTS = /etc/mdevctl.d/scripts.d/callouts/ + +libs = $(rootdir)/libap/libap.a \ + $(rootdir)/libutil/libutil.a + +ifeq (${HAVE_LOCKFILE},0) +all: + $(SKIP) HAVE_LOCKFILE=0 + +install: + $(SKIP) HAVE_LOCKFILE=0 + +else ifeq (${HAVE_JSONC},0) +all: + $(SKIP) HAVE_JSONC=0 + +install: + $(SKIP) HAVE_JSONC=0 +else +LDLIBS += -llockfile -ljson-c + +all: ap-check + +ap-check: ap-check.o $(libs) + +install: all + @if [ ! -d $(DESTDIR)$(MDEVCTL_CALLOUTS) ]; then \ + mkdir -p $(DESTDIR)$(MDEVCTL_CALLOUTS); \ + chown $(OWNER).$(GROUP) $(DESTDIR)$(MDEVCTL_DIR); \ + chown $(OWNER).$(GROUP) $(DESTDIR)$(MDEVCTL_SCRIPTS); \ + chown $(OWNER).$(GROUP) $(DESTDIR)$(MDEVCTL_CALLOUTS); \ + chmod 755 $(DESTDIR)$(MDEVCTL_DIR); \ + chmod 755 $(DESTDIR)$(MDEVCTL_SCRIPTS); \ + chmod 755 $(DESTDIR)$(MDEVCTL_CALLOUTS); \ + fi; \ + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 ap-check \ + $(DESTDIR)$(MDEVCTL_CALLOUTS) +endif + +clean: + rm -f *.o *~ ap-check core + +.PHONY: all install clean --- /dev/null +++ b/ap_tools/ap-check.c @@ -0,0 +1,911 @@ +/* + * ap-check - Validate vfio-ap mediated device configuration changes + * + * This tool in intended to be driven via the callout API of the mdevctl + * utility (https://github.com/mdevctl/mdevctl/) + * + * Copyright IBM Corp. 2022 + * + * 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 <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "lib/ap.h" +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_opt.h" +#include "lib/util_path.h" + +#include "ap-check.h" + +static const struct mdevctl_action mdevctl_action_table[NUM_MDEVCTL_ACTIONS] = { + {MDEVCTL_ACTION_DEFINE, "define"}, + {MDEVCTL_ACTION_LIST, "list"}, + {MDEVCTL_ACTION_MODIFY, "modify"}, + {MDEVCTL_ACTION_START, "start"}, + {MDEVCTL_ACTION_STOP, "stop"}, + {MDEVCTL_ACTION_TYPES, "types"}, + {MDEVCTL_ACTION_UNDEFINE, "undefine"}, + {MDEVCTL_ACTION_ATTRIBUTES, "attributes"} +}; + +static const struct mdevctl_event mdevctl_event_table[NUM_MDEVCTL_EVENTS] = { + {MDEVCTL_EVENT_PRE, "pre"}, + {MDEVCTL_EVENT_POST, "post"}, + {MDEVCTL_EVENT_GET, "get"} +}; + +/* + * Convert mdevctl action string to an enumerated value + */ +static enum mdevctl_action_id validate_action(char *action) +{ + int i; + + for (i = 0; i < NUM_MDEVCTL_ACTIONS; i++) { + if (strcmp(action, mdevctl_action_table[i].action) == 0) + return mdevctl_action_table[i].id; + } + + return MDEVCTL_ACTION_UNKNOWN; +} + +/* + * Convert mdevctl event string to an enumerated value + */ +static enum mdevctl_event_id validate_event(char *event) +{ + int i; + + for (i = 0; i < NUM_MDEVCTL_EVENTS; i++) { + if (strcmp(event, mdevctl_event_table[i].event) == 0) + return mdevctl_event_table[i].id; + } + + return MDEVCTL_EVENT_UNKNOWN; +} + +static struct util_opt opt_vec[] = { + UTIL_OPT_SECTION("DEVICE"), + { + .option = { "e", required_argument, NULL, 'e' }, + .argument = "EVENT", + .desc = "The type of callout being issued", + }, + { + .option = { "a", required_argument, NULL, 'a' }, + .argument = "ACTION", + .desc = "The action being performed on the specified device", + }, + { + .option = { "s", required_argument, NULL, 's' }, + .argument = "STATE", + .desc = "The state of the associated mdevctl command", + }, + { + .option = { "u", required_argument, NULL, 'u' }, + .argument = "UUID", + .desc = "Universally Unique ID for the mediated device", + }, + { + .option = { "p", required_argument, NULL, 'p' }, + .argument = "PDEV", + .desc = "Parent device name, e.g. matrix", + }, + { + .option = { "t", required_argument, NULL, 't' }, + .argument = "TYPE", + .desc = "Mediated device type, e.g. vfio_ap-passthrough", + }, + UTIL_OPT_END +}; + +/* + * Initialize the ap_check anchor struct. + */ +static void ap_check_init(struct ap_check_anchor *anc) +{ + anc->uuid = anc->parent = anc->type = NULL; + anc->dev = vfio_ap_device_new(); + anc->cleanup_lock = false; +} + +/* + * Free memory of ap_check anchor struct. + */ +static void ap_check_cleanup(struct ap_check_anchor *anc) +{ + if (anc->uuid) + free(anc->uuid); + if (anc->parent) + free(anc->parent); + if (anc->type) + free(anc->type); + if (anc->dev) + vfio_ap_device_free(anc->dev); + if (anc->cleanup_lock) + ap_release_lock(); +} + +/* + * Exit ap_check + */ +static void __noreturn ap_check_exit(struct ap_check_anchor *anc, int rc) +{ + ap_check_cleanup(anc); + exit(rc); +} + +/* + * parses the command line + */ +static void ap_check_parse(struct ap_check_anchor *anc, + int argc, char *argv[]) +{ + bool action = false, event = false, state = false, bad_opts = false; + int opt; + + util_opt_init(opt_vec, NULL); + + while (1) { + opt = util_opt_getopt_long(argc, argv); + if (opt == -1) + break; + switch (opt) { + case 'e': + if (event) { + bad_opts = true; + } else { + anc->event = validate_event(optarg); + event = true; + } + break; + case 'a': + if (action) { + bad_opts = true; + } else { + anc->action = validate_action(optarg); + action = true; + } + break; + case 's': + if (state) { + bad_opts = true; + } else { + /* Ignore the state */ + state = true; + } + break; + case 'u': + if (anc->uuid) + bad_opts = true; + else + anc->uuid = util_strdup(optarg); + break; + case 'p': + if (anc->parent) + bad_opts = true; + else + anc->parent = util_strdup(optarg); + break; + case 't': + if (anc->type) + bad_opts = true; + else + anc->type = util_strdup(optarg); + break; + default: + fprintf(stderr, "Unknown operand\n"); + ap_check_exit(anc, EXIT_FAILURE); + } + } + + /* Make sure we got all expected input values */ + if (!(action && event && state && anc->uuid && anc->parent && + anc->type) || bad_opts) { + fprintf(stderr, "Duplicate or missing operand\n"); + ap_check_exit(anc, EXIT_FAILURE); + } + + /* Check for invalid UUID */ + if (!is_valid_uuid(anc->uuid)) { + fprintf(stderr, "Invalid UUID specified\n"); + ap_check_exit(anc, EXIT_FAILURE); + } + anc->dev->uuid = util_strdup(anc->uuid); + + /* Check for valid type */ + if (strcmp(anc->type, VFIO_AP_TYPE) != 0) + ap_check_exit(anc, APC_EXIT_UNKNOWN_TYPE); + + /* Check for invalid parent - currently only 'matrix' supported */ + if (strcmp(anc->parent, "matrix") != 0) { + fprintf(stderr, "Invalid parent specified\n"); + ap_check_exit(anc, EXIT_FAILURE); + } +} + +/* + * Call a function for each entry in a directory: + * int callback(const char *abs_path, const char *rel_path, void *data) + * Continues for all entries in the directory regardless of callback return + * code. Will return 0 or, if one or more callbacks failed, the first nonzero + * rc received. + */ +static int path_for_each(const char *path, + int (*callback)(const char *, const char *, void *), + void *data) +{ + struct dirent *de; + int rc = 0; + int r = 0; + DIR *dir; + char *p; + + dir = opendir(path); + if (!dir) + return -1; + + while ((de = readdir(dir))) { + if (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0) + continue; + util_asprintf(&p, "%s/%s", path, de->d_name); + r = callback(p, de->d_name, data); + /* Save first nonzero return code for caller */ + if (rc == 0 && r != 0) + rc = r; + free(p); + } + + closedir(dir); + + return rc; +} + +/* + * Report an error message when the specified configuration will conflict + * with an existing device + */ +static void conflict_error(const char *uuid, unsigned int a, unsigned int d, + bool persistent) +{ + if (uuid) { + if (persistent) { + fprintf(stderr, + "APQN %u.%u is defined for autostart by %s\n", + a, d, uuid); + } else { + fprintf(stderr, "APQN %u.%u already in use by %s\n", + a, d, uuid); + } + } else { + if (persistent) { + fprintf(stderr, "AQPN %u.%u is not defined for " + "vfio_ap-passthrough use by the persistent " + "ap bus mask settings\n", a, d); + } else { + fprintf(stderr, "AQPN %u.%u is not allowed for " + "vfio_ap-passthrough use by the active ap " + "bus mask settings\n", a, d); + } + } +} + +/* + * Compare the list of adapters and domains for two devices, reporting error + * messages for any conflicts that occur. A conflict occurs when both devices + * have the same adapter + domain pair. + * The function below takes advantage of the fact that the lists are known to + * be sorted in numeric order; therefore we can use this information to run + * the lists in parallel rather than always starting from the beginning. + */ +static int find_apqn_conflicts(const char *uuid, + struct util_list *adapters, + struct util_list *domains, + struct util_list *adapters2, + struct util_list *domains2, + bool persistent) +{ + struct vfio_ap_node *a, *a2, *d, *d2; + int rc = 0; + + /* Checks for conflicts with the device */ + a = util_list_start(adapters); + a2 = util_list_start(adapters2); + while ((a != NULL) && (a2 != NULL)) { + if (a->id == a2->id) { + d = util_list_start(domains); + d2 = util_list_start(domains2); + while ((d != NULL) && (d2 != NULL)) { + if (d->id == d2->id) { + /* Report error, look for more */ + conflict_error(uuid, a->id, d->id, + persistent); + rc = -1; + d = util_list_next(domains, d); + d2 = util_list_next(domains2, d2); + } else if (d->id > d2->id) { + d2 = util_list_next(domains2, d2); + } else { + d = util_list_next(domains, d); + } + } + a = util_list_next(adapters, a); + a2 = util_list_next(adapters2, a2); + } else if (a->id > a2->id) { + a2 = util_list_next(adapters2, a2); + } else { + a = util_list_next(adapters, a); + } + } + + return rc; +} + +/* + * If the provided path maps to a valid vfio-ap device configuration, + * determine if its current configuration will conflict with the proposed + * changes. + */ +static int check_other_mdev_cfg_cb(const char *path, + const char *filename, + void *data) +{ + struct other_mdev_cb_data *cbdata = data; + struct vfio_ap_device *dev = cbdata->dev; + struct vfio_ap_device *dev2 = NULL; + int rc = 0; + + /* Skip anything that isn't an mdev config */ + if (!is_valid_uuid(filename)) + goto out; + + /* Skip if this is the input device */ + if (strcasecmp(cbdata->uuid, filename) == 0) + goto out; + + /* Read the device config */ + dev2 = vfio_ap_device_new(); + if (vfio_ap_read_device_config(path, dev2) != 0) + goto out; + + /* If wrong device type, skip */ + if (strcmp(dev2->type, VFIO_AP_TYPE) != 0) + goto out; + + /* If not AUTO device, skip */ + if (dev2->manual) + goto out; + + /* Perform mdev-to-mdev apqn conflict analysis */ + rc = find_apqn_conflicts(filename, dev->adapters, dev->domains, + dev2->adapters, dev2->domains, true); + +out: + if (dev2 != NULL) + vfio_ap_device_free(dev2); + return rc; +} + +/* + * Perform conflict analysis against all other vfio-ap persistent + * configurations. + */ +int check_other_mdevs_cfg(struct ap_check_anchor *anc) +{ + struct other_mdev_cb_data cb_data; + + if (!util_path_is_dir(VFIO_AP_CONFIG_PATH)) + return 0; + + cb_data.uuid = anc->uuid; + cb_data.dev = anc->dev; + + return path_for_each(VFIO_AP_CONFIG_PATH, check_other_mdev_cfg_cb, + &cb_data); +} + +/* + * If the provided path maps to a valid device, determine if its current + * configuration will conflict with the proposed changes. + */ +static int check_other_mdev_sysfs_cb(const char *path, const char *filename, + void *data) +{ + struct other_mdev_cb_data *cbdata = data; + struct vfio_ap_device *dev = cbdata->dev; + struct vfio_ap_device *dev2; + char *matrix_path; + char buf[80]; + int rc = 0; + FILE *f; + + if (!is_valid_uuid(filename) || path == NULL || + strcasecmp(filename, cbdata->uuid) == 0) + return 0; + + dev2 = vfio_ap_device_new(); + matrix_path = path_get_vfio_ap_attr(filename, "matrix"); + f = fopen(matrix_path, "r"); + while (fgets(buf, sizeof(buf), f)) + vfio_ap_parse_matrix(dev2, buf); + vfio_ap_sort_matrix_results(dev2); + fclose(f); + free(matrix_path); + + /* Look for conflicts between target device and this device */ + rc = find_apqn_conflicts(filename, dev->adapters, dev->domains, + dev2->adapters, dev2->domains, false); + + vfio_ap_device_free(dev2); + + return rc; +} + +/* Run conflict analysis against all other active vfio-ap devices */ +static int check_other_mdevs_sysfs(struct ap_check_anchor *anc) +{ + struct other_mdev_cb_data cb_data; + char *root; + int rc = 0; + + cb_data.uuid = anc->uuid; + cb_data.dev = anc->dev; + + root = path_get_vfio_ap_mdev(""); + if (util_path_is_dir(root)) + rc = path_for_each(root, check_other_mdev_sysfs_cb, &cb_data); + + free(root); + return rc; +} + +/* + * Determine if there are any conflicts between the specified device and + * the active apmask/aqmask settings. This is done by treating the masks + * as a temporary vfio_ap_device with all of the associated APQNs owned by + * the system. + */ +static int check_sysfs_mask_conflicts(struct ap_check_anchor *anc) +{ + struct vfio_ap_device *sysdev = vfio_ap_device_new(); + char *apmask = util_zalloc(AP_MASK_SIZE); + char *aqmask = util_zalloc(AP_MASK_SIZE); + int rc = 0; + + if (ap_read_sysfs_masks(apmask, aqmask, AP_MASK_SIZE) != 0) { + fprintf(stderr, "Error reading system AP settings\n"); + rc = -1; + goto out; + } + + /* Convert the masks to a device with the associated APQNs */ + ap_mask_to_list(apmask, sysdev->adapters); + ap_mask_to_list(aqmask, sysdev->domains); + + /* Perform conflict analysis */ + rc = find_apqn_conflicts(NULL, anc->dev->adapters, + anc->dev->domains, sysdev->adapters, + sysdev->domains, false); +out: + free(apmask); + free(aqmask); + vfio_ap_device_free(sysdev); + + return rc; +} + +/* + * Determine if there are any conflicts between the specified device and + * the apmask/aqmask settings stored in udev. This is done by treating + * the masks as a temporary vfio_ap_device with all of the associated + * AQPNs owned by the system. + */ +static int check_cfg_mask_conflicts(struct ap_check_anchor *anc) +{ + struct vfio_ap_device *sysdev = vfio_ap_device_new(); + char *apmask = util_zalloc(AP_MASK_SIZE); + char *aqmask = util_zalloc(AP_MASK_SIZE); + bool read_ap = false, read_aq = false; + char *path; + int rc = 0; + + path = path_get_ap_udev(); + if (!ap_read_udev_masks(path, apmask, aqmask, &read_ap, &read_aq)) { + fprintf(stderr, "Error reading system AP settings\n"); + rc = -1; + goto out; + } + + /* Convert the masks to a device with the associated APQNs */ + ap_mask_to_list(apmask, sysdev->adapters); + ap_mask_to_list(aqmask, sysdev->domains); + + /* Perform conflict analysis */ + rc = find_apqn_conflicts(NULL, anc->dev->adapters, + anc->dev->domains, sysdev->adapters, + sysdev->domains, true); +out: + free(apmask); + free(aqmask); + free(path); + vfio_ap_device_free(sysdev); + + return rc; +} + +/* Subroutine to handle checking shared between DEFINE and MODIFY actions. */ +static int ap_check_changes(struct ap_check_anchor *anc) +{ + int rc = 0, rc2; + + rc = ap_get_lock_callout(); + if (rc) { + fprintf(stderr, "Failed to acquire configuration lock %d\n", + rc); + rc = -1; + goto out; + } + anc->cleanup_lock = true; + + if (vfio_ap_read_device_config(NULL, anc->dev) != 0) { + fprintf(stderr, "Failed to read device config\n"); + rc = -1; + goto out; + } + + if (strcmp(anc->dev->type, anc->type) != 0) { + fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); + rc = -1; + goto out; + } + + if (!anc->dev->manual) { + /* Check against all other AUTO config files */ + rc = check_other_mdevs_cfg(anc); + /* Check against the system UDEV rule for apmask/aqmask */ + rc2 = check_cfg_mask_conflicts(anc); + /* If either hit an error, reflect this */ + rc = rc != 0 ? rc : rc2; + } + + /* If successful, lock must remain held until post callout */ + if (rc == 0) + anc->cleanup_lock = false; + +out: + return rc; +} + +/* + * Determine if defining the specified device is a valid operation. + * mdevctl can reach us for a DEFINE under the following circumstances: + * 1) the device does not exist + * 2) the device is active but does not have a config file, so this action + * would be to generate a config file based upon the active device. + * DEFINE has no effect on an active device (if one exists) it only creates + * the configuration file. The config file might be empty or may have various + * attributes if being fed by --jsonfile or an active device. + */ +static int ap_check_handle_define(struct ap_check_anchor *anc) +{ + char *path = path_get_vfio_ap_mdev_config(anc->uuid); + + if (util_path_is_readable(path)) { + fprintf(stderr, "Config already exists\n"); + free(path); + return -1; + } + + free(path); + + return ap_check_changes(anc); +} + +/* + * Determine if modifying the specified device is a valid operation. + * mdevctl can reach us for a MODIFY under the following circumstances: + * 1) Modifying a MANUAL device + * 2) Modifying an AUTO device + * In the case of MANUAL, we don't take any action because changes made via + * MODIFY don't take affect on the active mdev until a STOP/START cycle. + * In the case of AUTO, we must compare the contents of the proposed device + * with the contents of stashed AUTO mdev configurations + the system. + */ +static int ap_check_handle_modify(struct ap_check_anchor *anc) +{ + char *path = path_get_vfio_ap_mdev_config(anc->uuid); + FILE *fd = fopen(path, "r"); + + /* Determine if a base config file already exists for UUID */ + free(path); + if (fd == NULL) { + fprintf(stderr, "Config doesn't exist\n"); + return -1; + } + fclose(fd); + + return ap_check_changes(anc); +} + +/* + * Determine if starting the specified device is a valid operation. + * mdevctl can reach us for a START under the following circumstances: + * 1) STARTing a MANUAL device + * 1a) Where the MANUAL device is defined (has a config file) + * 1b) Where the MANUAL device is NOT defined (no config file). + * For vfio-ap this case provides an mdev with no adapters/domains. + * 1c) Where the MANUAL device is NOT defined but a full configuration is + * provided via --jsonfile + * 2) STARTing an AUTO device + * 2a) Where the AUTO device is defined (has a config file) + * 2b) Where the AUTO device is NOT defined but a full configuration is + * provided via --jsonfile + * In each case, we must compare the proposed device with the contents of + * active mdevs + the system. + */ +static int ap_check_handle_start(struct ap_check_anchor *anc) +{ + int rc = 0, rc2; + + /* Can only start a device if vfio_ap is built-in or loaded */ + if (!util_path_is_dir(VFIO_AP_PATH)) { + fprintf(stderr, "vfio_ap module is not loaded\n"); + ap_check_exit(anc, EXIT_FAILURE); + } + + rc = ap_get_lock_callout(); + if (rc) { + fprintf(stderr, "Failed to acquire configuration lock %d\n", + rc); + rc = -1; + goto out; + } + anc->cleanup_lock = true; + + if (vfio_ap_read_device_config(NULL, anc->dev) != 0) { + fprintf(stderr, "Failed to read device config\n"); + rc = -1; + goto out; + } + + if (strcmp(anc->dev->type, anc->type) != 0) { + fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); + rc = -1; + goto out; + } + + /* Check against all other active vfio-ap devices */ + rc = check_other_mdevs_sysfs(anc); + /* Check against the system sysfs values for apmask/aqmask */ + rc2 = check_sysfs_mask_conflicts(anc); + /* If either hit an error, reflect this */ + rc = rc != 0 ? rc : rc2; + + /* If successful, lock must remain held until post callout */ + if (rc == 0) + anc->cleanup_lock = false; + +out: + return rc; +} + +/* + * Acquire the appropriate serialization so that the specified device can be + * STOPped. + */ +static int ap_check_handle_stop(void) +{ + int rc; + + rc = ap_get_lock_callout(); + if (rc) { + fprintf(stderr, "Failed to acquire configuration lock %d\n", + rc); + return -1; + } + + /* The lock must remain held until post callout */ + return 0; +} + +/* + * Determine if UNDEFINEing the specified device is a valid operation. + * mdevctl can reach us for an UNDEFINE under the following circumstances: + * 1) UNDEFINEing an active device + * 2) UNDEFINEing an inactive device + * UNDEFINE has no effect on the active device, it only removes the config + * file. + */ +static int ap_check_handle_undefine(struct ap_check_anchor *anc) +{ + char *path = path_get_vfio_ap_mdev_config(anc->uuid); + int rc = 0; + + rc = ap_get_lock_callout(); + if (rc) { + fprintf(stderr, "Failed to acquire configuration lock %d\n", + rc); + rc = -1; + goto out; + } + anc->cleanup_lock = true; + + if (vfio_ap_read_device_config(path, anc->dev) != 0) { + fprintf(stderr, "Failed to read device config\n"); + rc = -1; + goto out; + } + + if (strcmp(anc->dev->type, anc->type) != 0) { + fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); + rc = -1; + goto out; + } + + /* Success: lock must remain held until post callout */ + anc->cleanup_lock = false; + +out: + free(path); + return rc; +} + +/* + * For callouts where the "pre" callout would have acquired the lock, it is + * now safe to remove the lock as all changes have been committed. + */ +static int ap_check_handle_post(void) +{ + return ap_release_lock(); +} + +/* For the specified device, print the attributes to stdout in JSON format */ +static int ap_check_handle_get_attributes(struct ap_check_anchor *anc) +{ + struct vfio_ap_device *dev = anc->dev; + struct vfio_ap_node *node; + bool has_attr = false; + char buf[80]; + char *path; + FILE *f; + + path = path_get_vfio_ap_attr(anc->uuid, "matrix"); + f = fopen(path, "r"); + while (fgets(buf, sizeof(buf), f)) + vfio_ap_parse_matrix(dev, buf); + vfio_ap_sort_matrix_results(dev); + fclose(f); + free(path); + + path = path_get_vfio_ap_attr(anc->uuid, "control_domains"); + f = fopen(path, "r"); + while (fgets(buf, sizeof(buf), f)) + vfio_ap_parse_control(dev, buf); + fclose(f); + free(path); + + printf("[{"); + + if (!util_list_is_empty(dev->adapters)) { + util_list_iterate(dev->adapters, node) { + if (has_attr) + printf("},{"); + printf("\"assign_adapter\": \"%u\"", node->id); + has_attr = true; + } + } + + if (!util_list_is_empty(dev->domains)) { + util_list_iterate(dev->domains, node) { + if (has_attr) + printf("},{"); + printf("\"assign_domain\": \"%u\"", node->id); + has_attr = true; + } + } + + if (!util_list_is_empty(dev->controls)) { + util_list_iterate(dev->controls, node) { + if (has_attr) + printf("},{"); + printf("\"assign_control_domain\": \"%u\"", node->id); + has_attr = true; + } + } + + printf("}]\n"); + + return 0; +} + +/* + * Determine which mdevctl action is being checked and handle accordingly. + */ +static int ap_check_handle_action(struct ap_check_anchor *anc) +{ + int rc = 0; + + switch (anc->event) { + case MDEVCTL_EVENT_PRE: + switch (anc->action) { + case MDEVCTL_ACTION_DEFINE: + rc = ap_check_handle_define(anc); + break; + case MDEVCTL_ACTION_MODIFY: + rc = ap_check_handle_modify(anc); + break; + case MDEVCTL_ACTION_START: + rc = ap_check_handle_start(anc); + break; + case MDEVCTL_ACTION_STOP: + rc = ap_check_handle_stop(); + break; + case MDEVCTL_ACTION_UNDEFINE: + rc = ap_check_handle_undefine(anc); + break; + case MDEVCTL_ACTION_LIST: + case MDEVCTL_ACTION_TYPES: + default: + /* Ignore some actions including unknown ones */ + break; + } + break; + case MDEVCTL_EVENT_POST: + switch (anc->action) { + case MDEVCTL_ACTION_DEFINE: + case MDEVCTL_ACTION_MODIFY: + case MDEVCTL_ACTION_START: + case MDEVCTL_ACTION_STOP: + case MDEVCTL_ACTION_UNDEFINE: + ap_check_handle_post(); + break; + default: + /* Ignore other post events */ + break; + } + break; + case MDEVCTL_EVENT_GET: + switch (anc->action) { + case MDEVCTL_ACTION_ATTRIBUTES: + rc = ap_check_handle_get_attributes(anc); + break; + default: + /* Ignore some actions including unknown ones */ + break; + } + break; + default: + /* Ignore any unknown events */ + break; + } + + return rc; +} + +/* + * + */ +int main(int argc, char *argv[]) +{ + struct ap_check_anchor anchor; + int rc; + + ap_check_init(&anchor); + + ap_check_parse(&anchor, argc, argv); + + rc = ap_check_handle_action(&anchor); + + ap_check_exit(&anchor, rc); +} --- /dev/null +++ b/ap_tools/ap-check.h @@ -0,0 +1,74 @@ +/* + * ap-check - Validate vfio-ap mediated device configuration changes + * + * Copyright IBM Corp. 2022 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef AP_CHECK_H +#define AP_CHECK_H + +#include <stdbool.h> + +/* + * List of all of the supported mdevctl actions + */ +enum mdevctl_action_id { + MDEVCTL_ACTION_DEFINE = 0, + MDEVCTL_ACTION_LIST, + MDEVCTL_ACTION_MODIFY, + MDEVCTL_ACTION_START, + MDEVCTL_ACTION_STOP, + MDEVCTL_ACTION_TYPES, + MDEVCTL_ACTION_UNDEFINE, + MDEVCTL_ACTION_ATTRIBUTES, + /* UNKNOWN must always be the last in the list */ + MDEVCTL_ACTION_UNKNOWN, +}; +#define NUM_MDEVCTL_ACTIONS MDEVCTL_ACTION_UNKNOWN + +struct mdevctl_action { + enum mdevctl_action_id id; + const char action[32]; +}; + +enum mdevctl_event_id { + MDEVCTL_EVENT_PRE = 0, + MDEVCTL_EVENT_POST, + MDEVCTL_EVENT_GET, + MDEVCTL_EVENT_UNKNOWN, +}; +#define NUM_MDEVCTL_EVENTS MDEVCTL_EVENT_UNKNOWN + +struct mdevctl_event { + enum mdevctl_event_id id; + const char event[32]; +}; + +/* ap-check special exit codes */ +#define APC_EXIT_UNKNOWN_TYPE 2 + +struct ap_check_anchor { + enum mdevctl_event_id event; + enum mdevctl_action_id action; + char *uuid; + char *parent; + char *type; + struct vfio_ap_device *dev; + /* Active Masks */ + char apmask[80]; + char aqmask[80]; + /* Persistent Masks */ + char p_apmask[80]; + char p_aqmask[80]; + bool cleanup_lock; +}; + +struct other_mdev_cb_data { + const char *uuid; + struct vfio_ap_device *dev; +}; + +#endif /* AP_CHECK_H */
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor