// SPDX-License-Identifier: GPL-2.0 /* * PAV alias management for the DASD ECKD discipline * * Copyright IBM Corp. 2007 * Author(s): Stefan Weinhuber <wein@de.ibm.com>
*/
/* * General concept of alias management: * - PAV and DASD alias management is specific to the eckd discipline. * - A device is connected to an lcu as long as the device exists. * dasd_alias_make_device_known_to_lcu will be called wenn the * device is checked by the eckd discipline and * dasd_alias_disconnect_device_from_lcu will be called * before the device is deleted. * - The dasd_alias_add_device / dasd_alias_remove_device * functions mark the point when a device is 'ready for service'. * - A summary unit check is a rare occasion, but it is mandatory to * support it. It requires some complex recovery actions before the * devices can be used again (see dasd_alias_handle_summary_unit_check). * - dasd_alias_get_start_dev will find an alias device that can be used * instead of the base device and does some (very simple) load balancing. * This is the function that gets called for each I/O, so when improving * something, this function should get faster or better, the rest has just * to be correct.
*/
/* for hyper pav there is only one group */ if (lcu->pav == HYPER_PAV) { if (list_empty(&lcu->grouplist)) return NULL; else return list_first_entry(&lcu->grouplist, struct alias_pav_group, group);
}
/* for base pav we have to find the group that matches the base */ if (uid->type == UA_BASE_DEVICE)
search_unit_addr = uid->real_unit_addr; else
search_unit_addr = uid->base_unit_addr;
list_for_each_entry(pos, &lcu->grouplist, group) { if (pos->uid.base_unit_addr == search_unit_addr &&
!strncmp(pos->uid.vduit, uid->vduit, sizeof(uid->vduit))) return pos;
} return NULL;
}
/* * This is the function that will allocate all the server and lcu data, * so this function must be called first for a new device. * If the return value is 1, the lcu was already known before, if it * is 0, this is a new lcu. * Negative return code indicates that something went wrong (e.g. -ENOMEM)
*/ int dasd_alias_make_device_known_to_lcu(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private; unsignedlong flags; struct alias_server *server, *newserver; struct alias_lcu *lcu, *newlcu; struct dasd_uid uid;
device->discipline->get_uid(device, &uid);
spin_lock_irqsave(&aliastree.lock, flags);
server = _find_server(&uid); if (!server) {
spin_unlock_irqrestore(&aliastree.lock, flags);
newserver = _allocate_server(&uid); if (IS_ERR(newserver)) return PTR_ERR(newserver);
spin_lock_irqsave(&aliastree.lock, flags);
server = _find_server(&uid); if (!server) {
list_add(&newserver->server, &aliastree.serverlist);
server = newserver;
} else { /* someone was faster */
_free_server(newserver);
}
}
/* * This function removes a device from the scope of alias management. * The complicated part is to make sure that it is not in use by * any of the workers. If necessary cancel the work.
*/ void dasd_alias_disconnect_device_from_lcu(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private; unsignedlong flags; struct alias_lcu *lcu; struct alias_server *server; int was_pending; struct dasd_uid uid;
lcu = private->lcu; /* nothing to do if already disconnected */ if (!lcu) return;
device->discipline->get_uid(device, &uid);
spin_lock_irqsave(&lcu->lock, flags); /* make sure that the workers don't use this device */ if (device == lcu->suc_data.device) {
spin_unlock_irqrestore(&lcu->lock, flags);
cancel_work_sync(&lcu->suc_data.worker);
spin_lock_irqsave(&lcu->lock, flags); if (device == lcu->suc_data.device) {
dasd_put_device(device);
lcu->suc_data.device = NULL;
}
}
was_pending = 0; if (device == lcu->ruac_data.device) {
spin_unlock_irqrestore(&lcu->lock, flags);
was_pending = 1;
cancel_delayed_work_sync(&lcu->ruac_data.dwork);
spin_lock_irqsave(&lcu->lock, flags); if (device == lcu->ruac_data.device) {
dasd_put_device(device);
lcu->ruac_data.device = NULL;
}
}
private->lcu = NULL;
spin_unlock_irqrestore(&lcu->lock, flags);
spin_lock_irqsave(&aliastree.lock, flags);
spin_lock(&lcu->lock);
list_del_init(&device->alias_list); if (list_empty(&lcu->grouplist) &&
list_empty(&lcu->active_devices) &&
list_empty(&lcu->inactive_devices)) {
list_del(&lcu->lcu);
spin_unlock(&lcu->lock);
_free_lcu(lcu);
lcu = NULL;
} else { if (was_pending)
_schedule_lcu_update(lcu, NULL);
spin_unlock(&lcu->lock);
}
server = _find_server(&uid); if (server && list_empty(&server->lculist)) {
list_del(&server->server);
_free_server(server);
}
spin_unlock_irqrestore(&aliastree.lock, flags);
}
/* * This function assumes that the unit address configuration stored * in the lcu is up to date and will update the device uid before * adding it to a pav group.
*/
/* * intrc values ENODEV, ENOLINK and EPERM * will be optained from sleep_on to indicate that no * IO operation can be started
*/ if (cqr->intrc == -ENODEV) return 1;
if (cqr->intrc == -ENOLINK) return 1;
if (cqr->intrc == -EPERM) return 1;
sense = dasd_get_sense(&cqr->irb); if (!sense) return 0;
/* Prepare for Read Subsystem Data */
prssdp = (struct dasd_psf_prssd_data *) cqr->data;
memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data));
prssdp->order = PSF_ORDER_PRSSD;
prssdp->suborder = 0x0e; /* Read unit address configuration */ /* all other bytes of prssdp must be zero */
/* need to unset flag here to detect race with summary unit check */
spin_lock_irqsave(&lcu->lock, flags);
lcu->flags &= ~NEED_UAC_UPDATE;
spin_unlock_irqrestore(&lcu->lock, flags);
rc = dasd_sleep_on(cqr); if (!rc) goto out;
if (suborder_not_supported(cqr)) { /* suborder not supported or device unusable for IO */
rc = -EOPNOTSUPP;
} else { /* IO failed but should be retried */
spin_lock_irqsave(&lcu->lock, flags);
lcu->flags |= NEED_UAC_UPDATE;
spin_unlock_irqrestore(&lcu->lock, flags);
}
out:
dasd_sfree_request(cqr, cqr->memdev); return rc;
}
rc = read_unit_address_configuration(refdev, lcu); if (rc) return rc;
spin_lock_irqsave(&lcu->lock, flags); /* * there is another update needed skip the remaining handling * the data might already be outdated * but especially do not add the device to an LCU with pending * update
*/ if (lcu->flags & NEED_UAC_UPDATE) goto out;
lcu->pav = NO_PAV; for (i = 0; i < MAX_DEVICES_PER_LCU; ++i) { switch (lcu->uac->unit[i].ua_type) { case UA_BASE_PAV_ALIAS:
lcu->pav = BASE_PAV; break; case UA_HYPER_PAV_ALIAS:
lcu->pav = HYPER_PAV; break;
} if (lcu->pav != NO_PAV) break;
}
ruac_data = container_of(work, struct read_uac_work_data, dwork.work);
lcu = container_of(ruac_data, struct alias_lcu, ruac_data);
device = ruac_data->device;
rc = _lcu_update(device, lcu); /* * Need to check flags again, as there could have been another * prepare_update or a new device a new device while we were still * processing the data
*/
spin_lock_irqsave(&lcu->lock, flags); if ((rc && (rc != -EOPNOTSUPP)) || (lcu->flags & NEED_UAC_UPDATE)) {
DBF_DEV_EVENT(DBF_WARNING, device, "could not update" " alias data in lcu (rc = %d), retry later", rc); if (!schedule_delayed_work(&lcu->ruac_data.dwork, 30*HZ))
dasd_put_device(device);
} else {
dasd_put_device(device);
lcu->ruac_data.device = NULL;
lcu->flags &= ~UPDATE_PENDING;
}
spin_unlock_irqrestore(&lcu->lock, flags);
}
lcu->flags |= NEED_UAC_UPDATE; if (lcu->ruac_data.device) { /* already scheduled or running */ return 0;
} if (device && !list_empty(&device->alias_list))
usedev = device;
if (!usedev && !list_empty(&lcu->grouplist)) {
group = list_first_entry(&lcu->grouplist, struct alias_pav_group, group); if (!list_empty(&group->baselist))
usedev = list_first_entry(&group->baselist, struct dasd_device,
alias_list); elseif (!list_empty(&group->aliaslist))
usedev = list_first_entry(&group->aliaslist, struct dasd_device,
alias_list);
} if (!usedev && !list_empty(&lcu->active_devices)) {
usedev = list_first_entry(&lcu->active_devices, struct dasd_device, alias_list);
} /* * if we haven't found a proper device yet, give up for now, the next * device that will be set active will trigger an lcu update
*/ if (!usedev) return -EINVAL;
dasd_get_device(usedev);
lcu->ruac_data.device = usedev; if (!schedule_delayed_work(&lcu->ruac_data.dwork, 0))
dasd_put_device(usedev); return 0;
}
rc = 0;
spin_lock_irqsave(&lcu->lock, flags); /* * Check if device and lcu type differ. If so, the uac data may be * outdated and needs to be updated.
*/ if (private->uid.type != lcu->uac->unit[uaddr].ua_type) {
lcu->flags |= UPDATE_PENDING;
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "uid type mismatch - trigger rescan");
} if (!(lcu->flags & UPDATE_PENDING)) {
rc = _add_device_to_lcu(lcu, device, device); if (rc)
lcu->flags |= UPDATE_PENDING;
} if (lcu->flags & UPDATE_PENDING) {
list_move(&device->alias_list, &lcu->active_devices);
private->pavgroup = NULL;
_schedule_lcu_update(lcu, device);
}
spin_unlock_irqrestore(&lcu->lock, flags); return rc;
}
int dasd_alias_update_add_device(struct dasd_device *device)
{ struct dasd_eckd_private *private = device->private;
if (!lcu) return NULL; if (lcu->pav == NO_PAV ||
lcu->flags & (NEED_UAC_UPDATE | UPDATE_PENDING)) return NULL; if (unlikely(!(private->features.feature[8] & 0x01))) { /* * PAV enabled but prefix not, very unlikely * seems to be a lost pathgroup * use base device to do IO
*/
DBF_DEV_EVENT(DBF_ERR, base_device, "%s", "Prefix not enabled with PAV enabled\n"); return NULL;
}
/* * Summary unit check handling depends on the way alias devices * are handled so it is done here rather then in dasd_eckd.c
*/ staticint reset_summary_unit_check(struct alias_lcu *lcu, struct dasd_device *device, char reason)
{ struct dasd_ccw_req *cqr; int rc = 0; struct ccw1 *ccw;
/* * Problem here ist that dasd_flush_device_queue may wait * for termination of a request to complete. We can't keep * the lcu lock during that time, so we must assume that * the lists may have changed. * Idea: first gather all active alias devices in a separate list, * then flush the first element of this list unlocked, and afterwards * check if it is still on the list before moving it to the * active_devices list.
*/
lcu = private->lcu; if (!lcu) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "device not ready to handle summary" " unit check (no lcu structure)"); goto out;
}
spin_lock_irqsave(&lcu->lock, flags); /* If this device is about to be removed just return and wait for * the next interrupt on a different device
*/ if (list_empty(&device->alias_list)) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "device is in offline processing," " don't do summary unit check handling"); goto out_unlock;
} if (lcu->suc_data.device) { /* already scheduled or running */
DBF_DEV_EVENT(DBF_WARNING, device, "%s", "previous instance of summary unit check worker" " still pending"); goto out_unlock;
}
_stop_all_devices_on_lcu(lcu); /* prepare for lcu_update */
lcu->flags |= NEED_UAC_UPDATE | UPDATE_PENDING;
lcu->suc_data.reason = private->suc_reason;
lcu->suc_data.device = device;
dasd_get_device(device); if (!schedule_work(&lcu->suc_data.worker))
dasd_put_device(device);
out_unlock:
spin_unlock_irqrestore(&lcu->lock, flags);
out:
clear_bit(DASD_FLAG_SUC, &device->flags);
dasd_put_device(device);
};
Messung V0.5
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.