Subject: [PATCH] [FEAT VS1822] libap: Add library for ap / vfio-ap management tools
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: bf71e3bb1fc39e1e552f012b5346f5816b454097
Problem-ID:  VS1822

Upstream-Description:

             libap: Add library for ap / vfio-ap management tools

             libap is intended to provide utility functions to be used by
             tooling supporting the ap bus and vfio-ap mediated devices.

             Reviewed-by: Jan Hoeppner <hoeppner@linux.ibm.com>
             Reviewed-by: Tony Krowiak <akrowiak@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       |    2 
 Makefile         |    2 
 common.mak       |    4 
 include/lib/ap.h |   94 ++++++
 libap/Makefile   |   37 ++
 libap/ap.c       |  760 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 898 insertions(+), 1 deletion(-)

--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,8 @@ iucvterm/src/iucvconn
 iucvterm/src/iucvtty
 iucvterm/src/ttyrun
 iucvterm/test/test_afiucv
+libap/check-dep-lock
+libap/check-dep-json
 libekmfweb/check-dep-libekmfweb
 libekmfweb/detect-openssl-version.dep
 libekmfweb/libekmfweb.so
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ include common.mak
 #
 BASELIB_DIRS = libutil libseckey
 LIB_DIRS = libvtoc libzds libdasd libvmdump libccw libvmcp libekmfweb \
-	   libkmipclient libpv
+	   libkmipclient libpv libap
 TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
 	   tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \
 	   vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
--- a/common.mak
+++ b/common.mak
@@ -387,6 +387,10 @@ $(rootdir)/libpv/libpv.a: $(rootdir)/lib
 	$(MAKE) -C $(rootdir)/libpv libpv.a
 .PHONY: $(rootdir)/libpv
 
+$(rootdir)/libap/libap.a: $(rootdir)/libap
+	$(MAKE) -C $(rootdir)/libap/ libap.a
+.PHONY: $(rootdir)/libap
+
 $(rootdir)/zipl/boot/data.o:
 	$(MAKE) -C $(rootdir)/zipl/boot/ data.o
 
