Subject: [PATCH] [FEAT VS1822] zdev: Introduce the ap device type
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: 773d01e674628cd6a0ff49cc3cfe33d78aa587fe
Problem-ID:  VS1822

Upstream-Description:

             zdev: Introduce the ap device type

             The AP device type initially allows only the setting of type
             attributes 'apmask' and 'aqmask'.

             Reviewed-by: Tony Krowiak <akrowiak@linux.ibm.com>
             Reviewed-by: Peter Oberparleiter <oberpar@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>
---
 zdev/include/ap.h        |   40 +
 zdev/include/namespace.h |    2 
 zdev/include/path.h      |    1 
 zdev/src/Makefile        |   16 
 zdev/src/ap.c            | 1127 +++++++++++++++++++++++++++++++++++++++++++++++
 zdev/src/devtype.c       |    2 
 zdev/src/namespace.c     |    2 
 zdev/src/path.c          |    6 
 8 files changed, 1194 insertions(+), 2 deletions(-)

--- /dev/null
+++ b/zdev/include/ap.h
@@ -0,0 +1,40 @@
+/*
+ * zdev - Modify and display the persistent configuration of devices
+ *
+ * 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_H
+#define AP_H
+
+#include "lib/util_list.h"
+
+#define AP_MOD_NAME		"ap"
+#define AP_NAME			"ap"
+#define VFIO_AP_MOD_NAME	"vfio_ap"
+
+struct devtype;
+struct subtype;
+struct namespace;
+
+extern struct devtype ap_devtype;
+extern struct subtype ap_subtype;
+extern struct namespace ap_namespace;
+
+struct ap_node {
+	struct util_list_node node;
+	unsigned int id;
+};
+
+struct mdev_cb_data {
+	struct util_list *adapters;
+	struct util_list *domains;
+	bool typeap;
+	bool autoconf;
+	bool found_conflict;
+};
+
+#endif /* AP_H */
--- a/zdev/include/namespace.h
+++ b/zdev/include/namespace.h
@@ -36,7 +36,7 @@ struct namespace;
 struct subtype;
 
 /* NULL-terminated list of namespaces. */
