File s390-tools-sles15sp4-05-ap_tools-Introduce-ap_tools-and-the-ap-check-tool.patch of Package s390-tools.29120
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 */