--- /dev/null
+++ b/include/lib/ap.h
@@ -0,0 +1,94 @@
+/*
+ * libap - A collection of tools for ap/vfio-ap management
+ *
+ * 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 LIB_AP_H
+#define LIB_AP_H
+
+#include <stdbool.h>
+
+#include "lib/util_list.h"
+
+#define VFIO_AP_PATH "/sys/devices/vfio_ap"
+#define VFIO_AP_PARENT_PATH "devices/vfio_ap/matrix"
+#define VFIO_AP_CONFIG_PATH "/etc/mdevctl.d/matrix"
+#define VFIO_AP_TYPE "vfio_ap-passthrough"
+#define AP_UDEV_FILE "/etc/udev/rules.d/41-ap.rules"
+#define AP_LOCKFILE "/run/lock/s390apconfig.lock"
+
+#define AP_LOCK_RETRIES 15
+
+/* apmask and aqmask are each represented as 67 character strings with:
+ *  '0x' leading characters
+ *  64 hex digits (to represent 256 bits)
+ *  terminating character
+ */
+#define AP_MASK_SIZE 67
+#define AP_MAX_MASK_VALUE 255
+
+/* List structure used for keeping track of lists of adapter/domain IDs */
+struct vfio_ap_node {
+	struct util_list_node node;	/* prev/next list info */
+	unsigned int id;		/* list entry (adapter, domain, etc) */
+};
+
+/*
+ * Structure used to represent a vfio_ap-passthrough device configuration.
+ * The list of adapters and domains can be used to derive the APQNs for the
+ * device.  The type value is used to verify that the device (when read from
+ * a mdevctl config file) is of type vfio_ap-passthrough.  The manual value
+ * represents whether the device is started on-demand after boot (true) or
+ * automatically during boot (false).
+ */
+struct vfio_ap_device {
+	char *uuid;			/* Unique ID for this mdev */
+	struct util_list *adapters;	/* List of adapters for device */
+	struct util_list *domains;	/* List of usage domains for device */
+	struct util_list *controls;	/* List of control domains for device */
+	char *type;			/* mdev type string */
+	bool manual;			/* manual/auto start setting for mdev */
+};
+
+/* General Utility Functions */
+void print_ap_device(struct vfio_ap_device *dev);
+bool is_valid_uuid(const char *uuid);
+int ap_test_bit(int n, const char *hexbytestr);
+void ap_set_bit(int n, char *hexbytestr, bool val);
+
+/* Path-related functions */
+char *path_get_vfio_ap_mdev(const char *uuid);
+char *path_get_vfio_ap_mdev_config(const char *uuid);
+char *path_get_vfio_ap_attr(const char *uuid, const char *attr);
+char *path_get_ap_udev(void);
+
+/* Functions for manipulating sysfs to read active device info */
+void vfio_ap_parse_matrix(struct vfio_ap_device *dev, char *matrix);
+void vfio_ap_sort_matrix_results(struct vfio_ap_device *dev);
+void vfio_ap_parse_control(struct vfio_ap_device *dev, char *control);
+
+/* Functions for reading JSON device config */
+int vfio_ap_read_device_config(const char *path, struct vfio_ap_device *dev);
+
+/* Functions for managing vfio_ap device structures */
+struct vfio_ap_device *vfio_ap_device_new(void);
+void vfio_ap_device_clear(struct vfio_ap_device *dev);
+void vfio_ap_device_free(struct vfio_ap_device *dev);
+
+/* Functions for acquiring current vfio_ap device info */
+int ap_read_sysfs_masks(char *ap, char *aq, int size);
+bool ap_read_udev_masks(char *path, char *ap, char *aq, bool *read_ap,
+			bool *read_aq);
+void ap_mask_to_list(char *mask, struct util_list *list);
+void ap_list_remove_all(struct util_list *list);
+
+/* Lock Functions */
+int ap_get_lock(void);
+int ap_get_lock_callout(void);
+int ap_release_lock(void);
+
+#endif /* LIB_AP_H */
--- /dev/null
+++ b/libap/Makefile
@@ -0,0 +1,37 @@
+include ../common.mak
+
+lib = libap.a
+
+check-dep-lock:
+	touch check-dep-lock
+ifneq (${HAVE_LOCKFILE},0)
+	$(call check_dep, \
+		"libap", \
+		"lockfile.h", \
+		"liblockfile-devel", \
+		"HAVE_LOCKFILE=0")
+ALL_CPPFLAGS += -DHAVE_LOCKFILE
+endif
+
+check-dep-json:
+	touch check-dep-json
+ifneq (${HAVE_JSONC},0)
+	$(call check_dep, \
+		"libap", \
+		"json-c/json.h", \
+		"json-c-devel", \
+		"HAVE_JSONC=0")
+ALL_CPPFLAGS += -DHAVE_JSONC
+endif
+
+all: $(lib)
+objects = ap.o
+
+$(lib): $(objects)
+
+$(objects): check-dep-lock check-dep-json
+
+install: all
+
+clean:
+	rm -f *.o check-dep-lock check-dep-json $(lib)
--- /dev/null
+++ b/libap/ap.c
@@ -0,0 +1,760 @@
+/*
+ * libap - A collection of tools for ap/vfio-ap management
+ *
+ * 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 <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_JSONC
+#include <json-c/json.h>
+#endif /* HAVE_JSONC */
+
+#ifdef HAVE_LOCKFILE
+#include <lockfile.h>
+#endif /* HAVE_LOCKFILE */
+
+#include "lib/ap.h"
+#include "lib/util_file.h"
+#include "lib/util_libc.h"
+#include "lib/util_panic.h"
+#include "lib/util_path.h"
+#include "lib/util_udev.h"
+
+/*
+ * Return sysfs path to a bus attribute
+ * Note: caller is responsible for freeing the returned string
+ */
+static char *path_get_bus_attr(const char *bus, const char *attr)
+{
+	return util_path_sysfs("bus/%s/%s", bus, attr);
+}
+
+/*
+ * Compare two vfio_ap nodes based upon their id value. Return:
+ * -1: a < b
+ *  1: a > b
+ *  0: a == b
+ */
+static int vfio_ap_node_cmp(void *a, void *b, void *UNUSED(data))
+{
+	struct vfio_ap_node *a_node = a, *b_node = b;
+
+	if (a_node->id < b_node->id)
+		return -1;
+	return (a_node->id > b_node->id);
+}
+
+static void vfio_ap_node_add_tail(struct util_list *list, unsigned int val)
+{
+	struct vfio_ap_node *node = util_zalloc(sizeof(struct vfio_ap_node));
+
+	node->id = val;
+	util_list_add_tail(list, node);
+}
+
+/* Remove duplicate entries from a sorted vfio_ap_node list */
+static void vfio_ap_node_remove_dupes(struct util_list *list)
+{
+	struct vfio_ap_node *node, *check, *next;
+
+	util_list_iterate(list, node) {
+		/* Remove any subsequent duplicates */
+		check = util_list_next(list, node);
+
+		while (check) {
+			next = util_list_next(list, check);
+			if (node->id == check->id) {
+				util_list_remove(list, check);
+				free(check);
+			}
+			check = next;
+		}
+	}
+}
+
+static bool starts_with(const char *str, const char *s)
+{
+	size_t len = strlen(s);
+
+	return (strncmp(str, s, len) == 0);
+}
+
+/*
+ * Pass a comma-delimited string of IDs (adapter, domains or control domains)
+ * and add these IDs to the input list.  The resulting list will be sorted
+ * and duplicates removed before returning.
+ */
+static void modify_device_attr(struct util_list *list, char *value)
+{
+	unsigned int val;
+	char *curr;
+
+	curr = strtok(value, ",");
+	if (curr == NULL)
+		return;
+
+	/* Create list from input setting */
+	while (curr != NULL) {
+		val = strtol(curr, NULL, 0);
+		vfio_ap_node_add_tail(list, val);
+		curr = strtok(NULL, ",");
+	}
+
+	/* Cleanup the list */
+	util_list_sort(list, vfio_ap_node_cmp, NULL);
+	vfio_ap_node_remove_dupes(list);
+}
+
+static void load_attr_to_device(struct vfio_ap_device *dev, char *attr,
+				const char *value)
+{
+	char *v = util_strdup(value);
+
+	if (strcmp(attr, "assign_adapter") == 0)
+		modify_device_attr(dev->adapters, v);
+	else if (strcmp(attr, "assign_domain") == 0)
+		modify_device_attr(dev->domains, v);
+	else if (strcmp(attr, "assign_control_domain") == 0)
+		modify_device_attr(dev->controls, v);
+
+	free(v);
+}
+
+/**
+ * Print the contents of the vfio-ap device struct to stderr. Used for
+ * debugging.
+ *
+ * @param[in]      dev        vfio-ap device strcucture to print
+ */
+void print_ap_device(struct vfio_ap_device *dev)
+{
+	struct vfio_ap_node *node;
+
+	warnx("Device %s:", dev->uuid);
+	warnx("Type: %s", dev->type);
+	if (dev->manual)
+		warnx("Start: MANUAL");
+	else
+		warnx("Start: AUTO");
+	if (util_list_is_empty(dev->adapters)) {
+		warnx("Adapters: (none)");
+	} else {
+		warnx("Adapters:");
+		util_list_iterate(dev->adapters, node) {
+			warnx("  %u", node->id);
+		}
+	}
+	if (util_list_is_empty(dev->domains)) {
+		warnx("Domains: (none)");
+	} else {
+		warnx("Domains:");
+		util_list_iterate(dev->domains, node) {
+			warnx("  %u", node->id);
+		}
+	}
+	if (util_list_is_empty(dev->controls)) {
+		warnx("Controls: (none)");
+	} else {
+		warnx("Controls:");
+		util_list_iterate(dev->controls, node) {
+			warnx("  %u", node->id);
+		}
+	}
+}
+
+/**
+ * Determine if the input string is a valid Universally Unique Identifier
+ *
+ * @param[in]      uuid       Character string to inspect
+ *
+ * @retval         true       Specified string is a valid UUID
+ * @retval         false      Specified string is not a valid UUID
+ */
+bool is_valid_uuid(const char *uuid)
+{
+	uint32_t s1, s2, s3, s4;
+	uint64_t s5;
+	char d;
+
+	return (strlen(uuid) == 36 && sscanf(uuid, "%8x-%4x-%4x-%4x-%12x %c",
+					     &s1, &s2, &s3, &s4,
+					     (unsigned int *) &s5, &d) == 5);
+}
+
+/*
+ * For an input string of hex characters, find the nth character and return its
+ * numeric value to the caller.
+ */
+static int get_hexbyte_value(int n, const char *hexbytestr)
+{
+	int i = 0, v = 0;
+	char c;
+
+	if (strncmp(hexbytestr, "0x", 2) == 0)
+		i = 2;
+
+	/*
+	 * The specified mask is only valid if it includes at least 64 hex
+	 * digits.  The specified mask may optionally include a leading '0x'
+	 */
+	util_assert((strlen(hexbytestr) >= (size_t)(AP_MASK_SIZE - i - 1)),
+		    "Invalid hex string provided for mask: %s", hexbytestr);
+
+	c = hexbytestr[i + n / 4];
+	if (c >= '0' && c <= '9')
+		v = c - '0';
+	else if (c >= 'a' && c <= 'f')
+		v = 10 + c - 'a';
+	else if (c >= 'A' && c <= 'F')
+		v = 10 + c - 'A';
+	else
+		util_assert(false, "Could not parse hex digit '%c'", c);
+
+	return v;
+}
+
+/**
+ * For an input hex string, determine if the specified nth bit is ON or OFF.
+ *
+ * @param[in]      n          Bit number to test
+ * @param[in, out] hexbytestr Character string of hex characters
+ *
+ * @retval         0          Specified bit is OFF
+ * @retval         != 0       Specified bit is ON
+ */
+int ap_test_bit(int n, const char *hexbytestr)
+{
+	int v;
+
+	v = get_hexbyte_value(n, hexbytestr);
+
+	return v & (1 << (3 - (n % 4)));
+}
+
+/**
+ * For an input hex string, set the nth bit true/false
+ *
+ * @param[in]      n          Bit number to set
+ * @param[in, out] hexbytestr Character string of hex characters
+ * @param[in]      val        Bit is to be set ON (true) or OFF (false)
+ */
+void ap_set_bit(int n, char *hexbytestr, bool val)
+{
+	char c = 0;
+	int v, m, i = 0;
+
+	v = get_hexbyte_value(n, hexbytestr);
+
+	/* Calculate the bit mask */
+	m = (1 << (3 - (n % 4)));
+
+	/* Return if bit already at correct value */
+	if (((val) && ((v & m) != 0)) || (!val && ((v & m) == 0)))
+		return;
+
+	if (val)
+		v = v + m;
+	else
+		v = v - m;
+
+	if (v < 10)
+		c = '0' + v;
+	else if (v >= 10 && v <= 15)
+		c = 'a' + (v - 10);
+	else
+		util_assert(false, "Could not set bit value '%d'", v);
+
+	if (strncmp(hexbytestr, "0x", 2) == 0)
+		i = 2;
+
+	/* Set the new value */
+	hexbytestr[i + n / 4] = c;
+}
+
+/**
+ * Return sysfs path to vfio_ap mdev
+ * Note: caller is responsible for freeing the returned string
+ *
+ * @param[in]      uuid       Character string containing an mdev UUID
+ *
+ * @retval         != 0       sysfs path for a vfio-ap device with this UUID
+ */
+char *path_get_vfio_ap_mdev(const char *uuid)
+{
+	return util_path_sysfs("%s/%s", VFIO_AP_PARENT_PATH, uuid);
+}
+
+/**
+ * Return path to mdevctl config file for specified UUID
+ * Note: caller is responsible for freeing the returned string
+ *
+ * @param[in]      uuid       Character string containing an mdev UUID
+ *
+ * @retval         != 0       config file path for vfio-ap device with this UUID
+ */
+char *path_get_vfio_ap_mdev_config(const char *uuid)
+{
+	char *path;
+
+	util_asprintf(&path, "%s/%s", VFIO_AP_CONFIG_PATH, uuid);
+	return path;
+}
+
+/**
+ * Return sysfs path to vfio_ap mdev attribute (matrix, remove, ...)
+ * Note: caller is responsible for freeing the returned string
+ *
+ * @param[in]      uuid       Character string containing an mdev UUID
+ * @param[in]      attr       Character string containing device attribute name
+ *
+ * @retval         != 0       sysfs path to specified attribute for this device
+ */
+char *path_get_vfio_ap_attr(const char *uuid, const char *attr)
+{
+	return util_path_sysfs("%s/%s/%s", VFIO_AP_PARENT_PATH, uuid, attr);
+}
+
+/**
+ * Return path to ap udev config file
+ * Note: caller is responsible for freeing the returned string
+ *
+ * @retval         != 0       path to the ap udev config file
+ */
+char *path_get_ap_udev(void)
+{
+	char *path;
+
+	util_asprintf(&path, "%s", AP_UDEV_FILE);
+	return path;
+}
+
+/**
+ * Take one line from the active 'matrix' attribute and parse it
+ * into a list of adapters and domains.  Each line of the 'matrix'
+ * attribute is presented as a "adapter.domain" (e.g. "03.0005") where the
+ * numeric values are always hexadecimal.
+ * In a case where no adapters are assigned, a valid string might be ".0005"
+ * In a case where no domains are assigned, a valid string might be "03."
+ *
+ * @param[in, out] dev        Vfio-ap struct that will be updated
+ * @param[in]      matrix     Character string to parse
+ */
+void vfio_ap_parse_matrix(struct vfio_ap_device *dev, char *matrix)
+{
+	char *curr;
+	int val;
+
+	if (!matrix)
+		return;
+
+	if (*matrix != '.') {
+		/* Handle a device with adapters */
+		curr = strtok(matrix, ".");
+		val = strtol(curr, NULL, 16);
+		vfio_ap_node_add_tail(dev->adapters, val);
+		curr = strtok(NULL, "\n");
+	} else {
+		/* Handle a device with no adapters */
+		curr = strtok(matrix + 1, "\n");
+	}
+
+	/* Leave now if the device has no domains */
+	if (!curr)
+		return;
+	/* Get the domain */
+	val = strtol(curr, NULL, 16);
+	vfio_ap_node_add_tail(dev->domains, val);
+}
+
+/**
+ * Function to sort the results of repeated vfio_ap_parse_matrix calls
+ *
+ * @param[in, out] dev        Vfio-ap struct whose lists will be sorted
+ */
+void vfio_ap_sort_matrix_results(struct vfio_ap_device *dev)
+{
+	/* Sort the lists for later use */
+	util_list_sort(dev->adapters, vfio_ap_node_cmp, NULL);
+	util_list_sort(dev->domains, vfio_ap_node_cmp, NULL);
+
+	/* Run the lists and delete duplicates */
+	vfio_ap_node_remove_dupes(dev->adapters);
+	vfio_ap_node_remove_dupes(dev->domains);
+}
+
+/**
+ * Take the string provided by the active 'control_domains' attribute and
+ * parse it into a list of control domains
+ *
+ * @param[in, out] dev        Vfio-ap struct that will be updated
+ * @param[in]      control    Character string to parse
+ */
+void vfio_ap_parse_control(struct vfio_ap_device *dev, char *control)
+{
+	char *curr;
+	int val;
+
+	curr = strtok(control, "\n");
+
+	while (curr != NULL) {
+		val = strtol(curr, NULL, 16);
+		vfio_ap_node_add_tail(dev->controls, val);
+		curr = strtok(NULL, "\n");
+	}
+}
+
+#ifdef HAVE_JSONC
+
+/**
+ * For a given path, read in the contents.  If no path is provided, get the
+ * input from stdin instead.
+ *
+ * @param[in]      path       Path to mdevctl config file
+ * @param[in, out] dev        Vfio-ap struct that will be updated
+ *
+ * @retval         0          Config read successfully, dev updated
+ * @retval         -1         Failed to read config, dev may have partial info
+ */
+int vfio_ap_read_device_config(const char *path, struct vfio_ap_device *dev)
+{
+	json_object *root, *type, *start, *attrs, *attr;
+	int i, len, rc = 0;
+	const char *val;
+
+	if (path == NULL)
+		root = json_object_from_fd(STDIN_FILENO);
+	else
+		root = json_object_from_file(path);
+	if (root == NULL)
+		return -1;
+
+	if (json_object_object_get_ex(root, "mdev_type", &type)) {
+		val = json_object_get_string(type);
+		if (!val)
+			goto err;
+		dev->type = util_strdup(val);
+		if (strcmp(val, "vfio_ap-passthrough") != 0)
+			goto err;
+	}
+
+	if (json_object_object_get_ex(root, "start", &start)) {
+		val = json_object_get_string(start);
+		if (!val)
+			goto err;
+		if (strcmp(val, "auto") == 0)
+			dev->manual = false;
+		else if (strcmp(val, "manual") == 0)
+			dev->manual = true;
+		else
+			goto err;
+	}
+
+	if (json_object_object_get_ex(root, "attrs", &attrs)) {
+		len = json_object_array_length(attrs);
+		for (i = 0; i < len; i++) {
+			attr = json_object_array_get_idx(attrs, i);
+			json_object_object_foreach(attr, key, setting) {
+				val = json_object_get_string(setting);
+				load_attr_to_device(dev, key, val);
+			}
+		}
+	}
+
+out:
+	json_object_put(root);
+	return rc;
+
+err:
+	rc = -1;
+	goto out;
+}
+
+#else
+int vfio_ap_read_device_config(const char *path, struct vfio_ap_device *dev)
+{
+	return -1;
+}
+#endif /* HAVE_JSONC */
+
+/**
+ * Allocate and initialize a vfio-ap device structure.
+ *
+ * @retval         !=0        Address of the new vfio-ap device structure
+ */
+struct vfio_ap_device *vfio_ap_device_new(void)
+{
+	struct vfio_ap_device *dev;
+
+	dev = util_zalloc(sizeof(struct vfio_ap_device));
+
+	dev->manual = false;
+	dev->type = NULL;
+	dev->uuid = NULL;
+
+	dev->adapters = util_list_new(struct vfio_ap_node, node);
+	dev->domains = util_list_new(struct vfio_ap_node, node);
+	dev->controls = util_list_new(struct vfio_ap_node, node);
+
+	return dev;
+}
+
+/**
+ * Re-initialize a vfio-ap device structure, leaving the structure allocated.
+ *
+ * @param[in, out] dev        Vfio-ap struct that will be re-initialized
+ */
+void vfio_ap_device_clear(struct vfio_ap_device *dev)
+{
+	if (dev == NULL)
+		return;
+
+	dev->manual = false;
+	if (dev->type) {
+		free(dev->type);
+		dev->type = NULL;
+	}
+	if (dev->uuid) {
+		free(dev->uuid);
+		dev->uuid = NULL;
+	}
+
+	ap_list_remove_all(dev->adapters);
+	ap_list_remove_all(dev->domains);
+	ap_list_remove_all(dev->controls);
+}
+
+/**
+ * Clear and release a vfio-ap device structure.
+ *
+ * @param[in, out] dev        Vfio-ap struct that will be freed
+ */
+void vfio_ap_device_free(struct vfio_ap_device *dev)
+{
+	if (dev == NULL)
+		return;
+
+	vfio_ap_device_clear(dev);
+	util_list_free(dev->adapters);
+	util_list_free(dev->domains);
+	util_list_free(dev->controls);
+	free(dev);
+}
+
+static int read_sysfs_mask(const char *path, char *mask, int size)
+{
+	return util_file_read_line(mask, size, "%s", path);
+}
+
+/**
+ * Get the apmask and aqmask from sysfs
+ *
+ * @param[in, out] ap         Buffer to hold apmask contents
+ * @param[in, out] aq         Buffer to hold aqmask contents
+ * @param[in]      size       Size of the mask buffers
+ *
+ * @retval         0          Both mask values read successfully
+ * @retval         != 0       Failed to read one or both mask values
+ */
+int ap_read_sysfs_masks(char *ap, char *aq, int size)
+{
+	char *path;
+	int rc = 0;
+
+	path = path_get_bus_attr("ap", "apmask");
+	rc = read_sysfs_mask(path, ap, size);
+	free(path);
+	if (rc != 0)
+		goto out;
+	path = path_get_bus_attr("ap", "aqmask");
+	rc = read_sysfs_mask(path, aq, size);
+	free(path);
+
+out:
+	return rc;
+}
+
+/**
+ * Get the apmask and aqmask from udev, falling back to sysfs if a udev rule
+ * is not available or does not provide values for both masks.
+ * The values in read_ap and read_aq tell the caller whether each mask was
+ * successfully loaded from udev or if the sysfs value was substituted
+ * Note: both ap and aq must point to a string that is at least AP_MASK_SIZE
+ * in length.
+ *
+ * @param[in]      path       Path to the ap udev file
+ * @param[in, out] ap         Buffer to hold apmask contents
+ * @param[in, out] aq         Buffer to hold aqmask contents
+ * @param[out]     read_ap    Specifies if an apmask value was read from udev
+ * @param[out]     read_aq    Specifies if an aqmask value was read from udev
+ *
+ * @retval         true       Udev file read successfully or did not exist
+ * @retval         false      Udev file exists but error was encountered
+ */
+bool ap_read_udev_masks(char *path, char *ap, char *aq, bool *read_ap,
+			bool *read_aq)
+{
+	struct util_udev_entry_node *entry;
+	struct util_udev_file *file = NULL;
+	struct util_udev_line_node *line;
+	char sysap[AP_MASK_SIZE];
+	char sysaq[AP_MASK_SIZE];
+	int rc;
+
+	/* Assume we fail to read both masks */
+	*read_ap = *read_aq = false;
+
+	/* If a udev file doesn't exist, quietly use the active masks */
+	if (!util_path_exists(path))
+		goto out;
+
+	rc = util_udev_read_file(path, &file);
+
+	/* If errors were encountered reading the udev file, exit now */
+	if (rc)
+		return false;
+
+	util_list_iterate(&file->lines, line) {
+		entry = util_list_start(&line->entries);
+
+		/* Skip comments and empty lines. */
+		if (!entry)
+			continue;
+
+		if (starts_with(entry->key, "ATTR{")) {
+			if (strstr(entry->key, "apmask")) {
+				util_strlcpy(ap, entry->value, AP_MASK_SIZE);
+				*read_ap = true;
+			} else if (strstr(entry->key, "aqmask")) {
+				util_strlcpy(aq, entry->value, AP_MASK_SIZE);
+				*read_aq = true;
+			}
+		}
+	}
+
+	util_udev_free_file(file);
+
+out:
+	/* If we didn't read in masks, use current sysfs values */
+	if ((!*read_ap) || (!*read_aq)) {
+		rc = ap_read_sysfs_masks(sysap, sysaq, AP_MASK_SIZE);
+		if (rc != 0)
+			return false;
+		if (!*read_ap)
+			strcpy(ap, sysap);
+		if (!*read_aq)
+			strcpy(aq, sysaq);
+	}
+
+	return true;
+}
+
+/**
+ * For a given bitmask, create a list of vfio_ap_node entries corresponding
+ * to the ON bits in the mask.
+ *
+ * @param[in]      mask       Character string of hex characters
+ * @param[in, out] list       List to be updated with entries for each ON bit
+ */
+void ap_mask_to_list(char *mask, struct util_list *list)
+{
+	int i;
+
+	if (mask == NULL || list == NULL)
+		return;
+
+	for (i = 0; i <= AP_MAX_MASK_VALUE; i++) {
+		if (ap_test_bit(i, mask))
+			vfio_ap_node_add_tail(list, i);
+	}
+
+	/* Could have duplicates if the input list was not empty */
+	util_list_sort(list, vfio_ap_node_cmp, NULL);
+	vfio_ap_node_remove_dupes(list);
+}
+
+/**
+ * For the specified list, remove all elements and free each node
+ *
+ * @param[in, out] list       List that will have all entries removed
+ */
+void ap_list_remove_all(struct util_list *list)
+{
+	struct ap_node *node;
+
+	while (!util_list_is_empty(list)) {
+		node = util_list_start(list);
+		util_list_remove(list, node);
+		free(node);
+	}
+}
+
+#ifdef HAVE_LOCKFILE
+static int ap_lockfile_create(int flags)
+{
+	return lockfile_create(AP_LOCKFILE, AP_LOCK_RETRIES, flags);
+}
+
+/**
+ * Acquire the ap config lock using this process PID (L_PID)
+ *
+ * @retval         0          Lock successfully acquired on behalf of L_PID
+ * @retval         != 0       Error, lock was not obtained
+ */
+int ap_get_lock(void)
+{
+	return ap_lockfile_create(L_PID);
+}
+
+/**
+ * Acquire the ap config lock using the parent process PID (L_PPID) -- intended
+ * for use by the mdevctl callout ap-check utility
+ *
+ * @retval         0          Lock successfully acquired on behalf of L_PPID
+ * @retval         != 0       Error, lock was not obtained
+ */
+int ap_get_lock_callout(void)
+{
+	return ap_lockfile_create(L_PPID);
+}
+
+/**
+ * Release the ap config lock
+ *
+ * @retval         0          Lock successfully released or file didn't exist
+ * @retval         != 0       Error removing the lockfile
+ */
+int ap_release_lock(void)
+{
+	return lockfile_remove(AP_LOCKFILE);
+}
+#else
+/* If no liblockfile, actions are performed without acquiring the file lock */
+int ap_get_lock(void)
+{
+	return 0;
+}
+
+int ap_get_lock_callout(void)
+{
+	return 0;
+}
+
+int ap_release_lock(void)
+{
+	return 0;
+}
+
+#endif /* HAVE_LOCKFILE */