-#define NUM_NAMESPACES	5
+#define NUM_NAMESPACES	6
 extern struct namespace *namespaces[NUM_NAMESPACES + 1];
 
 /* struct namespace - Definition of a device ID namespace
--- a/zdev/include/path.h
+++ b/zdev/include/path.h
@@ -64,6 +64,7 @@ char *path_get_sys_bus_drv(const char *,
 char *path_get_zfcp_lun_dev(struct zfcp_lun_devid *);
 char *path_get_zfcp_port_dev(struct zfcp_lun_devid *);
 char *path_get_scsi_hctl_dev(const char *);
+char *path_get_bus_attr(const char *, const char *);
 
 exit_code_t path_for_each(const char *,
 			  exit_code_t (*callback)(const char *, const char *,
--- a/zdev/src/Makefile
+++ b/zdev/src/Makefile
@@ -40,6 +40,9 @@ chzdev_objects += ctc.o ctc_auto.o
 # LCS devtype
 chzdev_objects += lcs.o lcs_auto.o
 
+# AP devtype
+chzdev_objects += ap.o
+
 # Generic CCW devtype
 chzdev_objects += generic_ccw.o
 
@@ -70,11 +73,21 @@ lszdev_objects += ctc.o ctc_auto.o
 # LCS devtype
 lszdev_objects += lcs.o lcs_auto.o
 
+# AP devtype
+lszdev_objects += ap.o
+
 # Generic CCW devtype
 lszdev_objects += generic_ccw.o
 
 all: chzdev lszdev zdev_id
 
+ifneq (${HAVE_LOCKFILE}, 0)
+LDLIBS += -llockfile
+endif
+ifneq (${HAVE_JSONC}, 0)
+LDLIBS += -ljson-c
+endif
+
 chzdev_usage.c: chzdev_usage.txt
 	$(SED) $< >$@ -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/g' \
 		      -e 's/$$/\\n"/g'
@@ -90,7 +103,8 @@ lszdev_usage.c: lszdev_usage.txt
 chzdev.o: chzdev_usage.c
 lszdev.o: lszdev_usage.c
 
-libs = $(rootdir)/libutil/libutil.a
+libs =	$(rootdir)/libap/libap.a \
+	$(rootdir)/libutil/libutil.a
 
 chzdev: $(chzdev_objects) $(libs)
 lszdev: $(lszdev_objects) $(libs)
--- /dev/null
+++ b/zdev/src/ap.c
@@ -0,0 +1,1127 @@
+/*
+ * zdev - Modify and display the persistent configuration of devices
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "lib/util_libc.h"
+#include "lib/util_path.h"
+#include "lib/util_list.h"
+#include "lib/ap.h"
+
+#include "attrib.h"
+#include "ccw.h"
+#include "device.h"
+#include "devtype.h"
+#include "internal.h"
+#include "misc.h"
+#include "namespace.h"
+#include "path.h"
+#include "ap.h"
+#include "setting.h"
+#include "udev.h"
+
+#define DEVNAME			"AP device"
+
+static const char default_mask[AP_MASK_SIZE] =
+	"0x0000000000000000000000000000000000000000000000000000000000000000";
+
+static char apmask[AP_MASK_SIZE] = {0};
+static char aqmask[AP_MASK_SIZE] = {0};
+static char p_apmask[AP_MASK_SIZE] = {0};
+static char p_aqmask[AP_MASK_SIZE] = {0};
+static bool valid_p_apmask = false, valid_p_aqmask = false;
+
+/*
+ * For 2 input masks (source and target), generate a list of all bits that are
+ * on in target and off in source (adds) as well as a list of all bits that are
+ * on in source but off in target (subs).
+ */
+static void ap_mask_list_changes(const char *source, const char *target,
+				 struct util_list *adds, struct util_list *subs)
+{
+	int i;
+	struct ap_node *node;
+
+	for (i = 0; i <= AP_MAX_MASK_VALUE; i++) {
+		if (ap_test_bit(i, source)) {
+			if (!ap_test_bit(i, target)) {
+				node = misc_malloc(sizeof(struct ap_node));
+				node->id = i;
+				util_list_add_tail(subs, node);
+			}
+		} else {
+			if (ap_test_bit(i, target)) {
+				node = misc_malloc(sizeof(struct ap_node));
+				node->id = i;
+				util_list_add_tail(adds, node);
+			}
+		}
+	}
+}
+
+/*
+ * For a given character string, convert to unsigned long and validate that
+ * it is within range.
+ */
+static bool parse_mask_entry_single(char *entry, unsigned long *val)
+{
+	char *end;
+
+	*val = strtoul(entry, &end, 0);
+	if (*val > AP_MAX_MASK_VALUE || *end != '\0')
+		return false;
+	return true;
+}
+
+/*
+ * For a given character string, split it into 2 entries delimited by a '-'
+ * character and parse each half of the range.
+ */
+static bool parse_mask_entry_range(char *entry, unsigned long *val,
+				   unsigned long *val2)
+{
+	char *entry2;
+
+	/* Find range delimeter, convert to 2 null-terminated entries */
+	entry2 = strchr(entry, '-');
+	*entry2 = '\0';
+	entry2++;
+
+	/* Handle invalid range where there is no 2nd number */
+	if (*entry2 == '\0')
+		return false;
+
+	if (!parse_mask_entry_single(entry, val))
+		return false;
+	if (!parse_mask_entry_single(entry2, val2))
+		return false;
+	if (val > val2)
+		return false;
+	return true;
+}
+
+/*
+ * For a given string, parse its contents and modify the provided mask.
+ * For assignment operations, valid strings are of the format:
+ * # or #-#
+ * Otherwise, for modification operations, valid strings are of the format:
+ * +# or +#-#
+ * -# or -#-#
+ */
+static bool parse_mask_entry(char *entry, bool assign, char *mask)
+{
+	unsigned long val, val2;
+	bool is_add = true;
+
+	/*
+	 * Modifications must begin with a +/- and assignments cannot begin
+	 * with a +/-
+	 */
+	if ((!assign && (*entry != '+' && *entry != '-')) ||
+	    (assign && (*entry == '+' || *entry == '-')))
+		return false;
+
+	/*
+	 * An assignment is now treated the same as a + modification.
+	 * But for modification operations, skip past the +/- and remember if
+	 * we are doing removal.
+	 */
+	if (!assign) {
+		if (*entry == '-')
+			is_add = false;
+		entry++;
+		/* Handle the case where there's nothing after the +/- */
+		if (*entry == '\0')
+			return false;
+	}
+
+	/* Next, parse either a single element or a range */
+	if (strchr(entry, '-')) {
+		if (!parse_mask_entry_range(entry, &val, &val2))
+			return false;
+	} else {
+		if (!parse_mask_entry_single(entry, &val))
+			return false;
+		val2 = val;
+	}
+
+	/* Set the specified bit(s) accordingly */
+	while (val <= val2) {
+		ap_set_bit(val, mask, is_add);
+		val++;
+	}
+
+	return true;
+}
+
+/*
+ * For an input string, validate that the format of the input is valid, then
+ * generate the new mask string as well as a list of bits that were added
+ * for later validation.  If all looks valid, change the mask in memory based
+ * upon the provided input
+ */
+static bool ap_validate_mask_input(char *mask, const char *input,
+				   struct util_list *adds,
+				   struct util_list *subs)
+{
+	bool is_assign = false, rc = true;
+	char *incopy = misc_strdup(input);
+	char newmask[AP_MASK_SIZE] = {0};
+	char *curr, *next;
+
+	/* If we aren't starting with +/-, it's an assignment */
+	if (*incopy != '+' && *incopy != '-') {
+		is_assign = true;
+		/* Start with all bits OFF */
+		strcpy(newmask, default_mask);
+	} else {
+		/* Otherwise, input is a list of changes */
+		strcpy(newmask, mask);
+	}
+
+	/* Get the first entry to start -- if there are none, exit on error */
+	curr = strtok(incopy, ",");
+	if (curr == NULL)
+		goto err_out;
+
+	/* Run the comma-delimited list of entries, updating the mask value */
+	do {
+		/* Ensure we null-terminate the current entry */
+		next = strtok(NULL, ",");
+		if (!parse_mask_entry(curr, is_assign, newmask))
+			goto err_out;
+		curr = next;
+	} while (curr != NULL);
+
+	/*
+	 * Translate the mask value into a list of additions/removals for later
+	 * conflict analysis.
+	 */
+	util_list_init(adds, struct ap_node, node);
+	ap_mask_list_changes(mask, newmask, adds, subs);
+	strcpy(mask, newmask);
+	goto out;
+
+err_out:
+	error("Invalid input string %s\n", input);
+	rc = false;
+
+out:
+	free(incopy);
+	return rc;
+}
+
+static int write_sysfs_mask(const char *path, char *mask)
+{
+	int rc = 0;
+	FILE *f;
+
+	f = fopen(path, "w");
+	if (!f) {
+		rc = -1;
+		goto out;
+	}
+	fputs(mask, f);
+	fclose(f);
+
+out:
+	return rc;
+}
+
+static bool ap_read_type_udev(char *ap, char *aq, bool autoconf)
+{
+	bool retval = false;
+	char *path = path_get_udev_rule(AP_NAME, NULL, autoconf);
+
+	if (ap_read_udev_masks(path, ap, aq, &valid_p_apmask, &valid_p_aqmask))
+		retval = true;
+
+	free(path);
+	return retval;
+}
+
+static exit_code_t ap_write_type_udev(char *ap, char *aq, bool autoconf)
+{
+	FILE *fd;
+	exit_code_t rc;
+	char *path = path_get_udev_rule(AP_NAME, NULL, autoconf);
+
+	/* Remove if already exists */
+	if (util_path_is_reg_file(path)) {
+		rc = remove_file(path);
+		if (rc) {
+			error("Could not remove file %s\n", path);
+			goto out;
+		}
+	}
+
+	/* If there's no masks, no need for a udev file */
+	if (!(valid_p_apmask || valid_p_aqmask))
+		return EXIT_OK;
+
+	debug("Writing udev rule file %s\n", path);
+	rc = path_create(path);
+	if (rc)
+		goto out;
+
+	fd = misc_fopen(path, "w");
+	if (!fd) {
+		error("Could not write to file %s: %s\n", path,
+		      strerror(errno));
+		rc = EXIT_RUNTIME_ERROR;
+		goto out;
+	}
+
+	/* Write udev rule prolog. */
+	fprintf(fd, "# Generated by chzdev\n");
+
+	/* Triggers. */
+	fprintf(fd, "ACTION==\"change\", SUBSYSTEM==\"ap\", "
+		"DEVPATH==\"/devices/ap\", ENV{BINDINGS}==\"complete\", "
+		"ENV{COMPLETECOUNT}==\"1\", GOTO=\"cfg_ap\"\n");
+	fprintf(fd, "GOTO=\"end_ap\"\n\n");
+
+	/* Begin Configuration */
+	fprintf(fd, "LABEL=\"cfg_ap\"\n\n");
+	if (valid_p_apmask)
+		fprintf(fd, "ATTR{../../bus/ap/apmask}=\"%s\"\n", ap);
+	if (valid_p_aqmask)
+		fprintf(fd, "ATTR{../../bus/ap/aqmask}=\"%s\"\n", aq);
+	fprintf(fd, "RUN{builtin}+=\"kmod load %s\"\n", VFIO_AP_MOD_NAME);
+
+	/* Write udev rule epilog. */
+	fprintf(fd, "\nLABEL=\"end_ap\"\n");
+
+	fclose(fd);
+
+out:
+	free(path);
+	return rc;
+}
+
+/*
+ * AP namespace
+ */
+static exit_code_t ap_is_id_valid(const char *id, err_t err)
+{
+	/* AP device doesn't support an ID */
+	err_t_print(err, "Error: %s management is not supported\n",
+		    DEVNAME);
+
+	return EXIT_INVALID_ID;
+}
+
+static int ap_cmp_ids(const char *a_str, const char *b_str)
+{
+	/* AP device doesn't support an ID */
+	return EXIT_OK;
+}
+
+static char *ap_normalize_id(const char *id)
+{
+	/* AP device doesn't support an ID, just return a copy of input */
+	return util_strdup(id);
+}
+
+static void *ap_parse_id(const char *id, err_t err)
+{
+	/* Nothing to be parsed, just use normalized string */
+	return ap_normalize_id(id);
+}
+
+static int ap_cmp_parsed_ids(const void *a, const void *b)
+{
+	return ap_cmp_ids((const char *)a, (const char *)b);
+}
+
+static int ap_qsort_cmp(const void *a_ptr, const void *b_ptr)
+{
+	const char *a = *((const char **) a_ptr);
+	const char *b = *((const char **) b_ptr);
+
+	return ap_cmp_ids(a, b);
+}
+
+static exit_code_t ap_is_id_range_valid(const char *range, err_t err)
+{
+	/* AP device doesn't support an ID */
+	err_t_print(err, "Error: %s management is not supported\n",
+		    DEVNAME);
+
+	return EXIT_INVALID_ID;
+}
+
+static unsigned long ap_num_ids_in_range(const char *range)
+{
+	/* AP device doesn't support an ID */
+	return 0;
+}
+
+static bool ap_is_id_in_range(const char *id, const char *range)
+{
+	/* AP device doesn't support an ID */
+	return false;
+}
+
+static void ap_range_start(struct ns_range_iterator *it,
+			   const char *range)
+{
+	/* AP device doesn't support an ID */
+	memset(it, 0, sizeof(struct ns_range_iterator));
+}
+
+static void ap_range_next(struct ns_range_iterator *it)
+{
+	/* AP device doesn't support an ID */
+}
+
+struct namespace ap_namespace = {
+	.devname		= DEVNAME,
+	.is_id_valid		= ap_is_id_valid,
+	.cmp_ids		= ap_cmp_ids,
+	.normalize_id		= ap_normalize_id,
+	.parse_id		= ap_parse_id,
+	.cmp_parsed_ids		= ap_cmp_parsed_ids,
+	.qsort_cmp		= ap_qsort_cmp,
+	.is_id_range_valid	= ap_is_id_range_valid,
+	.num_ids_in_range	= ap_num_ids_in_range,
+	.is_id_in_range		= ap_is_id_in_range,
+	.range_start		= ap_range_start,
+	.range_next		= ap_range_next,
+};
+
+/*
+ * AP type attributes
+ */
+static struct attrib ap_tattr_apmask = {
+	.name = "apmask",
+	.title = "Configure Cryptographic Adapter ID availability",
+	.desc =
+	"Specify the set of Cryptographic Adapters that will be available\n"
+	"for host usage.  Any bits not enabled for host usage indicate\n"
+	"adapters that are available for guest passthrough.\n"
+	"Input can be in the form of a comma-delimited list of adapters\n"
+	"such as:\n"
+	"  3,5-10,0x25\n"
+	"where the listed adapters are set for host usage and the unlisted\n"
+	"adapters are set for guest passthrough.  A range indicates that all\n"
+	"adapters in this range should be set for host usage.\n\n"
+	"Alternatively, this attribute can also be specified as a\n"
+	"comma-delimited list of changes to the current mask such as:\n"
+	"  +0-4,-0xf0\n"
+	"where each number or range preceded by a '+' will change the current\n"
+	"mask to set the specified adapters for host usage and each number\n"
+	"or range preceded by a '-' will set the specified adapters for guest\n"
+	"passthrough.\n"
+	"Adapters can be specified in both decimal and hexadecimal.",
+};
+
+static struct attrib ap_tattr_aqmask = {
+	.name = "aqmask",
+	.title = "Configure Cryptographic Queue Index availability",
+	.desc =
+	"Specify the set of Cryptographic Queue Indices that will be\n"
+	"available for host usage.  Any bits not enabled for host usage\n"
+	"indicate a queue index that is available for guest passthrough.\n"
+	"Input can be in the form of a comma-delimited list of indices\n"
+	"such as:\n"
+	"  3,5-10,0x25\n"
+	"where the listed queue indices are set for host usage and the\n"
+	"unlisted indices are set for guest passthrough.  A range indicates\n"
+	"that all indices in this range should be set for host usage.\n\n"
+	"Alternatively, this attribute can also be specified as a\n"
+	"comma-delimited list of changes to the current mask such as:\n"
+	"  +0-4,-0xf0\n"
+	"where each number or range preceded by a '+' will change the current\n"
+	"mask to set the specified indices for host usage and each number\n"
+	"or range preceded by a '-' will set the specified indices for guest\n"
+	"passthrough.\n"
+	"Queue indices can be specified in both decimal and hexadecimal.",
+};
+
+/*
+ * AP device attributes - there are currently none.
+ */
+
+/*
+ * AP subtype methods
+ */
+
+static void ap_st_init(struct subtype *st)
+{
+	st->devices = device_list_new(st);
+}
+
+static void ap_st_exit(struct subtype *st)
+{
+	device_list_free(st->devices);
+}
+
+/* Determine if an AP device exists in the active configuration. */
+static bool ap_st_exists_active(struct subtype *st, const char *id)
+{
+	return false;
+}
+
+static bool ap_st_exists_persistent(struct subtype *st, const char *id)
+{
+	return false;
+}
+
+static bool ap_st_exists_autoconf(struct subtype *st, const char *id)
+{
+	return false;
+}
+
+static void ap_st_add_active_ids(struct subtype *st, struct util_list *ids)
+{
+}
+
+static void ap_st_add_persistent_ids(struct subtype *st, struct util_list *ids)
+{
+}
+
+static void ap_st_add_autoconf_ids(struct subtype *st, struct util_list *ids)
+{
+}
+
+/* Read state of an AP device from the active configuration. */
+static exit_code_t ap_st_read_active(struct subtype *st,
+				     struct device *dev,
+				     read_scope_t scope)
+{
+	return EXIT_OK;
+}
+
+static exit_code_t ap_st_read_persistent(struct subtype *st,
+					 struct device *dev,
+					 read_scope_t scope)
+{
+	return EXIT_OK;
+}
+
+static exit_code_t ap_st_read_autoconf(struct subtype *st,
+				       struct device *dev,
+				       read_scope_t scope)
+{
+	return EXIT_OK;
+}
+
+/* Apply the settings of an AP device to the active configuration. */
+static exit_code_t ap_st_configure_active(struct subtype *st,
+					  struct device *dev)
+{
+	return device_write_active_settings(dev);
+}
+
+static exit_code_t ap_st_configure_persistent(struct subtype *st,
+					      struct device *dev)
+{
+	return EXIT_OK;
+}
+
+static exit_code_t ap_st_configure_autoconf(struct subtype *st,
+					    struct device *dev)
+{
+	return EXIT_OK;
+}
+
+static exit_code_t ap_st_deconfigure_active(struct subtype *st,
+					    struct device *dev)
+{
+	/* No additional step required */
+	return EXIT_OK;
+}
+
+static exit_code_t ap_st_deconfigure_persistent(struct subtype *st,
+						struct device *dev)
+{
+	/* No additional step required */
+	return EXIT_OK;
+}
+
+static exit_code_t ap_st_deconfigure_autoconf(struct subtype *st,
+					      struct device *dev)
+{
+	/* No additional step required */
+	return EXIT_OK;
+}
+
+/*
+ * AP subtype
+ */
+struct subtype ap_subtype = {
+	.super		= &subtype_base,
+	.devtype	= &ap_devtype,
+	.name		= AP_NAME,
+	.title		= "Cryptographic Adjunct Processor (AP) device",
+
+	.devname	= DEVNAME,
+	.modules	= STRING_ARRAY(AP_MOD_NAME),
+	.namespace	= &ap_namespace,
+
+	.dev_attribs = ATTRIB_ARRAY(),
+
+	.unknown_dev_attribs	= 0,
+	.support_definable	= 0,
+
+	.init			= &ap_st_init,
+	.exit			= &ap_st_exit,
+
+	.exists_active		= &ap_st_exists_active,
+	.exists_persistent	= &ap_st_exists_persistent,
+	.exists_autoconf	= &ap_st_exists_autoconf,
+
+	.add_active_ids		= &ap_st_add_active_ids,
+	.add_persistent_ids	= &ap_st_add_persistent_ids,
+	.add_autoconf_ids	= &ap_st_add_autoconf_ids,
+
+	.read_active		= &ap_st_read_active,
+	.read_persistent	= &ap_st_read_persistent,
+	.read_autoconf		= &ap_st_read_autoconf,
+
+	.configure_active	= &ap_st_configure_active,
+	.configure_persistent	= &ap_st_configure_persistent,
+	.configure_autoconf	= &ap_st_configure_autoconf,
+
+	.deconfigure_active	= &ap_st_deconfigure_active,
+	.deconfigure_persistent	= &ap_st_deconfigure_persistent,
+	.deconfigure_autoconf	= &ap_st_deconfigure_autoconf,
+};
+
+/*
+ * AP devtype methods
+ */
+/* Clean up all resources used by devtype object. */
+static void ap_devtype_exit(struct devtype *dt)
+{
+	setting_list_free(dt->active_settings);
+	setting_list_free(dt->persistent_settings);
+}
+
+/*
+ * Turn a full ap/aqmask bitmap into a comma-delimited list of bits
+ *
+ * Use a worst-case string size for the setting, which would be a string size
+ * for a string that has every other bit on, with up to 3 digits per bit plus
+ * commas.
+ */
+static char *ap_mask_to_setting(char *value)
+{
+	int size = ((AP_MAX_MASK_VALUE / 2) + 1)  * 4;
+	char *sval = misc_malloc(size);
+	char *curr = sval;
+	bool found_one = false;
+	int i, rc, start = -1, stop = -1;
+
+	for (i = 0; i <= AP_MAX_MASK_VALUE; i++) {
+		if (ap_test_bit(i, value)) {
+			found_one = true;
+			if (start < 0) {
+				start = stop = i;
+			} else if (stop == i - 1) {
+				stop++;
+			} else {
+				/* Write the old range, we have a new one */
+				if (start == stop)
+					/* print a single bit */
+					rc = snprintf(curr, size, "%d,", start);
+				else
+					/* print a range */
+					rc = snprintf(curr, size, "%d-%d,",
+						      start, stop);
+				curr += rc;
+				start = stop = i;
+			}
+		}
+	}
+
+	if (found_one) {
+		/* Print the final entry in the list */
+		if (start == stop)
+			/* print a single bit */
+			rc = snprintf(curr, size, "%d", start);
+		else
+			/* print a range */
+			rc = snprintf(curr, size, "%d-%d", start, stop);
+	} else {
+		/* If no bits are on, return an empty string */
+		curr[0] = '\0';
+	}
+
+	return sval;
+}
+
+static void ap_create_type_setting(struct devtype *dt,
+				   struct setting_list *slist, char *name,
+				   char *value)
+{
+	struct attrib *a;
+	struct setting *s;
+	char *c;
+
+	a = attrib_find(dt->type_attribs, name);
+	if (a != NULL) {
+		s = setting_list_find(slist, name);
+		if (s != NULL) {
+			free(s->value);
+			s->value = ap_mask_to_setting(value);
+		} else {
+			c = ap_mask_to_setting(value);
+			s = setting_new(a, NULL, c);
+			setting_list_add(slist, s);
+			free(c);
+		}
+	}
+}
+
+static exit_code_t ap_devtype_read_settings(struct devtype *dt, config_t config)
+{
+	exit_code_t rc = EXIT_OK;
+
+	dt->active_settings = setting_list_new();
+	dt->persistent_settings = setting_list_new();
+
+	rc = ap_get_lock();
+	if (rc != 0) {
+		rc = EXIT_RUNTIME_ERROR;
+		goto out2;
+	}
+
+	if (SCOPE_ACTIVE(config)) {
+		/* read apmask and aqmask */
+		if (ap_read_sysfs_masks(apmask, aqmask, AP_MASK_SIZE) != 0) {
+			rc = EXIT_RUNTIME_ERROR;
+			goto out;
+		}
+		dt->active_exists = 1;
+	}
+	if (SCOPE_PERSISTENT(config)) {
+		/* read apmask and aqmask udev */
+		if (ap_read_type_udev(p_apmask, p_aqmask, false))
+			dt->persistent_exists = 1;
+	} else if (SCOPE_AUTOCONF(config)) {
+		/* read apmask and aqmask udev */
+		if (ap_read_type_udev(p_apmask, p_aqmask, true))
+			dt->persistent_exists = 1;
+	}
+
+	if (dt->active_exists) {
+		ap_create_type_setting(dt, dt->active_settings, "apmask",
+				       apmask);
+		ap_create_type_setting(dt, dt->active_settings, "aqmask",
+				       aqmask);
+	}
+
+	if (dt->persistent_exists) {
+		if (valid_p_apmask)
+			ap_create_type_setting(dt, dt->persistent_settings,
+					       "apmask", p_apmask);
+		if (valid_p_aqmask)
+			ap_create_type_setting(dt, dt->persistent_settings,
+					       "aqmask", p_aqmask);
+	}
+
+out:
+	ap_release_lock();
+out2:
+	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 typeap, bool persistent)
+{
+	if (persistent) {
+		if (typeap) {
+			warnx("persistent apmask conflicts with defined "
+			      "autostart mdev %s APQN %u.%u", uuid, a, d);
+		} else {
+			warnx("persistent aqmask conflicts with defined "
+			      "autostart mdev %s APQN %u.%u",
+			      uuid, a, d);
+		}
+	} else {
+		if (typeap) {
+			warnx("apmask conflicts with mdev %s APQN %u.%u",
+			      uuid, a, d);
+		} else {
+			warnx("aqmask conflicts with mdev %s APQN %u.%u",
+			      uuid, a, d);
+		}
+	}
+}
+
+/*
+ * Compare the list of adapters and domains for the system and a vfio-ap
+ * device, reporting error messages for any conflicts that occur
+ */
+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 typeap,
+			       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,
+						       typeap, 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;
+}
+
+static exit_code_t check_mask_cfg_cb(const char *path,
+				     const char *filename,
+				     void *data)
+{
+	struct mdev_cb_data *cb_data = data;
+	struct vfio_ap_device *dev = NULL;
+	int rc = 0;
+
+	/* Skip anything that isn't an mdev config */
+	if (!is_valid_uuid(filename))
+		goto out;
+
+	/* Read the device config */
+	dev = vfio_ap_device_new();
+	if (vfio_ap_read_device_config(path, dev) != 0)
+		goto out;
+
+	/* If wrong device type, skip */
+	if (strcmp(dev->type, VFIO_AP_TYPE) != 0)
+		goto out;
+
+	/* If not AUTO device, skip */
+	if (dev->manual)
+		goto out;
+
+	/* Perform mdev-to-system apqn conflict analysis */
+	rc = find_apqn_conflicts(filename, cb_data->adapters, cb_data->domains,
+				 dev->adapters, dev->domains, cb_data->typeap,
+				 true);
+
+	if (rc != 0)
+		cb_data->found_conflict = true;
+
+out:
+	if (dev != NULL)
+		vfio_ap_device_free(dev);
+	/* Always return 0 so we continue looking for further conflicts */
+	return 0;
+}
+
+static exit_code_t check_mask_sysfs_cb(const char *path,
+				       const char *filename,
+				       void *data)
+{
+	struct mdev_cb_data *cb_data = data;
+	char *matrix_path;
+	FILE *f;
+	char buf[80];
+	struct vfio_ap_device *dev;
+	int rc = 0;
+
+	if (!is_valid_uuid(filename))
+		return 0;
+
+	dev = 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(dev, buf);
+	vfio_ap_sort_matrix_results(dev);
+	fclose(f);
+	free(matrix_path);
+
+	/* Look for conflicts between the system and this device */
+	rc = find_apqn_conflicts(filename, cb_data->adapters, cb_data->domains,
+				 dev->adapters, dev->domains, cb_data->typeap,
+				 false);
+
+	if (rc != 0)
+		cb_data->found_conflict = true;
+
+	vfio_ap_device_free(dev);
+
+	/* Always return 0 so we continue looking for further conflicts */
+	return 0;
+}
+
+/*
+ * We enter this function to check for mask changes under all circumstances.
+ * 'persistent' determines whether we are looking at sysfs or config files
+ * 'adapters' is used for error reporting (adapters vs domains)
+ */
+static exit_code_t check_mask_changes(struct util_list *a,
+				      struct util_list *d,
+				      bool persistent,
+				      bool typeap,
+				      bool autoconf)
+{
+	exit_code_t rc = EXIT_OK;
+	struct mdev_cb_data cb_data;
+	char *root;
+
+	cb_data.adapters = a;
+	cb_data.domains = d;
+	cb_data.found_conflict = false;
+	cb_data.typeap = typeap;
+
+	if (persistent) {
+		/* Validate udev changes against mdevctl AUTO configs */
+		root = path_get(VFIO_AP_CONFIG_PATH);
+		if (util_path_is_dir(root))
+			rc = path_for_each(root, check_mask_cfg_cb, &cb_data);
+	} else {
+		/* Validate immediate changes against active devices */
+		root = path_get_vfio_ap_mdev("");
+		if (util_path_is_dir(root))
+			rc = path_for_each(root, check_mask_sysfs_cb, &cb_data);
+	}
+
+	if (cb_data.found_conflict)
+		rc = EXIT_INVALID_CONFIG;
+
+	free(root);
+
+	return rc;
+}
+
+/* To validate changes to the system masks, consider the following set of
+ * APQNs:
+ * To validate apmask changes:  new_apmask_bits * aqmask
+ * To validate aqmask changes:  apmask * new_aqmask_bits
+ */
+static exit_code_t ap_check_mask_changes(config_t config,
+					 struct util_list *add_ap,
+					 struct util_list *add_aq,
+					 struct util_list *add_p_ap,
+					 struct util_list *add_p_aq)
+{
+	int rc = EXIT_OK, rc2 = EXIT_OK, rc3;
+	struct util_list all_ap;
+	struct util_list all_aq;
+
+	util_list_init(&all_ap, struct ap_node, node);
+	util_list_init(&all_aq, struct ap_node, node);
+
+	/* Validate the mask changes against existing vfio-ap devices */
+	if (SCOPE_ACTIVE(config) && (!util_list_is_empty(add_ap) ||
+				     !util_list_is_empty(add_aq))) {
+		ap_mask_to_list(apmask, &all_ap);
+		ap_mask_to_list(aqmask, &all_aq);
+		rc = check_mask_changes(add_ap, &all_aq, false, true, false);
+		rc2 = check_mask_changes(&all_ap, add_aq, false, false, false);
+		ap_list_remove_all(&all_ap);
+		ap_list_remove_all(&all_aq);
+		rc = rc == EXIT_OK ? rc2 : rc;
+	}
+	if (SCOPE_PERSISTENT(config) && (!util_list_is_empty(add_p_ap) ||
+					 !util_list_is_empty(add_p_aq))) {
+		ap_mask_to_list(p_apmask, &all_ap);
+		ap_mask_to_list(p_aqmask, &all_aq);
+		rc2 = check_mask_changes(add_p_ap, &all_aq, true, true, false);
+		rc3 = check_mask_changes(&all_ap, add_p_aq, true, false, false);
+		ap_list_remove_all(&all_ap);
+		ap_list_remove_all(&all_aq);
+		rc2 = rc2 == EXIT_OK ? rc3 : rc2;
+	} else if (SCOPE_AUTOCONF(config) && (!util_list_is_empty(add_p_ap) ||
+					      !util_list_is_empty(add_p_aq))) {
+		ap_mask_to_list(p_apmask, &all_ap);
+		ap_mask_to_list(p_aqmask, &all_aq);
+		rc2 = check_mask_changes(add_p_ap, &all_aq, true, true, true);
+		rc3 = check_mask_changes(&all_ap, add_p_aq, true, false, true);
+		ap_list_remove_all(&all_ap);
+		ap_list_remove_all(&all_aq);
+		rc2 = rc2 == EXIT_OK ? rc3 : rc2;
+	}
+
+	return rc == EXIT_OK ? rc2 : rc;
+}
+
+static exit_code_t ap_devtype_write_settings(struct devtype *dt,
+					     config_t config)
+{
+	struct setting *s;
+	char *path;
+	bool write_ap, write_aq, write_udev;
+	struct util_list add_ap, add_aq, sub_ap, sub_aq;
+	struct util_list add_p_ap, add_p_aq, sub_p_ap, sub_p_aq;
+	exit_code_t rc = EXIT_OK;
+	/* No kernel or module parameters exist for AP device driver. */
+
+	util_list_init(&add_ap, struct ap_node, node);
+	util_list_init(&add_aq, struct ap_node, node);
+	util_list_init(&sub_ap, struct ap_node, node);
+	util_list_init(&sub_aq, struct ap_node, node);
+	util_list_init(&add_p_ap, struct ap_node, node);
+	util_list_init(&add_p_aq, struct ap_node, node);
+	util_list_init(&sub_p_ap, struct ap_node, node);
+	util_list_init(&sub_p_aq, struct ap_node, node);
+	write_ap = write_aq = write_udev = false;
+
+	rc = ap_get_lock();
+	if (rc != 0) {
+		rc = EXIT_RUNTIME_ERROR;
+		goto out2;
+	}
+
+	/* Determine what was specified and if its valid */
+	if (SCOPE_ACTIVE(config) && dt->active_settings) {
+		s = setting_list_find(dt->active_settings, "apmask");
+		if (s != NULL && s->specified) {
+			if (!ap_validate_mask_input(apmask, s->value,
+						    &add_ap, &sub_ap)) {
+				rc = EXIT_INVALID_SETTING;
+				goto out;
+			}
+			write_ap = true;
+		}
+		s = setting_list_find(dt->active_settings, "aqmask");
+		if (s != NULL && s->specified) {
+			if (!ap_validate_mask_input(aqmask, s->value,
+						    &add_aq, &sub_aq)) {
+				rc = EXIT_INVALID_SETTING;
+				goto out;
+			}
+			write_aq = true;
+		}
+	}
+
+	if ((SCOPE_PERSISTENT(config) || SCOPE_AUTOCONF(config)) &&
+	     dt->persistent_settings) {
+		s = setting_list_find(dt->persistent_settings, "apmask");
+		if (s != NULL && s->specified) {
+			if (!ap_validate_mask_input(p_apmask, s->value,
+						    &add_p_ap, &sub_p_ap)) {
+				rc = EXIT_INVALID_SETTING;
+				goto out;
+			}
+			write_udev = true;
+			valid_p_apmask = true;
+		} else if (s != NULL && s->removed) {
+			write_udev = true;
+			valid_p_apmask = false;
+		}
+		s = setting_list_find(dt->persistent_settings, "aqmask");
+		if (s != NULL && s->specified) {
+			if (!ap_validate_mask_input(p_aqmask, s->value,
+						    &add_p_aq, &sub_p_aq)) {
+				rc = EXIT_INVALID_SETTING;
+				goto out;
+			}
+			write_udev = true;
+			valid_p_aqmask = true;
+		} else if (s != NULL && s->removed) {
+			write_udev = true;
+			valid_p_aqmask = false;
+		}
+	}
+
+	/* Check for conflicts with existing devices */
+	rc = ap_check_mask_changes(config, &add_ap, &add_aq, &add_p_ap,
+				   &add_p_aq);
+	if (rc != EXIT_OK)
+		goto out;
+
+	/* Commit the changes to sysfs/udev */
+	if (write_ap) {
+		path = path_get_bus_attr(AP_MOD_NAME, "apmask");
+		write_sysfs_mask(path, apmask);
+		free(path);
+	}
+	if (write_aq) {
+		path = path_get_bus_attr(AP_MOD_NAME, "aqmask");
+		write_sysfs_mask(path, aqmask);
+		free(path);
+	}
+	if (write_udev) {
+		if (SCOPE_PERSISTENT(config))
+			ap_write_type_udev(p_apmask, p_aqmask, false);
+		else if (SCOPE_AUTOCONF(config))
+			ap_write_type_udev(p_apmask, p_aqmask, true);
+	}
+
+out:
+	ap_list_remove_all(&add_ap);
+	ap_list_remove_all(&add_aq);
+	ap_list_remove_all(&sub_ap);
+	ap_list_remove_all(&sub_aq);
+	ap_list_remove_all(&add_p_ap);
+	ap_list_remove_all(&add_p_aq);
+	ap_list_remove_all(&sub_p_ap);
+	ap_list_remove_all(&sub_p_aq);
+	ap_release_lock();
+out2:
+	return rc;
+}
+
+/*
+ * AP devtype.
+ */
+
+struct devtype ap_devtype = {
+	.name		= "ap",
+	.title		= "", /* Only use subtypes. */
+	.devname	= "AP",
+
+	.subtypes = SUBTYPE_ARRAY(
+		&ap_subtype,
+	),
+
+	.type_attribs = ATTRIB_ARRAY(
+		&ap_tattr_apmask,
+		&ap_tattr_aqmask,
+	),
+
+	.exit			= &ap_devtype_exit,
+
+	.read_settings		= &ap_devtype_read_settings,
+	.write_settings		= &ap_devtype_write_settings,
+};
--- a/zdev/src/devtype.c
+++ b/zdev/src/devtype.c
@@ -27,6 +27,7 @@
 #include "setting.h"
 #include "subtype.h"
 #include "zfcp.h"
