/*
 * zdev - Modify and display the persistent configuration of devices
 *
 * Copyright IBM Corp. 2016, 2017
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "attrib.h"
#include "ccw.h"
#include "dasd.h"
#include "device.h"
#include "devtype.h"
#include "internal.h"
#include "misc.h"
#include "modprobe.h"
#include "module.h"
#include "path.h"
#include "setting.h"
#include "udev.h"

#define	DASD_MOD_NAME		"dasd_mod"
#define	DASD_ECKD_MOD_NAME	"dasd_eckd_mod"
#define	DASD_FBA_MOD_NAME	"dasd_fba_mod"
#define	DASD_DIAG_MOD_NAME	"dasd_diag_mod"

/* Subtype specific data. */
struct dasd_subtype_data {
	const char *ccwdrv;
	const char *module;
};

/* List of dependent modules. */
static const char *dasd_mods[] = {
	DASD_ECKD_MOD_NAME,
	DASD_FBA_MOD_NAME,
	DASD_DIAG_MOD_NAME,
	NULL,
};

/*
 * DASD type attributes.
 */

static struct attrib dasd_tattr_autodetect = {
	.name = "autodetect",
	.title = "Enable automatic DASD activation",
	.desc =
	"Control whether the DASD driver automatically enables all detected\n"
	"DASDs:\n"
	"  0: Only configured DASDs are enabled\n"
	"  1: All detected DASDs are automatically enabled\n",
	.defval = "0",
	.activerem = 1,
	.defunset = 1,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_tattr_probeonly = {
	.name = "probeonly",
	.title = "Inhibit access to DASD device nodes",
	.desc =
	"Control whether the DASD driver allows access to DASD devices:\n"
	"  0: All enabled DASDs can be accessed normally\n"
	"  1: Reject any attempt to open DASD devices\n",
	.defval = "0",
	.activerem = 1,
	.defunset = 1,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_tattr_nopav = {
	.name = "nopav",
	.title = "Deactivate the Parallel Access Volume feature",
	.desc = "Control the use of the Parallel Access Volume (PAV) feature:\n"
		"  0: PAV is used when supported by the storage system\n"
		"  1: The use of PAV is suppressed when running in LPAR\n",
	.defval = "0",
	.activerem = 1,
	.defunset = 1,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_tattr_nofcx = {
	.name = "nofcx",
	.title = "Deactivate the High Performance FICON feature",
	.desc = "Control the use of the High Performance FICON (HPF) feature:\n"
		"  0: HPF is used when supported by the storage hardware\n"
		"  1: HPF is not used\n",
	.defval = "0",
	.activerem = 1,
	.defunset = 1,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_tattr_eer_pages = {
	.name = "eer_pages",
	.title = "Modify buffer size for error records",
	.desc =
	"Control the number of 4 KB pages that are used for internal "
	"buffering\n"
	"of error records generated by the Extended Error Reporting (EER)\n"
	"feature.\n",
	.defval = "5",
	.activerem = 1,
	.nounload = 1,
	.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(1)),
};

/*
 * DASD device attributes.
 */

static struct attrib dasd_attr_failfast = {
	.name = "failfast",
	.title = "Modify error recovery in no-path scenario",
	.desc =
	"Control I/O handling when all paths to a DASD have been lost:\n"
	"  0: Queue I/O until path becomes available\n"
	"  1: Fail I/O request immediately\n",
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_attr_readonly = {
	.name = "readonly",
	.title = "Inhibit write access to DASD",
	.desc = "Control the DASD driver read-only setting for a DASD:\n"
		"  0: Allow writing to the device\n"
		"  1: Deny writing to the device\n",
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_attr_copy_pair = {
	.name = "copy_pair",
	.title = "Modify copy-pair relations",
	.desc = "Make a copy-pair relation for this device known to the DASD "
		"driver.\n"
		"A copy-pair is a comma-separated pair of device bus-IDs\n"
		"<primary>,<secondary>.\n"
		"Example: 0.0.1000,0.0.2000\n"
		"Up to 4 copy-pairs are accepted by the DASD driver for each "
		"device.\n",
	.unstable = 1,
	.multi = 1,
	.activerem = 1,
};

static struct attrib dasd_attr_erplog = {
	.name = "erplog",
	.title = "Enable logging of Error Recovery Processing",
	.desc = "Control logging of Error Recovery Processing (ERP), such as\n"
		"failing channel programs:\n"
		"  0: ERP logging is disabled\n"
		"  1: ERP logging is enabled\n",
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static int dasd_raw_diag_order_cmp(struct setting *, struct setting *);
static bool dasd_raw_diag_check(struct setting *, struct setting *, config_t);

static struct attrib dasd_attr_use_diag = {
	.name = "use_diag",
	.title = "Activate z/VM hypervisor assisted I/O processing",
	.desc =
	"Control I/O access mode for a DASD:\n"
	"  0: I/O is performed using standard channel programs\n"
	"  1: I/O is performed using the z/VM DIAGNOSE X'250' interface\n"
	"\n"
	"The DIAGNOSE X'250' access mode works only when:\n"
	"  - Running Linux as z/VM guest\n"
	"  - Using devices formatted with consistent block sizes, such as\n"
	"    ECKD DASDs with LDL or CMS format, or FBA devices\n",
	.order_cmp = dasd_raw_diag_order_cmp,
	.check = dasd_raw_diag_check,
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_attr_raw_track_access = {
	.name = "raw_track_access",
	.title = "Enable access to ECKD track metadata",
	.desc =
	"Control whether the DASD driver provides access to full ECKD tracks,\n"
	"including ECKD-specific meta-data:\n"
	"  0: Only access the data portion of ECKD tracks\n"
	"  1: Access the full track\n",
	.order_cmp = dasd_raw_diag_order_cmp,
	.check = dasd_raw_diag_check,
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

/* Ensure ordering if both use_diag and raw_track_access are modified. */
static int dasd_raw_diag_order_cmp(struct setting *a, struct setting *b)
{
	int a_val, b_val;

	if ((a->attrib == &dasd_attr_use_diag &&
	     b->attrib == &dasd_attr_raw_track_access) ||
	    (a->attrib == &dasd_attr_raw_track_access &&
	     b->attrib == &dasd_attr_use_diag)) {
		/* Define order to ensure that =0 is done before =1. */
		a_val = atoi(a->value);
		b_val = atoi(b->value);

		if (a_val == 1 && b_val == 0)
			return 1;
		if (a_val == 0 && b_val == 1)
			return -1;
	}

	return ccw_offline_only_order_cmp(a, b);
}

/* Conflict if both use_diag and raw_track_access are set. */
static bool dasd_raw_diag_check(struct setting *a, struct setting *b,
				config_t config)
{
	if ((a->attrib == &dasd_attr_use_diag &&
	     b->attrib == &dasd_attr_raw_track_access) ||
	    (a->attrib == &dasd_attr_raw_track_access &&
	     b->attrib == &dasd_attr_use_diag)) {
		if (atoi(a->value) == 1 && atoi(b->value) == 1)
			return false;
	}

	return ccw_offline_only_check(a, b, config);
}

static struct attrib dasd_attr_eer_enabled = {
	.name = "eer_enabled",
	.title = "Enable Extended Error Reporting",
	.desc =
	"Control the Extended Error Reporting (EER) feature for a DASD:\n"
	"  0: EER is disabled\n"
	"  1: EER is enabled\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_attr_expires = {
	.name = "expires",
	.title = "Modify I/O operation timeout",
	.desc =
	"Specify the time in seconds that the DASD driver waits for the\n"
	"completion of a single I/O operation before considering that I/O\n"
	"operation to have failed.\n"
	"\n"
	"The default value depends on the DASD type.\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(1, 40000000)),
};

static struct attrib dasd_attr_retries = {
	.name = "retries",
	.title = "Modify I/O operation retry counter",
	.desc =
	"Specify the number of times that a failed I/O should be retried\n"
	"before reporting it as failed.\n"
	"\n"
	"The default value is dependent on the DASD type.\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 32768)),
};

static struct attrib dasd_attr_timeout = {
	.name = "timeout",
	.title = "Modify I/O request timeout",
	.desc =
	"Specify the total time in seconds that the Linux block device layer\n"
	"waits for the completion of an I/O request issued to the DASD driver\n"
	"before considering that I/O to have failed. Specify 0 to deactivate\n"
	"timeout handling.\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(0)),
};

static struct attrib dasd_attr_reservation_policy = {
	.name = "reservation_policy",
	.title = "Modify lost device reservation behavior",
	.desc =
	"Control the DASD driver behavior if an existing DASD reservation of\n"
	"this system for a DASD is lost:\n"
	"  ignore: I/O operations are blocked until the external reservation\n"
	"          is released (default)\n"
	"    fail: All I/O operations are considered to have failed\n",
	.defval = "ignore",
	.accept = ACCEPT_ARRAY(ACCEPT_STR("ignore"), ACCEPT_STR("fail")),
};

static struct attrib dasd_attr_last_known_reservation_state = {
	.name = "last_known_reservation_state",
	.title = "Display and reset driver device reservation view",
	.desc =
	"Display the DASD driver's view of device reservations held by this\n"
	"system:\n"
	"      none: No reservation held or no information available\n"
	"  reserved: Device is assumed to be reserved by this system\n"
	"      lost: A reservation held by this system was lost\n"
	"\n"
	"To reset the reservation state from 'lost' to 'none', set this\n"
	"attribute to 'reset'.\n",
	.unstable = 1,
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_attr_safe_offline = {
	.name = "safe_offline",
	.title = "Deactivate DASD after processing pending I/Os",
	.desc = "Write an arbitrary value to this attribute to attempt to set\n"
		"the DASD offline after all outstanding I/O requests have\n"
		"been processed.\n",
	.activeonly = 1,
	.writeonly = 1,
};

static struct attrib dasd_attr_fc_security = {
	.name = "fc_security",
	.title = "Show FC Endpoint Security state of DASD device",
	.desc =
	"This read-only attribute shows the Fibre Channel Endpoint Security\n"
	"status of the connection to the DASD device:\n"
	"  Unsupported   : The DASD device does not support Fibre Channel\n"
	"                  Endpoint Security\n"
	"  Inconsistent  : The operational channel paths of the DASD device\n"
	"                  report inconsistent Fibre Channel Endpoint\n"
	"                  Security status\n"
	"  Authentication: The connection has been authenticated\n"
	"  Encryption    : The connection is encrypted\n",
	.readonly = 1,
};

static struct attrib dasd_attr_aq_mask = {
	.name = "aq_mask",
	.title = "Specify autoquiesce triggers",
	.desc =
	"Use the aq_mask attribute to automatically quiesce a device and block\n"
	"new I/O after certain events.\n"
	"\n"
	"The value is a bitmask in decimal or hexadecimal format where each set bit\n"
	"indicates that the associated event shown in the table below triggers an\n"
	"autoquiesce.\n"
	"  Bit 0 is not used.\n"
	"  1 - 0x02 - A terminal I/O error occurred\n"
	"  2 - 0x04 - No active channel paths remain for the device\n"
	"  3 - 0x08 - A state change interrupt occurred\n"
	"  4 - 0x10 - The device is PPRC suspended\n"
	"  5 - 0x20 - No space is left on an ESE device\n"
	"  6 - 0x40 – The number of timeouts specified in aq_timeouts is reached\n"
	"  7 - 0x80 - I/O was not started because of an error in the start function\n"
	"\n"
	"For example bits 1,3 and 5 set (0010 1010) lead to an integer value of 42\n"
	"or 0x2A.\n"
	"An integer value of 0 turns off the autoquiesce function.\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.defval = "0",
	/*
	 * Currently only 8 bits are defined and the max value is 255.
	 * This needs to be adjusted if more bits are defined.
	 */
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 255)),
};

static struct attrib dasd_attr_aq_requeue = {
	.name = "aq_requeue",
	.title = "Control I/O requeing during autoquiesce",
	.desc =
	"Use the aq_requeue attribute to control whether outstanding I/O\n"
	"operations to the blocklayer should be automatically requeued after\n"
	"an autoquiesce event.\n"
	"Valid values are 1 for requeuing, or 0 for no requeueing.\n"
	"Requeing the I/O requests to the blocklayer might benefit I/O\n"
	"in case of a copy_pair swap operation.\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.defval = "0",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)),
};

static struct attrib dasd_attr_aq_timeouts = {
	.name = "aq_timeouts",
	.title = "Specify timeout retry threshold",
	.desc =
	"Specify the number of sequential timeout events for an I/O operation\n"
	"before an autoquiesce is triggered on a device.\n"
	"This requires that the corresponding trigger bit 6 is set\n"
	"in the aq_mask attribute.\n",
	.order_cmp = ccw_online_only_order_cmp,
	.check = ccw_online_only_check,
	.defval = "32768",
	.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 32768)),
};

/*
 * DASD subtype methods.
 */

static bool _check_use_diag(struct setting_list *list)
{
	struct setting *u;

	u = setting_list_find(list, dasd_attr_use_diag.name);
	if (u && u->modified) {
		if (u->value && atoi(u->value) == 1) {
			delayed_warn("Cannot set 'use_diag=1' on non-z/VM system\n");
			return false;
		}
	}
	return true;
}

/* Check if use_diag setting can be correctly applied. */
static exit_code_t check_use_diag(struct device *dev, config_t config)
{
	if (is_zvm())
		return EXIT_OK;

	if (SCOPE_ACTIVE(config) && !_check_use_diag(dev->active.settings))
		return EXIT_INVALID_CONFIG;
	if (SCOPE_PERSISTENT(config) &&
	    !_check_use_diag(dev->persistent.settings))
		return EXIT_INVALID_CONFIG;
	if (SCOPE_AUTOCONF(config) &&
	    !_check_use_diag(dev->autoconf.settings))
		return EXIT_INVALID_CONFIG;

	return EXIT_OK;
}

static exit_code_t dasd_st_check_pre_configure(struct subtype *st,
					       struct device *dev,
					       int prereq, config_t config)
{
	exit_code_t rc;

	if (dev->active.deconfigured)
		return EXIT_OK;
	rc = check_use_diag(dev, config);
	if (rc)
		return rc;

	return EXIT_OK;
}

static void dasd_st_add_modules(struct subtype *st, struct device *dev,
				struct util_list *modules)
{
	int changed, aset, pset;

	/* Add main module. */
	st->super->add_modules(st, dev, modules);

	/* Add dasd_diag_mod if use_diag is set. */
	setting_list_get_bool_state(dev->active.settings,
				    dasd_attr_use_diag.name, &changed, &aset);
	setting_list_get_bool_state(dev->persistent.settings,
				    dasd_attr_use_diag.name, &changed, &pset);
	setting_list_get_bool_state(dev->autoconf.settings,
				    dasd_attr_use_diag.name, &changed, &pset);

	if (aset || pset)
		strlist_add_unique(modules, DASD_DIAG_MOD_NAME);
}

static char *dasd_eckd_st_get_ipldev_id(struct subtype *st)
{
	char *id, *type;

	/* Handle CCW-type IPL first. */
	id = st->super->get_ipldev_id(st);
	if (id)
		return id;

	/* Extra-handling for eckd LD-IPL. */
	type = path_read_text_file(1, err_ignore, PATH_IPL "/ipl_type");
	if (strcmp(type, "eckd") != 0)
		goto out;
	id = path_read_text_file(1, err_ignore, PATH_IPL "/ipl/device");

out:
	free(type);
	return id;
}

/*
 * DASD methods.
 */

/* Clean up all resources used by devtype object. */
static void dasd_devtype_exit(struct devtype *dt)
{
	setting_list_free(dt->active_settings);
	setting_list_free(dt->persistent_settings);
}

/* Split a dasd= module parameter string into a setting_list, based on
 * known attributes encoded in dasd= parameters. */
static void split_dasd(struct devtype *dt, const char *str,
		       struct setting_list *list)
{
	char *copy, *curr, *next, *dasd_str;
	struct attrib *a;
	struct util_list *dasd;

	dasd = strlist_new();
	copy = misc_strdup(str);
	next = copy;
	while ((curr = strsep(&next, ","))) {
		if (strcmp(curr, "(null)") == 0)
			continue;
		if (*curr == 0) {
			/* Handle dasd="" */
			continue;
		}
		/* Check for known attributes. */
		a = attrib_find(dt->type_attribs, curr);
		if (a) {
			/* Currently only boolean attributes implemented. */
			setting_list_apply_actual(list, a, curr, "1");
			continue;
		}
		/* An unknown setting or device spec - leave in dasd=. */
		strlist_add(dasd, "%s", curr);
	}
	free(copy);

	/* Add dasd= setting if necessary. */
	if (!util_list_is_empty(dasd)) {
		dasd_str = strlist_flatten(dasd, ",");
		setting_list_apply_actual(list, NULL, "dasd", dasd_str);
		free(dasd_str);
	}
	strlist_free(dasd);
}

/* Convert a module parameter list into a devtype settings list. */
static void convert_params_to_settings(struct devtype *dt,
				       struct setting_list *from,
				       struct setting_list *to)
{
	struct setting *s;

	util_list_iterate(&from->list, s) {
		if (strcmp(s->name, "dasd") == 0) {
			/* Special handling: split known attribs out. */
			split_dasd(dt, s->value, to);
			continue;
		}
		/* Just copy remaining attributes. */
		setting_list_apply_actual(to, s->attrib, s->name, s->value);
	}
}

/* Convert a devtype settings list to a module parameter list. */
static void convert_settings_to_params(struct devtype *dt,
				       struct setting_list *from,
				       struct setting_list **to)
{
	struct setting *s_from, *s_to;
	struct util_list *dasd;
	int dasd_mod;
	char *flat;

	*to = setting_list_new();
	dasd = strlist_new();
	dasd_mod = 0;

	util_list_iterate(&from->list, s_from) {
		if (s_from->removed)
			continue;
		if (s_from->derived && !s_from->modified)
			continue;
		if (strcmp(s_from->name, "eer_pages") == 0) {
			/* Copy stand-alone parameter. */
			setting_list_add(*to, setting_copy(s_from));
			continue;
		}
		/* All other settings need to go into dasd=. */
		if (strcmp(s_from->name, "dasd") == 0) {
			/* Just add explicit dasd= content. */
			strlist_add(dasd, "%s", s_from->value);
		} else if (strcmp(s_from->value, "0") == 0) {
			/* A flag in dasd= counts as set, so skip the unset
			 * ones. */
			continue;
		} else {
			/* Just add the name for set attributes. */
			strlist_add(dasd, "%s", s_from->name);
			dasd_mod += s_from->modified;
		}
	}

	flat = strlist_flatten(dasd, ",");
	s_to = setting_new(NULL, "dasd", flat);
	free(flat);
	if (dasd_mod > 0)
		s_to->modified = 1;
	if (util_list_is_empty(dasd))
		s_to->removed = 1;
	setting_list_add(*to, s_to);
	strlist_free(dasd);
}

static exit_code_t dasd_devtype_read_settings(struct devtype *dt,
					      config_t config)
{
	struct setting_list *list;
	char *path;
	exit_code_t rc = EXIT_OK;

	if (SCOPE_ACTIVE(config) && !dt->active_settings) {
		dt->active_exists = 0;
		rc = module_get_params(DASD_MOD_NAME, dt->type_attribs, &list);
		if (rc)
			return rc;
		dt->active_settings = setting_list_new();
		if (list) {
			convert_params_to_settings(dt, list,
						   dt->active_settings);
			setting_list_mark_default_derived(dt->active_settings);
			setting_list_apply_defaults(dt->active_settings,
						    dt->type_attribs, false);
			setting_list_free(list);
			dt->active_exists = 1;
		}
	}

	if (SCOPE_PERSISTENT(config) && !dt->persistent_settings) {
		dt->persistent_exists = 0;
		path = path_get_modprobe_conf(dt);
		rc = modprobe_read_settings(path, DASD_MOD_NAME,
					    dt->type_attribs, &list);
		free(path);
		if (rc)
			return rc;
		dt->persistent_settings = setting_list_new();
		if (list) {
			convert_params_to_settings(dt, list,
						   dt->persistent_settings);
			setting_list_apply_defaults(dt->persistent_settings,
						    dt->type_attribs, false);
			setting_list_free(list);
			dt->persistent_exists = 1;
		}
	}

	return rc;
}

static exit_code_t dasd_devtype_write_settings(struct devtype *dt,
					       config_t config)
{
	struct setting_list *list;
	char *path;
	exit_code_t rc = EXIT_OK;

	if (SCOPE_ACTIVE(config) && dt->active_settings) {
		/* Try setting parameters directly via Sysfs. */
		if (module_set_params(DASD_MOD_NAME, dt->active_settings))
			goto persistent;

		convert_settings_to_params(dt, dt->active_settings, &list);
		rc = module_load(DASD_MOD_NAME, dasd_mods, list,
				 err_delayed_print);
		setting_list_free(list);
		if (rc)
			return rc;
	}

persistent:
	if (SCOPE_PERSISTENT(config) && dt->persistent_settings) {
		path = path_get_modprobe_conf(dt);
		if (!rc) {
			convert_settings_to_params(dt, dt->persistent_settings,
						   &list);
			rc = modprobe_write_settings(path, DASD_MOD_NAME, list);
			setting_list_free(list);
		}
		free(path);
	}

	return rc;
}

/* Remove all entries from the copy_pair attribute by writing 'clear' to it. */
static void dasd_clear_copy_pair(struct setting *s)
{
	free(s->value);
	s->value = misc_strdup("clear");
	strlist_free(s->values);
	s->values = strlist_new();
	strlist_add(s->values, "clear");
	s->removed = 0;
	s->modified = 1;
}

/*
 * Copy-pair values may contain multiple values in one line
 * those need to be split up and put in multiple values entries
 * delimiter is " " and "\n" depending on the source
 * values read from sysfs have " " as delimiter and values from
 * zdev have "\n"
 */
static void dasd_split_copy_pair(struct setting *s)
{
	struct util_list *new = NULL;
	struct strlist_node *node;

	new = strlist_new();
	util_list_iterate(s->values, node)
		strlist_add_multi(new, node->str, "\n ", 0);

	strlist_free(s->values);
	s->values = new;
}

/*
 * For persistent configurations one value per values[] entry is required
 * to achieve a correct multi-line copy-pair statement in the resulting
 * udev-rule.
 */
static exit_code_t dasd_st_configure_persistent(struct subtype *st,
						struct device *dev)
{
	struct setting *s;

	util_list_iterate(&dev->persistent.settings->list, s) {
		if (strcmp(s->name, "copy_pair") == 0)
			dasd_split_copy_pair(s);
	}

	return st->super->configure_persistent(st, dev);
}

/*
 * For active configurations one value per values[] entry is required to
 * correctly operate the sysfs attribute.
 *
 * In case of removal the string "clear" has to be written to the
 * value and values[] entry.
 */
static exit_code_t dasd_st_configure_active(struct subtype *st,
					    struct device *dev)
{
	struct setting *s;

	util_list_iterate(&dev->active.settings->list, s) {
		if (strcmp(s->name, "copy_pair") != 0)
			continue;

		if (s->removed)
			dasd_clear_copy_pair(s);
		else
			dasd_split_copy_pair(s);
	}

	return st->super->configure_active(st, dev);
}

/*
 * DASD device sub-types.
 */
static struct ccw_subtype_data dasd_eckd_data = {
	.ccwdrv		= "dasd-eckd",
	.mod		= "dasd_eckd_mod",
};

struct subtype dasd_subtype_eckd = {
	.super		= &ccw_subtype,
	.devtype	= &dasd_devtype,
	.name		= "dasd-eckd",
	.title		= "Enhanced Count Key Data (ECKD) DASDs",
	.devname	= "ECKD DASD",
	.modules	= STRING_ARRAY(DASD_ECKD_MOD_NAME),
	.namespace	= &ccw_namespace,
	.data		= &dasd_eckd_data,

	.dev_attribs = ATTRIB_ARRAY(
		&ccw_attr_online_force,
		&ccw_attr_cmb_enable,
		&dasd_attr_failfast,
		&dasd_attr_readonly,
		&dasd_attr_erplog,
		&dasd_attr_use_diag,
		&dasd_attr_raw_track_access,
		&dasd_attr_eer_enabled,
		&dasd_attr_expires,
		&dasd_attr_retries,
		&dasd_attr_timeout,
		&dasd_attr_reservation_policy,
		&dasd_attr_last_known_reservation_state,
		&dasd_attr_safe_offline,
		&dasd_attr_fc_security,
		&dasd_attr_copy_pair,
		&dasd_attr_aq_mask,
		&dasd_attr_aq_requeue,
		&dasd_attr_aq_timeouts,
		&internal_attr_early,
	),
	.unknown_dev_attribs	= 1,

	.configure_active	= &dasd_st_configure_active,
	.configure_persistent	= &dasd_st_configure_persistent,
	.check_pre_configure	= &dasd_st_check_pre_configure,
	.add_modules		= &dasd_st_add_modules,
	.get_ipldev_id		= &dasd_eckd_st_get_ipldev_id,
};

static struct ccw_subtype_data dasd_fba_data = {
	.ccwdrv		= "dasd-fba",
	.mod		= "dasd_fba_mod",
};

struct subtype dasd_subtype_fba = {
	.super		= &ccw_subtype,
	.devtype	= &dasd_devtype,
	.name		= "dasd-fba",
	.title		= "Fixed Block Architecture (FBA) DASDs",
	.devname	= "FBA DASD",
	.modules	= STRING_ARRAY(DASD_FBA_MOD_NAME),
	.namespace	= &ccw_namespace,
	.data		= &dasd_fba_data,

	.dev_attribs = ATTRIB_ARRAY(
		&ccw_attr_online_force,
		&ccw_attr_cmb_enable,
		&dasd_attr_failfast,
		&dasd_attr_readonly,
		&dasd_attr_erplog,
		&dasd_attr_use_diag,
		&dasd_attr_expires,
		&dasd_attr_retries,
		&dasd_attr_timeout,
		&dasd_attr_reservation_policy,
		&dasd_attr_last_known_reservation_state,
		&dasd_attr_safe_offline,
		&internal_attr_early,
	),
	.unknown_dev_attribs	= 1,

	.check_pre_configure	= &dasd_st_check_pre_configure,
	.add_modules		= &dasd_st_add_modules,
};

/*
 * DASD device type.
 */

struct devtype dasd_devtype = {
	.name		= "dasd",
	.title		= "FICON-attached Direct Access Storage Devices "
			  "(DASDs)",
	.devname	= "DASD",
	.modules	= STRING_ARRAY(DASD_MOD_NAME),
	.site_support	= 1,

	.subtypes = SUBTYPE_ARRAY(
		&dasd_subtype_eckd,
		&dasd_subtype_fba,
	),

	.type_attribs = ATTRIB_ARRAY(
		&dasd_tattr_autodetect,
		&dasd_tattr_probeonly,
		&dasd_tattr_nopav,
		&dasd_tattr_nofcx,
		&dasd_tattr_eer_pages,
	),

	.exit			= &dasd_devtype_exit,

	.read_settings		= &dasd_devtype_read_settings,
	.write_settings		= &dasd_devtype_write_settings,
};
