mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Add a new action type 'capabilities' for the 'get' event. This will return a JSON list of events and actions that the callout script recognizes and actually does something with. It omits events like 'notify' and actions like 'list' that ap-check currently ignores. Reviewed-by: Boris Fiuczynski <fiuczy@linux.ibm.com> Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1228 lines
30 KiB
C
1228 lines
30 KiB
C
/*
|
|
* 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 <json-c/json.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"
|
|
|
|
/* The supported mdevctl callout version */
|
|
#define MDEVCTL_CAP_VERSION 2
|
|
|
|
static const struct mdevctl_action mdevctl_action_table[NUM_MDEVCTL_ACTIONS] = {
|
|
{MDEVCTL_ACTION_DEFINE, "define"},
|
|
{MDEVCTL_ACTION_MODIFY, "modify"},
|
|
{MDEVCTL_ACTION_START, "start"},
|
|
{MDEVCTL_ACTION_STOP, "stop"},
|
|
{MDEVCTL_ACTION_UNDEFINE, "undefine"},
|
|
{MDEVCTL_ACTION_ATTRIBUTES, "attributes"},
|
|
{MDEVCTL_ACTION_CAPABILITIES, "capabilities"}
|
|
/*
|
|
* Note: the following actions are known to exist but currently ignored:
|
|
* {MDEVCTL_ACTION_LIST, "list"},
|
|
* {MDEVCTL_ACTION_TYPES, "types"}
|
|
*/
|
|
};
|
|
|
|
static const struct mdevctl_event mdevctl_event_table[NUM_MDEVCTL_EVENTS] = {
|
|
{MDEVCTL_EVENT_PRE, "pre"},
|
|
{MDEVCTL_EVENT_POST, "post"},
|
|
{MDEVCTL_EVENT_GET, "get"},
|
|
{MDEVCTL_EVENT_LIVE, "live"}
|
|
/*
|
|
* Note: the following events are known to exist but currently ignored:
|
|
* {MDEVCTL_EVENT_NOTIFY, "notify"},
|
|
*/
|
|
};
|
|
|
|
/*
|
|
* 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_callout();
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* Read the 'matrix' attribute to get the list of queues for the active
|
|
* device. If the sysfs attribute is unreadable, assume the device is
|
|
* being destroyed and skip it.
|
|
*/
|
|
matrix_path = path_get_vfio_ap_attr(filename, "matrix");
|
|
f = fopen(matrix_path, "r");
|
|
free(matrix_path);
|
|
if (!f)
|
|
return 0;
|
|
|
|
dev2 = vfio_ap_device_new();
|
|
|
|
while (fgets(buf, sizeof(buf), f))
|
|
vfio_ap_parse_matrix(dev2, buf);
|
|
vfio_ap_sort_matrix_results(dev2);
|
|
fclose(f);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static int ap_check_active(struct ap_check_anchor *anc)
|
|
{
|
|
int rc, rc2;
|
|
|
|
/* Ensure device with control domains also has usage domains */
|
|
if (util_list_is_empty(anc->dev->domains) &&
|
|
!util_list_is_empty(anc->dev->controls)) {
|
|
fprintf(stderr, "At least one usage domain must be specified\n");
|
|
return -1;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int ap_do_dynamic_config(struct ap_check_anchor *anc)
|
|
{
|
|
char *adapters, *domains, *controls, *path, *attr;
|
|
int asize, dsize, csize, size;
|
|
int rc = 0;
|
|
FILE *f;
|
|
|
|
adapters = vfio_ap_device_get_adapter_mask(anc->dev, &asize);
|
|
domains = vfio_ap_device_get_domain_mask(anc->dev, &dsize);
|
|
controls = vfio_ap_device_get_control_mask(anc->dev, &csize);
|
|
|
|
if (!adapters || !domains || !controls) {
|
|
fprintf(stderr, "Failed to read device config\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* The 'ap_config' command takes a comma-delimited list of the 3 masks
|
|
* combined. Each mask size includes a terminating character, two of
|
|
* which will be replaced by commas and the final replaced by a
|
|
* newline, which ap_config seems to require at the end of the input.
|
|
* Add one to ensure room for a null termination.
|
|
*/
|
|
size = asize + dsize + csize + 1;
|
|
attr = util_zalloc(size);
|
|
|
|
/* Use the 3 masks to generate a 'ap_config' command */
|
|
rc = snprintf(attr, size, "%s,%s,%s\n", adapters, domains, controls);
|
|
|
|
if (rc < size - 1) {
|
|
fprintf(stderr, "Error creating ap_config command\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Apply the new configuration to the active device */
|
|
path = path_get_vfio_ap_attr(anc->uuid, "ap_config");
|
|
f = fopen(path, "w");
|
|
if (!f) {
|
|
fprintf(stderr, "Error opening ap_config\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
rc = fputs(attr, f);
|
|
fclose(f);
|
|
|
|
if (rc == EOF)
|
|
fprintf(stderr, "Error writing to ap_config\n");
|
|
else
|
|
rc = 0;
|
|
|
|
out:
|
|
if (!adapters)
|
|
free(adapters);
|
|
if (!domains)
|
|
free(domains);
|
|
if (!controls)
|
|
free(controls);
|
|
if (!path)
|
|
free(path);
|
|
if (!attr)
|
|
free(attr);
|
|
|
|
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 modifying the active device is a valid operation.
|
|
* This is similar to STARTing a device, in that the requested modifications
|
|
* cannot conflict with the active configuration. LIVE MODIFY can only be
|
|
* handled if the ap_config attribute is available in the vfio-ap driver.
|
|
*/
|
|
static int ap_check_handle_live_modify(struct ap_check_anchor *anc)
|
|
{
|
|
int rc;
|
|
|
|
rc = ap_get_lock_callout();
|
|
if (rc) {
|
|
fprintf(stderr, "Failed to acquire configuration lock %d\n",
|
|
rc);
|
|
return -1;
|
|
}
|
|
anc->cleanup_lock = true;
|
|
|
|
if (vfio_ap_read_device_config(NULL, anc->dev) != 0) {
|
|
fprintf(stderr, "Failed to read device config\n");
|
|
return -1;
|
|
}
|
|
|
|
if (strcmp(anc->dev->type, anc->type) != 0) {
|
|
fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type);
|
|
return -1;
|
|
}
|
|
|
|
if (!vfio_ap_need_dynamic_config(anc->dev)) {
|
|
fprintf(stderr, "vfio-ap module does not support ap_config for live modification");
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the new configuration would cause conflicts */
|
|
rc = ap_check_active(anc);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Attempt to perform the dynamic configuration */
|
|
rc = ap_do_dynamic_config(anc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
rc = ap_check_active(anc);
|
|
|
|
/* 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_callout();
|
|
}
|
|
|
|
/* 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;
|
|
int rc;
|
|
|
|
/*
|
|
* For the get-attributes callout, we are typically called without the
|
|
* callout lock held. However, there is a particular scenario (define
|
|
* of an active mdev) where we may or may not be called with the lock
|
|
* already held on behalf of mdevctl, depending on the mdevctl version.
|
|
* Let's test for lock ownership first and, if already owned by the
|
|
* parent (mdevctl) proceed rather than waiting on the file lock.
|
|
*/
|
|
rc = ap_try_lock_callout();
|
|
switch (rc) {
|
|
case 0:
|
|
/* Lock acquired */
|
|
anc->cleanup_lock = true;
|
|
break;
|
|
case 1:
|
|
/* Lock held by parent -- trust the lock will remain held */
|
|
break;
|
|
default:
|
|
/* Lock not acquired or held by parent -- do a normal obtain */
|
|
rc = ap_get_lock_callout();
|
|
if (rc) {
|
|
fprintf(stderr,
|
|
"Failed to acquire configuration lock %d\n",
|
|
rc);
|
|
return -1;
|
|
}
|
|
anc->cleanup_lock = true;
|
|
}
|
|
|
|
/*
|
|
* Read the 'matrix' and 'control_domains' attributes to get the
|
|
* current attributes of the active device. If either of these sysfs
|
|
* attributes is unreadable, assume the device is being destroyed
|
|
* and return nothing.
|
|
*/
|
|
path = path_get_vfio_ap_attr(anc->uuid, "matrix");
|
|
f = fopen(path, "r");
|
|
free(path);
|
|
if (!f)
|
|
return 0;
|
|
while (fgets(buf, sizeof(buf), f))
|
|
vfio_ap_parse_matrix(dev, buf);
|
|
vfio_ap_sort_matrix_results(dev);
|
|
fclose(f);
|
|
|
|
path = path_get_vfio_ap_attr(anc->uuid, "control_domains");
|
|
f = fopen(path, "r");
|
|
free(path);
|
|
if (!f)
|
|
return 0;
|
|
while (fgets(buf, sizeof(buf), f))
|
|
vfio_ap_parse_control(dev, buf);
|
|
fclose(f);
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If the target 'attr' is in the 's' array, add it to the 't' array.
|
|
*/
|
|
static void json_add_attr(json_object *t, json_object *s, const char *attr)
|
|
{
|
|
size_t vlen, alen = strlen(attr);
|
|
const char *val;
|
|
json_object *o;
|
|
int i, num;
|
|
|
|
num = json_object_array_length(s);
|
|
for (i = 0; i < num; i++) {
|
|
o = json_object_array_get_idx(s, i);
|
|
val = json_object_get_string(o);
|
|
vlen = strlen(val);
|
|
if (alen == vlen && strncasecmp(attr, val, alen) == 0) {
|
|
json_object_array_add(t, json_object_new_string(val));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generate a JSON-formatted list of capability information that this script
|
|
* supports and return it to the caller via stdout. An example of what the
|
|
* output should look like (without the newlines):
|
|
* {
|
|
* "supports": {
|
|
* "version": 2,
|
|
* "actions": ["define",
|
|
* "modify",
|
|
* "start",
|
|
* "stop",
|
|
* "undefine",
|
|
* "attributes",
|
|
* "capabilities"],
|
|
* "events": ["pre",
|
|
* "post",
|
|
* "get",
|
|
* "live"]
|
|
* }
|
|
* }
|
|
*/
|
|
static int ap_check_handle_get_capabilities(void)
|
|
{
|
|
json_object *root, *csup, *cver, *cact, *cev, *cap, *caps, *o;
|
|
int i, rc = 0;
|
|
|
|
root = json_object_from_fd(STDIN_FILENO);
|
|
|
|
if (!root) {
|
|
fprintf(stderr, "No capabilities provided\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!json_object_object_get_ex(root, "provides", &csup)) {
|
|
fprintf(stderr, "No supported capabilities provided\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!json_object_object_get_ex(csup, "version", &cver)) {
|
|
fprintf(stderr, "No version provided in capabilities\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!json_object_object_get_ex(csup, "actions", &cact)) {
|
|
fprintf(stderr, "No actions provided in capabilities\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!json_object_object_get_ex(csup, "events", &cev)) {
|
|
fprintf(stderr, "No events provided in capabilities\n");
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Advertise the subset of supported capabilities from the list
|
|
* provided on stdin.
|
|
*/
|
|
|
|
cap = json_object_new_object();
|
|
caps = json_object_new_object();
|
|
json_object_object_add(cap, "supports", caps);
|
|
/*
|
|
* Currently we always advertise a fixed version, but we may need to
|
|
* revisit this if we increase MDEVCTL_CAP_VERSION in the future (e.g.
|
|
* how to handle ap-check having a greater supported version than
|
|
* what mdevctl reports)
|
|
*/
|
|
o = json_object_new_int(MDEVCTL_CAP_VERSION);
|
|
json_object_object_add(caps, "version", o);
|
|
|
|
o = json_object_new_array();
|
|
for (i = 0; i < NUM_MDEVCTL_ACTIONS; i++)
|
|
json_add_attr(o, cact, mdevctl_action_table[i].action);
|
|
json_object_object_add(caps, "actions", o);
|
|
|
|
o = json_object_new_array();
|
|
for (i = 0; i < NUM_MDEVCTL_EVENTS; i++)
|
|
json_add_attr(o, cev, mdevctl_event_table[i].event);
|
|
json_object_object_add(caps, "events", o);
|
|
|
|
/* Return supported capabilities JSON on stdout */
|
|
printf("%s\n", json_object_to_json_string(cap));
|
|
|
|
json_object_put(cap);
|
|
|
|
out:
|
|
json_object_put(root);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
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;
|
|
case MDEVCTL_ACTION_CAPABILITIES:
|
|
rc = ap_check_handle_get_capabilities();
|
|
break;
|
|
default:
|
|
/* Ignore some actions including unknown ones */
|
|
break;
|
|
}
|
|
break;
|
|
case MDEVCTL_EVENT_LIVE:
|
|
switch (anc->action) {
|
|
case MDEVCTL_ACTION_MODIFY:
|
|
rc = ap_check_handle_live_modify(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);
|
|
}
|