+#include "ap.h"
 
 /* Array of pointers to known device types. */
 struct devtype *devtypes[] = {
@@ -35,6 +36,7 @@ struct devtype *devtypes[] = {
 	&qeth_devtype,
 	&ctc_devtype,
 	&lcs_devtype,
+	&ap_devtype,
 	&generic_ccw_devtype,	/* Generic types should come last. */
 	NULL
 };
--- a/zdev/src/namespace.c
+++ b/zdev/src/namespace.c
@@ -17,6 +17,7 @@
 #include "namespace.h"
 #include "qeth.h"
 #include "zfcp_lun.h"
+#include "ap.h"
 
 struct namespace *namespaces[] = {
 	&ccw_namespace,
@@ -24,6 +25,7 @@ struct namespace *namespaces[] = {
 	&qeth_namespace,
 	&ctc_namespace,
 	&lcs_namespace,
+	&ap_namespace,
 	NULL,
 };
 
--- a/zdev/src/path.c
+++ b/zdev/src/path.c
@@ -398,3 +398,9 @@ char *path_get_scsi_hctl_dev(const char
 {
 	return path_get("/sys/bus/scsi/devices/%s", hctl);
 }
+
+/* Return sysfs path to a bus attribute */
+char *path_get_bus_attr(const char *bus, const char *attr)
+{
+	return path_get("/sys/bus/%s/%s", bus, attr);
+}
