162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * PAV alias management for the DASD ECKD discipline
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright IBM Corp. 2007
662306a36Sopenharmony_ci * Author(s): Stefan Weinhuber <wein@de.ibm.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define KMSG_COMPONENT "dasd-eckd"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/list.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <asm/ebcdic.h>
1462306a36Sopenharmony_ci#include "dasd_int.h"
1562306a36Sopenharmony_ci#include "dasd_eckd.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#ifdef PRINTK_HEADER
1862306a36Sopenharmony_ci#undef PRINTK_HEADER
1962306a36Sopenharmony_ci#endif				/* PRINTK_HEADER */
2062306a36Sopenharmony_ci#define PRINTK_HEADER "dasd(eckd):"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/*
2462306a36Sopenharmony_ci * General concept of alias management:
2562306a36Sopenharmony_ci * - PAV and DASD alias management is specific to the eckd discipline.
2662306a36Sopenharmony_ci * - A device is connected to an lcu as long as the device exists.
2762306a36Sopenharmony_ci *   dasd_alias_make_device_known_to_lcu will be called wenn the
2862306a36Sopenharmony_ci *   device is checked by the eckd discipline and
2962306a36Sopenharmony_ci *   dasd_alias_disconnect_device_from_lcu will be called
3062306a36Sopenharmony_ci *   before the device is deleted.
3162306a36Sopenharmony_ci * - The dasd_alias_add_device / dasd_alias_remove_device
3262306a36Sopenharmony_ci *   functions mark the point when a device is 'ready for service'.
3362306a36Sopenharmony_ci * - A summary unit check is a rare occasion, but it is mandatory to
3462306a36Sopenharmony_ci *   support it. It requires some complex recovery actions before the
3562306a36Sopenharmony_ci *   devices can be used again (see dasd_alias_handle_summary_unit_check).
3662306a36Sopenharmony_ci * - dasd_alias_get_start_dev will find an alias device that can be used
3762306a36Sopenharmony_ci *   instead of the base device and does some (very simple) load balancing.
3862306a36Sopenharmony_ci *   This is the function that gets called for each I/O, so when improving
3962306a36Sopenharmony_ci *   something, this function should get faster or better, the rest has just
4062306a36Sopenharmony_ci *   to be correct.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic void summary_unit_check_handling_work(struct work_struct *);
4562306a36Sopenharmony_cistatic void lcu_update_work(struct work_struct *);
4662306a36Sopenharmony_cistatic int _schedule_lcu_update(struct alias_lcu *, struct dasd_device *);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic struct alias_root aliastree = {
4962306a36Sopenharmony_ci	.serverlist = LIST_HEAD_INIT(aliastree.serverlist),
5062306a36Sopenharmony_ci	.lock = __SPIN_LOCK_UNLOCKED(aliastree.lock),
5162306a36Sopenharmony_ci};
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic struct alias_server *_find_server(struct dasd_uid *uid)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct alias_server *pos;
5662306a36Sopenharmony_ci	list_for_each_entry(pos, &aliastree.serverlist, server) {
5762306a36Sopenharmony_ci		if (!strncmp(pos->uid.vendor, uid->vendor,
5862306a36Sopenharmony_ci			     sizeof(uid->vendor))
5962306a36Sopenharmony_ci		    && !strncmp(pos->uid.serial, uid->serial,
6062306a36Sopenharmony_ci				sizeof(uid->serial)))
6162306a36Sopenharmony_ci			return pos;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci	return NULL;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic struct alias_lcu *_find_lcu(struct alias_server *server,
6762306a36Sopenharmony_ci				   struct dasd_uid *uid)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct alias_lcu *pos;
7062306a36Sopenharmony_ci	list_for_each_entry(pos, &server->lculist, lcu) {
7162306a36Sopenharmony_ci		if (pos->uid.ssid == uid->ssid)
7262306a36Sopenharmony_ci			return pos;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci	return NULL;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic struct alias_pav_group *_find_group(struct alias_lcu *lcu,
7862306a36Sopenharmony_ci					   struct dasd_uid *uid)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	struct alias_pav_group *pos;
8162306a36Sopenharmony_ci	__u8 search_unit_addr;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/* for hyper pav there is only one group */
8462306a36Sopenharmony_ci	if (lcu->pav == HYPER_PAV) {
8562306a36Sopenharmony_ci		if (list_empty(&lcu->grouplist))
8662306a36Sopenharmony_ci			return NULL;
8762306a36Sopenharmony_ci		else
8862306a36Sopenharmony_ci			return list_first_entry(&lcu->grouplist,
8962306a36Sopenharmony_ci						struct alias_pav_group, group);
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* for base pav we have to find the group that matches the base */
9362306a36Sopenharmony_ci	if (uid->type == UA_BASE_DEVICE)
9462306a36Sopenharmony_ci		search_unit_addr = uid->real_unit_addr;
9562306a36Sopenharmony_ci	else
9662306a36Sopenharmony_ci		search_unit_addr = uid->base_unit_addr;
9762306a36Sopenharmony_ci	list_for_each_entry(pos, &lcu->grouplist, group) {
9862306a36Sopenharmony_ci		if (pos->uid.base_unit_addr == search_unit_addr &&
9962306a36Sopenharmony_ci		    !strncmp(pos->uid.vduit, uid->vduit, sizeof(uid->vduit)))
10062306a36Sopenharmony_ci			return pos;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci	return NULL;
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic struct alias_server *_allocate_server(struct dasd_uid *uid)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct alias_server *server;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	server = kzalloc(sizeof(*server), GFP_KERNEL);
11062306a36Sopenharmony_ci	if (!server)
11162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
11262306a36Sopenharmony_ci	memcpy(server->uid.vendor, uid->vendor, sizeof(uid->vendor));
11362306a36Sopenharmony_ci	memcpy(server->uid.serial, uid->serial, sizeof(uid->serial));
11462306a36Sopenharmony_ci	INIT_LIST_HEAD(&server->server);
11562306a36Sopenharmony_ci	INIT_LIST_HEAD(&server->lculist);
11662306a36Sopenharmony_ci	return server;
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic void _free_server(struct alias_server *server)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	kfree(server);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic struct alias_lcu *_allocate_lcu(struct dasd_uid *uid)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct alias_lcu *lcu;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	lcu = kzalloc(sizeof(*lcu), GFP_KERNEL);
12962306a36Sopenharmony_ci	if (!lcu)
13062306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
13162306a36Sopenharmony_ci	lcu->uac = kzalloc(sizeof(*(lcu->uac)), GFP_KERNEL | GFP_DMA);
13262306a36Sopenharmony_ci	if (!lcu->uac)
13362306a36Sopenharmony_ci		goto out_err1;
13462306a36Sopenharmony_ci	lcu->rsu_cqr = kzalloc(sizeof(*lcu->rsu_cqr), GFP_KERNEL | GFP_DMA);
13562306a36Sopenharmony_ci	if (!lcu->rsu_cqr)
13662306a36Sopenharmony_ci		goto out_err2;
13762306a36Sopenharmony_ci	lcu->rsu_cqr->cpaddr = kzalloc(sizeof(struct ccw1),
13862306a36Sopenharmony_ci				       GFP_KERNEL | GFP_DMA);
13962306a36Sopenharmony_ci	if (!lcu->rsu_cqr->cpaddr)
14062306a36Sopenharmony_ci		goto out_err3;
14162306a36Sopenharmony_ci	lcu->rsu_cqr->data = kzalloc(16, GFP_KERNEL | GFP_DMA);
14262306a36Sopenharmony_ci	if (!lcu->rsu_cqr->data)
14362306a36Sopenharmony_ci		goto out_err4;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	memcpy(lcu->uid.vendor, uid->vendor, sizeof(uid->vendor));
14662306a36Sopenharmony_ci	memcpy(lcu->uid.serial, uid->serial, sizeof(uid->serial));
14762306a36Sopenharmony_ci	lcu->uid.ssid = uid->ssid;
14862306a36Sopenharmony_ci	lcu->pav = NO_PAV;
14962306a36Sopenharmony_ci	lcu->flags = NEED_UAC_UPDATE | UPDATE_PENDING;
15062306a36Sopenharmony_ci	INIT_LIST_HEAD(&lcu->lcu);
15162306a36Sopenharmony_ci	INIT_LIST_HEAD(&lcu->inactive_devices);
15262306a36Sopenharmony_ci	INIT_LIST_HEAD(&lcu->active_devices);
15362306a36Sopenharmony_ci	INIT_LIST_HEAD(&lcu->grouplist);
15462306a36Sopenharmony_ci	INIT_WORK(&lcu->suc_data.worker, summary_unit_check_handling_work);
15562306a36Sopenharmony_ci	INIT_DELAYED_WORK(&lcu->ruac_data.dwork, lcu_update_work);
15662306a36Sopenharmony_ci	spin_lock_init(&lcu->lock);
15762306a36Sopenharmony_ci	init_completion(&lcu->lcu_setup);
15862306a36Sopenharmony_ci	return lcu;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ciout_err4:
16162306a36Sopenharmony_ci	kfree(lcu->rsu_cqr->cpaddr);
16262306a36Sopenharmony_ciout_err3:
16362306a36Sopenharmony_ci	kfree(lcu->rsu_cqr);
16462306a36Sopenharmony_ciout_err2:
16562306a36Sopenharmony_ci	kfree(lcu->uac);
16662306a36Sopenharmony_ciout_err1:
16762306a36Sopenharmony_ci	kfree(lcu);
16862306a36Sopenharmony_ci	return ERR_PTR(-ENOMEM);
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic void _free_lcu(struct alias_lcu *lcu)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	kfree(lcu->rsu_cqr->data);
17462306a36Sopenharmony_ci	kfree(lcu->rsu_cqr->cpaddr);
17562306a36Sopenharmony_ci	kfree(lcu->rsu_cqr);
17662306a36Sopenharmony_ci	kfree(lcu->uac);
17762306a36Sopenharmony_ci	kfree(lcu);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci/*
18162306a36Sopenharmony_ci * This is the function that will allocate all the server and lcu data,
18262306a36Sopenharmony_ci * so this function must be called first for a new device.
18362306a36Sopenharmony_ci * If the return value is 1, the lcu was already known before, if it
18462306a36Sopenharmony_ci * is 0, this is a new lcu.
18562306a36Sopenharmony_ci * Negative return code indicates that something went wrong (e.g. -ENOMEM)
18662306a36Sopenharmony_ci */
18762306a36Sopenharmony_ciint dasd_alias_make_device_known_to_lcu(struct dasd_device *device)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
19062306a36Sopenharmony_ci	unsigned long flags;
19162306a36Sopenharmony_ci	struct alias_server *server, *newserver;
19262306a36Sopenharmony_ci	struct alias_lcu *lcu, *newlcu;
19362306a36Sopenharmony_ci	struct dasd_uid uid;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	device->discipline->get_uid(device, &uid);
19662306a36Sopenharmony_ci	spin_lock_irqsave(&aliastree.lock, flags);
19762306a36Sopenharmony_ci	server = _find_server(&uid);
19862306a36Sopenharmony_ci	if (!server) {
19962306a36Sopenharmony_ci		spin_unlock_irqrestore(&aliastree.lock, flags);
20062306a36Sopenharmony_ci		newserver = _allocate_server(&uid);
20162306a36Sopenharmony_ci		if (IS_ERR(newserver))
20262306a36Sopenharmony_ci			return PTR_ERR(newserver);
20362306a36Sopenharmony_ci		spin_lock_irqsave(&aliastree.lock, flags);
20462306a36Sopenharmony_ci		server = _find_server(&uid);
20562306a36Sopenharmony_ci		if (!server) {
20662306a36Sopenharmony_ci			list_add(&newserver->server, &aliastree.serverlist);
20762306a36Sopenharmony_ci			server = newserver;
20862306a36Sopenharmony_ci		} else {
20962306a36Sopenharmony_ci			/* someone was faster */
21062306a36Sopenharmony_ci			_free_server(newserver);
21162306a36Sopenharmony_ci		}
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	lcu = _find_lcu(server, &uid);
21562306a36Sopenharmony_ci	if (!lcu) {
21662306a36Sopenharmony_ci		spin_unlock_irqrestore(&aliastree.lock, flags);
21762306a36Sopenharmony_ci		newlcu = _allocate_lcu(&uid);
21862306a36Sopenharmony_ci		if (IS_ERR(newlcu))
21962306a36Sopenharmony_ci			return PTR_ERR(newlcu);
22062306a36Sopenharmony_ci		spin_lock_irqsave(&aliastree.lock, flags);
22162306a36Sopenharmony_ci		lcu = _find_lcu(server, &uid);
22262306a36Sopenharmony_ci		if (!lcu) {
22362306a36Sopenharmony_ci			list_add(&newlcu->lcu, &server->lculist);
22462306a36Sopenharmony_ci			lcu = newlcu;
22562306a36Sopenharmony_ci		} else {
22662306a36Sopenharmony_ci			/* someone was faster */
22762306a36Sopenharmony_ci			_free_lcu(newlcu);
22862306a36Sopenharmony_ci		}
22962306a36Sopenharmony_ci	}
23062306a36Sopenharmony_ci	spin_lock(&lcu->lock);
23162306a36Sopenharmony_ci	list_add(&device->alias_list, &lcu->inactive_devices);
23262306a36Sopenharmony_ci	private->lcu = lcu;
23362306a36Sopenharmony_ci	spin_unlock(&lcu->lock);
23462306a36Sopenharmony_ci	spin_unlock_irqrestore(&aliastree.lock, flags);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return 0;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci/*
24062306a36Sopenharmony_ci * This function removes a device from the scope of alias management.
24162306a36Sopenharmony_ci * The complicated part is to make sure that it is not in use by
24262306a36Sopenharmony_ci * any of the workers. If necessary cancel the work.
24362306a36Sopenharmony_ci */
24462306a36Sopenharmony_civoid dasd_alias_disconnect_device_from_lcu(struct dasd_device *device)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
24762306a36Sopenharmony_ci	unsigned long flags;
24862306a36Sopenharmony_ci	struct alias_lcu *lcu;
24962306a36Sopenharmony_ci	struct alias_server *server;
25062306a36Sopenharmony_ci	int was_pending;
25162306a36Sopenharmony_ci	struct dasd_uid uid;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	lcu = private->lcu;
25462306a36Sopenharmony_ci	/* nothing to do if already disconnected */
25562306a36Sopenharmony_ci	if (!lcu)
25662306a36Sopenharmony_ci		return;
25762306a36Sopenharmony_ci	device->discipline->get_uid(device, &uid);
25862306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
25962306a36Sopenharmony_ci	/* make sure that the workers don't use this device */
26062306a36Sopenharmony_ci	if (device == lcu->suc_data.device) {
26162306a36Sopenharmony_ci		spin_unlock_irqrestore(&lcu->lock, flags);
26262306a36Sopenharmony_ci		cancel_work_sync(&lcu->suc_data.worker);
26362306a36Sopenharmony_ci		spin_lock_irqsave(&lcu->lock, flags);
26462306a36Sopenharmony_ci		if (device == lcu->suc_data.device) {
26562306a36Sopenharmony_ci			dasd_put_device(device);
26662306a36Sopenharmony_ci			lcu->suc_data.device = NULL;
26762306a36Sopenharmony_ci		}
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci	was_pending = 0;
27062306a36Sopenharmony_ci	if (device == lcu->ruac_data.device) {
27162306a36Sopenharmony_ci		spin_unlock_irqrestore(&lcu->lock, flags);
27262306a36Sopenharmony_ci		was_pending = 1;
27362306a36Sopenharmony_ci		cancel_delayed_work_sync(&lcu->ruac_data.dwork);
27462306a36Sopenharmony_ci		spin_lock_irqsave(&lcu->lock, flags);
27562306a36Sopenharmony_ci		if (device == lcu->ruac_data.device) {
27662306a36Sopenharmony_ci			dasd_put_device(device);
27762306a36Sopenharmony_ci			lcu->ruac_data.device = NULL;
27862306a36Sopenharmony_ci		}
27962306a36Sopenharmony_ci	}
28062306a36Sopenharmony_ci	private->lcu = NULL;
28162306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	spin_lock_irqsave(&aliastree.lock, flags);
28462306a36Sopenharmony_ci	spin_lock(&lcu->lock);
28562306a36Sopenharmony_ci	list_del_init(&device->alias_list);
28662306a36Sopenharmony_ci	if (list_empty(&lcu->grouplist) &&
28762306a36Sopenharmony_ci	    list_empty(&lcu->active_devices) &&
28862306a36Sopenharmony_ci	    list_empty(&lcu->inactive_devices)) {
28962306a36Sopenharmony_ci		list_del(&lcu->lcu);
29062306a36Sopenharmony_ci		spin_unlock(&lcu->lock);
29162306a36Sopenharmony_ci		_free_lcu(lcu);
29262306a36Sopenharmony_ci		lcu = NULL;
29362306a36Sopenharmony_ci	} else {
29462306a36Sopenharmony_ci		if (was_pending)
29562306a36Sopenharmony_ci			_schedule_lcu_update(lcu, NULL);
29662306a36Sopenharmony_ci		spin_unlock(&lcu->lock);
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci	server = _find_server(&uid);
29962306a36Sopenharmony_ci	if (server && list_empty(&server->lculist)) {
30062306a36Sopenharmony_ci		list_del(&server->server);
30162306a36Sopenharmony_ci		_free_server(server);
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci	spin_unlock_irqrestore(&aliastree.lock, flags);
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci/*
30762306a36Sopenharmony_ci * This function assumes that the unit address configuration stored
30862306a36Sopenharmony_ci * in the lcu is up to date and will update the device uid before
30962306a36Sopenharmony_ci * adding it to a pav group.
31062306a36Sopenharmony_ci */
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_cistatic int _add_device_to_lcu(struct alias_lcu *lcu,
31362306a36Sopenharmony_ci			      struct dasd_device *device,
31462306a36Sopenharmony_ci			      struct dasd_device *pos)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
31862306a36Sopenharmony_ci	struct alias_pav_group *group;
31962306a36Sopenharmony_ci	struct dasd_uid uid;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	spin_lock(get_ccwdev_lock(device->cdev));
32262306a36Sopenharmony_ci	private->uid.type = lcu->uac->unit[private->uid.real_unit_addr].ua_type;
32362306a36Sopenharmony_ci	private->uid.base_unit_addr =
32462306a36Sopenharmony_ci		lcu->uac->unit[private->uid.real_unit_addr].base_ua;
32562306a36Sopenharmony_ci	uid = private->uid;
32662306a36Sopenharmony_ci	spin_unlock(get_ccwdev_lock(device->cdev));
32762306a36Sopenharmony_ci	/* if we have no PAV anyway, we don't need to bother with PAV groups */
32862306a36Sopenharmony_ci	if (lcu->pav == NO_PAV) {
32962306a36Sopenharmony_ci		list_move(&device->alias_list, &lcu->active_devices);
33062306a36Sopenharmony_ci		return 0;
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci	group = _find_group(lcu, &uid);
33362306a36Sopenharmony_ci	if (!group) {
33462306a36Sopenharmony_ci		group = kzalloc(sizeof(*group), GFP_ATOMIC);
33562306a36Sopenharmony_ci		if (!group)
33662306a36Sopenharmony_ci			return -ENOMEM;
33762306a36Sopenharmony_ci		memcpy(group->uid.vendor, uid.vendor, sizeof(uid.vendor));
33862306a36Sopenharmony_ci		memcpy(group->uid.serial, uid.serial, sizeof(uid.serial));
33962306a36Sopenharmony_ci		group->uid.ssid = uid.ssid;
34062306a36Sopenharmony_ci		if (uid.type == UA_BASE_DEVICE)
34162306a36Sopenharmony_ci			group->uid.base_unit_addr = uid.real_unit_addr;
34262306a36Sopenharmony_ci		else
34362306a36Sopenharmony_ci			group->uid.base_unit_addr = uid.base_unit_addr;
34462306a36Sopenharmony_ci		memcpy(group->uid.vduit, uid.vduit, sizeof(uid.vduit));
34562306a36Sopenharmony_ci		INIT_LIST_HEAD(&group->group);
34662306a36Sopenharmony_ci		INIT_LIST_HEAD(&group->baselist);
34762306a36Sopenharmony_ci		INIT_LIST_HEAD(&group->aliaslist);
34862306a36Sopenharmony_ci		list_add(&group->group, &lcu->grouplist);
34962306a36Sopenharmony_ci	}
35062306a36Sopenharmony_ci	if (uid.type == UA_BASE_DEVICE)
35162306a36Sopenharmony_ci		list_move(&device->alias_list, &group->baselist);
35262306a36Sopenharmony_ci	else
35362306a36Sopenharmony_ci		list_move(&device->alias_list, &group->aliaslist);
35462306a36Sopenharmony_ci	private->pavgroup = group;
35562306a36Sopenharmony_ci	return 0;
35662306a36Sopenharmony_ci};
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_cistatic void _remove_device_from_lcu(struct alias_lcu *lcu,
35962306a36Sopenharmony_ci				    struct dasd_device *device)
36062306a36Sopenharmony_ci{
36162306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
36262306a36Sopenharmony_ci	struct alias_pav_group *group;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	list_move(&device->alias_list, &lcu->inactive_devices);
36562306a36Sopenharmony_ci	group = private->pavgroup;
36662306a36Sopenharmony_ci	if (!group)
36762306a36Sopenharmony_ci		return;
36862306a36Sopenharmony_ci	private->pavgroup = NULL;
36962306a36Sopenharmony_ci	if (list_empty(&group->baselist) && list_empty(&group->aliaslist)) {
37062306a36Sopenharmony_ci		list_del(&group->group);
37162306a36Sopenharmony_ci		kfree(group);
37262306a36Sopenharmony_ci		return;
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci	if (group->next == device)
37562306a36Sopenharmony_ci		group->next = NULL;
37662306a36Sopenharmony_ci};
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_cistatic int
37962306a36Sopenharmony_cisuborder_not_supported(struct dasd_ccw_req *cqr)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	char *sense;
38262306a36Sopenharmony_ci	char reason;
38362306a36Sopenharmony_ci	char msg_format;
38462306a36Sopenharmony_ci	char msg_no;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	/*
38762306a36Sopenharmony_ci	 * intrc values ENODEV, ENOLINK and EPERM
38862306a36Sopenharmony_ci	 * will be optained from sleep_on to indicate that no
38962306a36Sopenharmony_ci	 * IO operation can be started
39062306a36Sopenharmony_ci	 */
39162306a36Sopenharmony_ci	if (cqr->intrc == -ENODEV)
39262306a36Sopenharmony_ci		return 1;
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	if (cqr->intrc == -ENOLINK)
39562306a36Sopenharmony_ci		return 1;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	if (cqr->intrc == -EPERM)
39862306a36Sopenharmony_ci		return 1;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	sense = dasd_get_sense(&cqr->irb);
40162306a36Sopenharmony_ci	if (!sense)
40262306a36Sopenharmony_ci		return 0;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	reason = sense[0];
40562306a36Sopenharmony_ci	msg_format = (sense[7] & 0xF0);
40662306a36Sopenharmony_ci	msg_no = (sense[7] & 0x0F);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	/* command reject, Format 0 MSG 4 - invalid parameter */
40962306a36Sopenharmony_ci	if ((reason == 0x80) && (msg_format == 0x00) && (msg_no == 0x04))
41062306a36Sopenharmony_ci		return 1;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	return 0;
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic int read_unit_address_configuration(struct dasd_device *device,
41662306a36Sopenharmony_ci					   struct alias_lcu *lcu)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	struct dasd_psf_prssd_data *prssdp;
41962306a36Sopenharmony_ci	struct dasd_ccw_req *cqr;
42062306a36Sopenharmony_ci	struct ccw1 *ccw;
42162306a36Sopenharmony_ci	int rc;
42262306a36Sopenharmony_ci	unsigned long flags;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */	+ 1 /* RSSD */,
42562306a36Sopenharmony_ci				   (sizeof(struct dasd_psf_prssd_data)),
42662306a36Sopenharmony_ci				   device, NULL);
42762306a36Sopenharmony_ci	if (IS_ERR(cqr))
42862306a36Sopenharmony_ci		return PTR_ERR(cqr);
42962306a36Sopenharmony_ci	cqr->startdev = device;
43062306a36Sopenharmony_ci	cqr->memdev = device;
43162306a36Sopenharmony_ci	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
43262306a36Sopenharmony_ci	cqr->retries = 10;
43362306a36Sopenharmony_ci	cqr->expires = 20 * HZ;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	/* Prepare for Read Subsystem Data */
43662306a36Sopenharmony_ci	prssdp = (struct dasd_psf_prssd_data *) cqr->data;
43762306a36Sopenharmony_ci	memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data));
43862306a36Sopenharmony_ci	prssdp->order = PSF_ORDER_PRSSD;
43962306a36Sopenharmony_ci	prssdp->suborder = 0x0e;	/* Read unit address configuration */
44062306a36Sopenharmony_ci	/* all other bytes of prssdp must be zero */
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	ccw = cqr->cpaddr;
44362306a36Sopenharmony_ci	ccw->cmd_code = DASD_ECKD_CCW_PSF;
44462306a36Sopenharmony_ci	ccw->count = sizeof(struct dasd_psf_prssd_data);
44562306a36Sopenharmony_ci	ccw->flags |= CCW_FLAG_CC;
44662306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(prssdp);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	/* Read Subsystem Data - feature codes */
44962306a36Sopenharmony_ci	memset(lcu->uac, 0, sizeof(*(lcu->uac)));
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	ccw++;
45262306a36Sopenharmony_ci	ccw->cmd_code = DASD_ECKD_CCW_RSSD;
45362306a36Sopenharmony_ci	ccw->count = sizeof(*(lcu->uac));
45462306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(lcu->uac);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	cqr->buildclk = get_tod_clock();
45762306a36Sopenharmony_ci	cqr->status = DASD_CQR_FILLED;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	/* need to unset flag here to detect race with summary unit check */
46062306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
46162306a36Sopenharmony_ci	lcu->flags &= ~NEED_UAC_UPDATE;
46262306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	rc = dasd_sleep_on(cqr);
46562306a36Sopenharmony_ci	if (!rc)
46662306a36Sopenharmony_ci		goto out;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	if (suborder_not_supported(cqr)) {
46962306a36Sopenharmony_ci		/* suborder not supported or device unusable for IO */
47062306a36Sopenharmony_ci		rc = -EOPNOTSUPP;
47162306a36Sopenharmony_ci	} else {
47262306a36Sopenharmony_ci		/* IO failed but should be retried */
47362306a36Sopenharmony_ci		spin_lock_irqsave(&lcu->lock, flags);
47462306a36Sopenharmony_ci		lcu->flags |= NEED_UAC_UPDATE;
47562306a36Sopenharmony_ci		spin_unlock_irqrestore(&lcu->lock, flags);
47662306a36Sopenharmony_ci	}
47762306a36Sopenharmony_ciout:
47862306a36Sopenharmony_ci	dasd_sfree_request(cqr, cqr->memdev);
47962306a36Sopenharmony_ci	return rc;
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic int _lcu_update(struct dasd_device *refdev, struct alias_lcu *lcu)
48362306a36Sopenharmony_ci{
48462306a36Sopenharmony_ci	unsigned long flags;
48562306a36Sopenharmony_ci	struct alias_pav_group *pavgroup, *tempgroup;
48662306a36Sopenharmony_ci	struct dasd_device *device, *tempdev;
48762306a36Sopenharmony_ci	int i, rc;
48862306a36Sopenharmony_ci	struct dasd_eckd_private *private;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
49162306a36Sopenharmony_ci	list_for_each_entry_safe(pavgroup, tempgroup, &lcu->grouplist, group) {
49262306a36Sopenharmony_ci		list_for_each_entry_safe(device, tempdev, &pavgroup->baselist,
49362306a36Sopenharmony_ci					 alias_list) {
49462306a36Sopenharmony_ci			list_move(&device->alias_list, &lcu->active_devices);
49562306a36Sopenharmony_ci			private = device->private;
49662306a36Sopenharmony_ci			private->pavgroup = NULL;
49762306a36Sopenharmony_ci		}
49862306a36Sopenharmony_ci		list_for_each_entry_safe(device, tempdev, &pavgroup->aliaslist,
49962306a36Sopenharmony_ci					 alias_list) {
50062306a36Sopenharmony_ci			list_move(&device->alias_list, &lcu->active_devices);
50162306a36Sopenharmony_ci			private = device->private;
50262306a36Sopenharmony_ci			private->pavgroup = NULL;
50362306a36Sopenharmony_ci		}
50462306a36Sopenharmony_ci		list_del(&pavgroup->group);
50562306a36Sopenharmony_ci		kfree(pavgroup);
50662306a36Sopenharmony_ci	}
50762306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	rc = read_unit_address_configuration(refdev, lcu);
51062306a36Sopenharmony_ci	if (rc)
51162306a36Sopenharmony_ci		return rc;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
51462306a36Sopenharmony_ci	/*
51562306a36Sopenharmony_ci	 * there is another update needed skip the remaining handling
51662306a36Sopenharmony_ci	 * the data might already be outdated
51762306a36Sopenharmony_ci	 * but especially do not add the device to an LCU with pending
51862306a36Sopenharmony_ci	 * update
51962306a36Sopenharmony_ci	 */
52062306a36Sopenharmony_ci	if (lcu->flags & NEED_UAC_UPDATE)
52162306a36Sopenharmony_ci		goto out;
52262306a36Sopenharmony_ci	lcu->pav = NO_PAV;
52362306a36Sopenharmony_ci	for (i = 0; i < MAX_DEVICES_PER_LCU; ++i) {
52462306a36Sopenharmony_ci		switch (lcu->uac->unit[i].ua_type) {
52562306a36Sopenharmony_ci		case UA_BASE_PAV_ALIAS:
52662306a36Sopenharmony_ci			lcu->pav = BASE_PAV;
52762306a36Sopenharmony_ci			break;
52862306a36Sopenharmony_ci		case UA_HYPER_PAV_ALIAS:
52962306a36Sopenharmony_ci			lcu->pav = HYPER_PAV;
53062306a36Sopenharmony_ci			break;
53162306a36Sopenharmony_ci		}
53262306a36Sopenharmony_ci		if (lcu->pav != NO_PAV)
53362306a36Sopenharmony_ci			break;
53462306a36Sopenharmony_ci	}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	list_for_each_entry_safe(device, tempdev, &lcu->active_devices,
53762306a36Sopenharmony_ci				 alias_list) {
53862306a36Sopenharmony_ci		_add_device_to_lcu(lcu, device, refdev);
53962306a36Sopenharmony_ci	}
54062306a36Sopenharmony_ciout:
54162306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
54262306a36Sopenharmony_ci	return 0;
54362306a36Sopenharmony_ci}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_cistatic void lcu_update_work(struct work_struct *work)
54662306a36Sopenharmony_ci{
54762306a36Sopenharmony_ci	struct alias_lcu *lcu;
54862306a36Sopenharmony_ci	struct read_uac_work_data *ruac_data;
54962306a36Sopenharmony_ci	struct dasd_device *device;
55062306a36Sopenharmony_ci	unsigned long flags;
55162306a36Sopenharmony_ci	int rc;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	ruac_data = container_of(work, struct read_uac_work_data, dwork.work);
55462306a36Sopenharmony_ci	lcu = container_of(ruac_data, struct alias_lcu, ruac_data);
55562306a36Sopenharmony_ci	device = ruac_data->device;
55662306a36Sopenharmony_ci	rc = _lcu_update(device, lcu);
55762306a36Sopenharmony_ci	/*
55862306a36Sopenharmony_ci	 * Need to check flags again, as there could have been another
55962306a36Sopenharmony_ci	 * prepare_update or a new device a new device while we were still
56062306a36Sopenharmony_ci	 * processing the data
56162306a36Sopenharmony_ci	 */
56262306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
56362306a36Sopenharmony_ci	if ((rc && (rc != -EOPNOTSUPP)) || (lcu->flags & NEED_UAC_UPDATE)) {
56462306a36Sopenharmony_ci		DBF_DEV_EVENT(DBF_WARNING, device, "could not update"
56562306a36Sopenharmony_ci			    " alias data in lcu (rc = %d), retry later", rc);
56662306a36Sopenharmony_ci		if (!schedule_delayed_work(&lcu->ruac_data.dwork, 30*HZ))
56762306a36Sopenharmony_ci			dasd_put_device(device);
56862306a36Sopenharmony_ci	} else {
56962306a36Sopenharmony_ci		dasd_put_device(device);
57062306a36Sopenharmony_ci		lcu->ruac_data.device = NULL;
57162306a36Sopenharmony_ci		lcu->flags &= ~UPDATE_PENDING;
57262306a36Sopenharmony_ci	}
57362306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
57462306a36Sopenharmony_ci}
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_cistatic int _schedule_lcu_update(struct alias_lcu *lcu,
57762306a36Sopenharmony_ci				struct dasd_device *device)
57862306a36Sopenharmony_ci{
57962306a36Sopenharmony_ci	struct dasd_device *usedev = NULL;
58062306a36Sopenharmony_ci	struct alias_pav_group *group;
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	lcu->flags |= NEED_UAC_UPDATE;
58362306a36Sopenharmony_ci	if (lcu->ruac_data.device) {
58462306a36Sopenharmony_ci		/* already scheduled or running */
58562306a36Sopenharmony_ci		return 0;
58662306a36Sopenharmony_ci	}
58762306a36Sopenharmony_ci	if (device && !list_empty(&device->alias_list))
58862306a36Sopenharmony_ci		usedev = device;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	if (!usedev && !list_empty(&lcu->grouplist)) {
59162306a36Sopenharmony_ci		group = list_first_entry(&lcu->grouplist,
59262306a36Sopenharmony_ci					 struct alias_pav_group, group);
59362306a36Sopenharmony_ci		if (!list_empty(&group->baselist))
59462306a36Sopenharmony_ci			usedev = list_first_entry(&group->baselist,
59562306a36Sopenharmony_ci						  struct dasd_device,
59662306a36Sopenharmony_ci						  alias_list);
59762306a36Sopenharmony_ci		else if (!list_empty(&group->aliaslist))
59862306a36Sopenharmony_ci			usedev = list_first_entry(&group->aliaslist,
59962306a36Sopenharmony_ci						  struct dasd_device,
60062306a36Sopenharmony_ci						  alias_list);
60162306a36Sopenharmony_ci	}
60262306a36Sopenharmony_ci	if (!usedev && !list_empty(&lcu->active_devices)) {
60362306a36Sopenharmony_ci		usedev = list_first_entry(&lcu->active_devices,
60462306a36Sopenharmony_ci					  struct dasd_device, alias_list);
60562306a36Sopenharmony_ci	}
60662306a36Sopenharmony_ci	/*
60762306a36Sopenharmony_ci	 * if we haven't found a proper device yet, give up for now, the next
60862306a36Sopenharmony_ci	 * device that will be set active will trigger an lcu update
60962306a36Sopenharmony_ci	 */
61062306a36Sopenharmony_ci	if (!usedev)
61162306a36Sopenharmony_ci		return -EINVAL;
61262306a36Sopenharmony_ci	dasd_get_device(usedev);
61362306a36Sopenharmony_ci	lcu->ruac_data.device = usedev;
61462306a36Sopenharmony_ci	if (!schedule_delayed_work(&lcu->ruac_data.dwork, 0))
61562306a36Sopenharmony_ci		dasd_put_device(usedev);
61662306a36Sopenharmony_ci	return 0;
61762306a36Sopenharmony_ci}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ciint dasd_alias_add_device(struct dasd_device *device)
62062306a36Sopenharmony_ci{
62162306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
62262306a36Sopenharmony_ci	__u8 uaddr = private->uid.real_unit_addr;
62362306a36Sopenharmony_ci	struct alias_lcu *lcu = private->lcu;
62462306a36Sopenharmony_ci	unsigned long flags;
62562306a36Sopenharmony_ci	int rc;
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	rc = 0;
62862306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
62962306a36Sopenharmony_ci	/*
63062306a36Sopenharmony_ci	 * Check if device and lcu type differ. If so, the uac data may be
63162306a36Sopenharmony_ci	 * outdated and needs to be updated.
63262306a36Sopenharmony_ci	 */
63362306a36Sopenharmony_ci	if (private->uid.type !=  lcu->uac->unit[uaddr].ua_type) {
63462306a36Sopenharmony_ci		lcu->flags |= UPDATE_PENDING;
63562306a36Sopenharmony_ci		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
63662306a36Sopenharmony_ci			      "uid type mismatch - trigger rescan");
63762306a36Sopenharmony_ci	}
63862306a36Sopenharmony_ci	if (!(lcu->flags & UPDATE_PENDING)) {
63962306a36Sopenharmony_ci		rc = _add_device_to_lcu(lcu, device, device);
64062306a36Sopenharmony_ci		if (rc)
64162306a36Sopenharmony_ci			lcu->flags |= UPDATE_PENDING;
64262306a36Sopenharmony_ci	}
64362306a36Sopenharmony_ci	if (lcu->flags & UPDATE_PENDING) {
64462306a36Sopenharmony_ci		list_move(&device->alias_list, &lcu->active_devices);
64562306a36Sopenharmony_ci		private->pavgroup = NULL;
64662306a36Sopenharmony_ci		_schedule_lcu_update(lcu, device);
64762306a36Sopenharmony_ci	}
64862306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
64962306a36Sopenharmony_ci	return rc;
65062306a36Sopenharmony_ci}
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ciint dasd_alias_update_add_device(struct dasd_device *device)
65362306a36Sopenharmony_ci{
65462306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	private->lcu->flags |= UPDATE_PENDING;
65762306a36Sopenharmony_ci	return dasd_alias_add_device(device);
65862306a36Sopenharmony_ci}
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ciint dasd_alias_remove_device(struct dasd_device *device)
66162306a36Sopenharmony_ci{
66262306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
66362306a36Sopenharmony_ci	struct alias_lcu *lcu = private->lcu;
66462306a36Sopenharmony_ci	unsigned long flags;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	/* nothing to do if already removed */
66762306a36Sopenharmony_ci	if (!lcu)
66862306a36Sopenharmony_ci		return 0;
66962306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
67062306a36Sopenharmony_ci	_remove_device_from_lcu(lcu, device);
67162306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
67262306a36Sopenharmony_ci	return 0;
67362306a36Sopenharmony_ci}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_cistruct dasd_device *dasd_alias_get_start_dev(struct dasd_device *base_device)
67662306a36Sopenharmony_ci{
67762306a36Sopenharmony_ci	struct dasd_eckd_private *alias_priv, *private = base_device->private;
67862306a36Sopenharmony_ci	struct alias_lcu *lcu = private->lcu;
67962306a36Sopenharmony_ci	struct dasd_device *alias_device;
68062306a36Sopenharmony_ci	struct alias_pav_group *group;
68162306a36Sopenharmony_ci	unsigned long flags;
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_ci	if (!lcu)
68462306a36Sopenharmony_ci		return NULL;
68562306a36Sopenharmony_ci	if (lcu->pav == NO_PAV ||
68662306a36Sopenharmony_ci	    lcu->flags & (NEED_UAC_UPDATE | UPDATE_PENDING))
68762306a36Sopenharmony_ci		return NULL;
68862306a36Sopenharmony_ci	if (unlikely(!(private->features.feature[8] & 0x01))) {
68962306a36Sopenharmony_ci		/*
69062306a36Sopenharmony_ci		 * PAV enabled but prefix not, very unlikely
69162306a36Sopenharmony_ci		 * seems to be a lost pathgroup
69262306a36Sopenharmony_ci		 * use base device to do IO
69362306a36Sopenharmony_ci		 */
69462306a36Sopenharmony_ci		DBF_DEV_EVENT(DBF_ERR, base_device, "%s",
69562306a36Sopenharmony_ci			      "Prefix not enabled with PAV enabled\n");
69662306a36Sopenharmony_ci		return NULL;
69762306a36Sopenharmony_ci	}
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
70062306a36Sopenharmony_ci	group = private->pavgroup;
70162306a36Sopenharmony_ci	if (!group) {
70262306a36Sopenharmony_ci		spin_unlock_irqrestore(&lcu->lock, flags);
70362306a36Sopenharmony_ci		return NULL;
70462306a36Sopenharmony_ci	}
70562306a36Sopenharmony_ci	alias_device = group->next;
70662306a36Sopenharmony_ci	if (!alias_device) {
70762306a36Sopenharmony_ci		if (list_empty(&group->aliaslist)) {
70862306a36Sopenharmony_ci			spin_unlock_irqrestore(&lcu->lock, flags);
70962306a36Sopenharmony_ci			return NULL;
71062306a36Sopenharmony_ci		} else {
71162306a36Sopenharmony_ci			alias_device = list_first_entry(&group->aliaslist,
71262306a36Sopenharmony_ci							struct dasd_device,
71362306a36Sopenharmony_ci							alias_list);
71462306a36Sopenharmony_ci		}
71562306a36Sopenharmony_ci	}
71662306a36Sopenharmony_ci	if (list_is_last(&alias_device->alias_list, &group->aliaslist))
71762306a36Sopenharmony_ci		group->next = list_first_entry(&group->aliaslist,
71862306a36Sopenharmony_ci					       struct dasd_device, alias_list);
71962306a36Sopenharmony_ci	else
72062306a36Sopenharmony_ci		group->next = list_first_entry(&alias_device->alias_list,
72162306a36Sopenharmony_ci					       struct dasd_device, alias_list);
72262306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
72362306a36Sopenharmony_ci	alias_priv = alias_device->private;
72462306a36Sopenharmony_ci	if ((alias_priv->count < private->count) && !alias_device->stopped &&
72562306a36Sopenharmony_ci	    !test_bit(DASD_FLAG_OFFLINE, &alias_device->flags))
72662306a36Sopenharmony_ci		return alias_device;
72762306a36Sopenharmony_ci	else
72862306a36Sopenharmony_ci		return NULL;
72962306a36Sopenharmony_ci}
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci/*
73262306a36Sopenharmony_ci * Summary unit check handling depends on the way alias devices
73362306a36Sopenharmony_ci * are handled so it is done here rather then in dasd_eckd.c
73462306a36Sopenharmony_ci */
73562306a36Sopenharmony_cistatic int reset_summary_unit_check(struct alias_lcu *lcu,
73662306a36Sopenharmony_ci				    struct dasd_device *device,
73762306a36Sopenharmony_ci				    char reason)
73862306a36Sopenharmony_ci{
73962306a36Sopenharmony_ci	struct dasd_ccw_req *cqr;
74062306a36Sopenharmony_ci	int rc = 0;
74162306a36Sopenharmony_ci	struct ccw1 *ccw;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	cqr = lcu->rsu_cqr;
74462306a36Sopenharmony_ci	memcpy((char *) &cqr->magic, "ECKD", 4);
74562306a36Sopenharmony_ci	ASCEBC((char *) &cqr->magic, 4);
74662306a36Sopenharmony_ci	ccw = cqr->cpaddr;
74762306a36Sopenharmony_ci	ccw->cmd_code = DASD_ECKD_CCW_RSCK;
74862306a36Sopenharmony_ci	ccw->flags = CCW_FLAG_SLI;
74962306a36Sopenharmony_ci	ccw->count = 16;
75062306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(cqr->data);
75162306a36Sopenharmony_ci	((char *)cqr->data)[0] = reason;
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
75462306a36Sopenharmony_ci	cqr->retries = 255;	/* set retry counter to enable basic ERP */
75562306a36Sopenharmony_ci	cqr->startdev = device;
75662306a36Sopenharmony_ci	cqr->memdev = device;
75762306a36Sopenharmony_ci	cqr->block = NULL;
75862306a36Sopenharmony_ci	cqr->expires = 5 * HZ;
75962306a36Sopenharmony_ci	cqr->buildclk = get_tod_clock();
76062306a36Sopenharmony_ci	cqr->status = DASD_CQR_FILLED;
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	rc = dasd_sleep_on_immediatly(cqr);
76362306a36Sopenharmony_ci	return rc;
76462306a36Sopenharmony_ci}
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_cistatic void _restart_all_base_devices_on_lcu(struct alias_lcu *lcu)
76762306a36Sopenharmony_ci{
76862306a36Sopenharmony_ci	struct alias_pav_group *pavgroup;
76962306a36Sopenharmony_ci	struct dasd_device *device;
77062306a36Sopenharmony_ci	struct dasd_eckd_private *private;
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	/* active and inactive list can contain alias as well as base devices */
77362306a36Sopenharmony_ci	list_for_each_entry(device, &lcu->active_devices, alias_list) {
77462306a36Sopenharmony_ci		private = device->private;
77562306a36Sopenharmony_ci		if (private->uid.type != UA_BASE_DEVICE)
77662306a36Sopenharmony_ci			continue;
77762306a36Sopenharmony_ci		dasd_schedule_block_bh(device->block);
77862306a36Sopenharmony_ci		dasd_schedule_device_bh(device);
77962306a36Sopenharmony_ci	}
78062306a36Sopenharmony_ci	list_for_each_entry(device, &lcu->inactive_devices, alias_list) {
78162306a36Sopenharmony_ci		private = device->private;
78262306a36Sopenharmony_ci		if (private->uid.type != UA_BASE_DEVICE)
78362306a36Sopenharmony_ci			continue;
78462306a36Sopenharmony_ci		dasd_schedule_block_bh(device->block);
78562306a36Sopenharmony_ci		dasd_schedule_device_bh(device);
78662306a36Sopenharmony_ci	}
78762306a36Sopenharmony_ci	list_for_each_entry(pavgroup, &lcu->grouplist, group) {
78862306a36Sopenharmony_ci		list_for_each_entry(device, &pavgroup->baselist, alias_list) {
78962306a36Sopenharmony_ci			dasd_schedule_block_bh(device->block);
79062306a36Sopenharmony_ci			dasd_schedule_device_bh(device);
79162306a36Sopenharmony_ci		}
79262306a36Sopenharmony_ci	}
79362306a36Sopenharmony_ci}
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_cistatic void flush_all_alias_devices_on_lcu(struct alias_lcu *lcu)
79662306a36Sopenharmony_ci{
79762306a36Sopenharmony_ci	struct alias_pav_group *pavgroup;
79862306a36Sopenharmony_ci	struct dasd_device *device, *temp;
79962306a36Sopenharmony_ci	struct dasd_eckd_private *private;
80062306a36Sopenharmony_ci	unsigned long flags;
80162306a36Sopenharmony_ci	LIST_HEAD(active);
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci	/*
80462306a36Sopenharmony_ci	 * Problem here ist that dasd_flush_device_queue may wait
80562306a36Sopenharmony_ci	 * for termination of a request to complete. We can't keep
80662306a36Sopenharmony_ci	 * the lcu lock during that time, so we must assume that
80762306a36Sopenharmony_ci	 * the lists may have changed.
80862306a36Sopenharmony_ci	 * Idea: first gather all active alias devices in a separate list,
80962306a36Sopenharmony_ci	 * then flush the first element of this list unlocked, and afterwards
81062306a36Sopenharmony_ci	 * check if it is still on the list before moving it to the
81162306a36Sopenharmony_ci	 * active_devices list.
81262306a36Sopenharmony_ci	 */
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
81562306a36Sopenharmony_ci	list_for_each_entry_safe(device, temp, &lcu->active_devices,
81662306a36Sopenharmony_ci				 alias_list) {
81762306a36Sopenharmony_ci		private = device->private;
81862306a36Sopenharmony_ci		if (private->uid.type == UA_BASE_DEVICE)
81962306a36Sopenharmony_ci			continue;
82062306a36Sopenharmony_ci		list_move(&device->alias_list, &active);
82162306a36Sopenharmony_ci	}
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	list_for_each_entry(pavgroup, &lcu->grouplist, group) {
82462306a36Sopenharmony_ci		list_splice_init(&pavgroup->aliaslist, &active);
82562306a36Sopenharmony_ci	}
82662306a36Sopenharmony_ci	while (!list_empty(&active)) {
82762306a36Sopenharmony_ci		device = list_first_entry(&active, struct dasd_device,
82862306a36Sopenharmony_ci					  alias_list);
82962306a36Sopenharmony_ci		spin_unlock_irqrestore(&lcu->lock, flags);
83062306a36Sopenharmony_ci		dasd_flush_device_queue(device);
83162306a36Sopenharmony_ci		spin_lock_irqsave(&lcu->lock, flags);
83262306a36Sopenharmony_ci		/*
83362306a36Sopenharmony_ci		 * only move device around if it wasn't moved away while we
83462306a36Sopenharmony_ci		 * were waiting for the flush
83562306a36Sopenharmony_ci		 */
83662306a36Sopenharmony_ci		if (device == list_first_entry(&active,
83762306a36Sopenharmony_ci					       struct dasd_device, alias_list)) {
83862306a36Sopenharmony_ci			list_move(&device->alias_list, &lcu->active_devices);
83962306a36Sopenharmony_ci			private = device->private;
84062306a36Sopenharmony_ci			private->pavgroup = NULL;
84162306a36Sopenharmony_ci		}
84262306a36Sopenharmony_ci	}
84362306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
84462306a36Sopenharmony_ci}
84562306a36Sopenharmony_ci
84662306a36Sopenharmony_cistatic void _stop_all_devices_on_lcu(struct alias_lcu *lcu)
84762306a36Sopenharmony_ci{
84862306a36Sopenharmony_ci	struct alias_pav_group *pavgroup;
84962306a36Sopenharmony_ci	struct dasd_device *device;
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	list_for_each_entry(device, &lcu->active_devices, alias_list) {
85262306a36Sopenharmony_ci		spin_lock(get_ccwdev_lock(device->cdev));
85362306a36Sopenharmony_ci		dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
85462306a36Sopenharmony_ci		spin_unlock(get_ccwdev_lock(device->cdev));
85562306a36Sopenharmony_ci	}
85662306a36Sopenharmony_ci	list_for_each_entry(device, &lcu->inactive_devices, alias_list) {
85762306a36Sopenharmony_ci		spin_lock(get_ccwdev_lock(device->cdev));
85862306a36Sopenharmony_ci		dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
85962306a36Sopenharmony_ci		spin_unlock(get_ccwdev_lock(device->cdev));
86062306a36Sopenharmony_ci	}
86162306a36Sopenharmony_ci	list_for_each_entry(pavgroup, &lcu->grouplist, group) {
86262306a36Sopenharmony_ci		list_for_each_entry(device, &pavgroup->baselist, alias_list) {
86362306a36Sopenharmony_ci			spin_lock(get_ccwdev_lock(device->cdev));
86462306a36Sopenharmony_ci			dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
86562306a36Sopenharmony_ci			spin_unlock(get_ccwdev_lock(device->cdev));
86662306a36Sopenharmony_ci		}
86762306a36Sopenharmony_ci		list_for_each_entry(device, &pavgroup->aliaslist, alias_list) {
86862306a36Sopenharmony_ci			spin_lock(get_ccwdev_lock(device->cdev));
86962306a36Sopenharmony_ci			dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
87062306a36Sopenharmony_ci			spin_unlock(get_ccwdev_lock(device->cdev));
87162306a36Sopenharmony_ci		}
87262306a36Sopenharmony_ci	}
87362306a36Sopenharmony_ci}
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_cistatic void _unstop_all_devices_on_lcu(struct alias_lcu *lcu)
87662306a36Sopenharmony_ci{
87762306a36Sopenharmony_ci	struct alias_pav_group *pavgroup;
87862306a36Sopenharmony_ci	struct dasd_device *device;
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci	list_for_each_entry(device, &lcu->active_devices, alias_list) {
88162306a36Sopenharmony_ci		spin_lock(get_ccwdev_lock(device->cdev));
88262306a36Sopenharmony_ci		dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
88362306a36Sopenharmony_ci		spin_unlock(get_ccwdev_lock(device->cdev));
88462306a36Sopenharmony_ci	}
88562306a36Sopenharmony_ci	list_for_each_entry(device, &lcu->inactive_devices, alias_list) {
88662306a36Sopenharmony_ci		spin_lock(get_ccwdev_lock(device->cdev));
88762306a36Sopenharmony_ci		dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
88862306a36Sopenharmony_ci		spin_unlock(get_ccwdev_lock(device->cdev));
88962306a36Sopenharmony_ci	}
89062306a36Sopenharmony_ci	list_for_each_entry(pavgroup, &lcu->grouplist, group) {
89162306a36Sopenharmony_ci		list_for_each_entry(device, &pavgroup->baselist, alias_list) {
89262306a36Sopenharmony_ci			spin_lock(get_ccwdev_lock(device->cdev));
89362306a36Sopenharmony_ci			dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
89462306a36Sopenharmony_ci			spin_unlock(get_ccwdev_lock(device->cdev));
89562306a36Sopenharmony_ci		}
89662306a36Sopenharmony_ci		list_for_each_entry(device, &pavgroup->aliaslist, alias_list) {
89762306a36Sopenharmony_ci			spin_lock(get_ccwdev_lock(device->cdev));
89862306a36Sopenharmony_ci			dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
89962306a36Sopenharmony_ci			spin_unlock(get_ccwdev_lock(device->cdev));
90062306a36Sopenharmony_ci		}
90162306a36Sopenharmony_ci	}
90262306a36Sopenharmony_ci}
90362306a36Sopenharmony_ci
90462306a36Sopenharmony_cistatic void summary_unit_check_handling_work(struct work_struct *work)
90562306a36Sopenharmony_ci{
90662306a36Sopenharmony_ci	struct alias_lcu *lcu;
90762306a36Sopenharmony_ci	struct summary_unit_check_work_data *suc_data;
90862306a36Sopenharmony_ci	unsigned long flags;
90962306a36Sopenharmony_ci	struct dasd_device *device;
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_ci	suc_data = container_of(work, struct summary_unit_check_work_data,
91262306a36Sopenharmony_ci				worker);
91362306a36Sopenharmony_ci	lcu = container_of(suc_data, struct alias_lcu, suc_data);
91462306a36Sopenharmony_ci	device = suc_data->device;
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	/* 1. flush alias devices */
91762306a36Sopenharmony_ci	flush_all_alias_devices_on_lcu(lcu);
91862306a36Sopenharmony_ci
91962306a36Sopenharmony_ci	/* 2. reset summary unit check */
92062306a36Sopenharmony_ci	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
92162306a36Sopenharmony_ci	dasd_device_remove_stop_bits(device,
92262306a36Sopenharmony_ci				     (DASD_STOPPED_SU | DASD_STOPPED_PENDING));
92362306a36Sopenharmony_ci	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
92462306a36Sopenharmony_ci	reset_summary_unit_check(lcu, device, suc_data->reason);
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
92762306a36Sopenharmony_ci	_unstop_all_devices_on_lcu(lcu);
92862306a36Sopenharmony_ci	_restart_all_base_devices_on_lcu(lcu);
92962306a36Sopenharmony_ci	/* 3. read new alias configuration */
93062306a36Sopenharmony_ci	_schedule_lcu_update(lcu, device);
93162306a36Sopenharmony_ci	lcu->suc_data.device = NULL;
93262306a36Sopenharmony_ci	dasd_put_device(device);
93362306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
93462306a36Sopenharmony_ci}
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_civoid dasd_alias_handle_summary_unit_check(struct work_struct *work)
93762306a36Sopenharmony_ci{
93862306a36Sopenharmony_ci	struct dasd_device *device = container_of(work, struct dasd_device,
93962306a36Sopenharmony_ci						  suc_work);
94062306a36Sopenharmony_ci	struct dasd_eckd_private *private = device->private;
94162306a36Sopenharmony_ci	struct alias_lcu *lcu;
94262306a36Sopenharmony_ci	unsigned long flags;
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci	lcu = private->lcu;
94562306a36Sopenharmony_ci	if (!lcu) {
94662306a36Sopenharmony_ci		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
94762306a36Sopenharmony_ci			    "device not ready to handle summary"
94862306a36Sopenharmony_ci			    " unit check (no lcu structure)");
94962306a36Sopenharmony_ci		goto out;
95062306a36Sopenharmony_ci	}
95162306a36Sopenharmony_ci	spin_lock_irqsave(&lcu->lock, flags);
95262306a36Sopenharmony_ci	/* If this device is about to be removed just return and wait for
95362306a36Sopenharmony_ci	 * the next interrupt on a different device
95462306a36Sopenharmony_ci	 */
95562306a36Sopenharmony_ci	if (list_empty(&device->alias_list)) {
95662306a36Sopenharmony_ci		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
95762306a36Sopenharmony_ci			    "device is in offline processing,"
95862306a36Sopenharmony_ci			    " don't do summary unit check handling");
95962306a36Sopenharmony_ci		goto out_unlock;
96062306a36Sopenharmony_ci	}
96162306a36Sopenharmony_ci	if (lcu->suc_data.device) {
96262306a36Sopenharmony_ci		/* already scheduled or running */
96362306a36Sopenharmony_ci		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
96462306a36Sopenharmony_ci			    "previous instance of summary unit check worker"
96562306a36Sopenharmony_ci			    " still pending");
96662306a36Sopenharmony_ci		goto out_unlock;
96762306a36Sopenharmony_ci	}
96862306a36Sopenharmony_ci	_stop_all_devices_on_lcu(lcu);
96962306a36Sopenharmony_ci	/* prepare for lcu_update */
97062306a36Sopenharmony_ci	lcu->flags |= NEED_UAC_UPDATE | UPDATE_PENDING;
97162306a36Sopenharmony_ci	lcu->suc_data.reason = private->suc_reason;
97262306a36Sopenharmony_ci	lcu->suc_data.device = device;
97362306a36Sopenharmony_ci	dasd_get_device(device);
97462306a36Sopenharmony_ci	if (!schedule_work(&lcu->suc_data.worker))
97562306a36Sopenharmony_ci		dasd_put_device(device);
97662306a36Sopenharmony_ciout_unlock:
97762306a36Sopenharmony_ci	spin_unlock_irqrestore(&lcu->lock, flags);
97862306a36Sopenharmony_ciout:
97962306a36Sopenharmony_ci	clear_bit(DASD_FLAG_SUC, &device->flags);
98062306a36Sopenharmony_ci	dasd_put_device(device);
98162306a36Sopenharmony_ci};
982