// SPDX-License-Identifier: GPL-2.0 /* * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> * Horst Hummel <Horst.Hummel@de.ibm.com> * Carsten Otte <Cotte@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> * Bugreports.to..: <Linux390@de.ibm.com> * Copyright IBM Corp. 1999,2001 * * Device mapping and dasd= parameter parsing functions. All devmap * functions may not be called from interrupt context. In particular * dasd_get_device is a no-no from interrupt context. *
*/
/* * dasd_devmap_t is used to store the features and the relation * between device number and device index. To find a dasd_devmap_t * that corresponds to a device number of a device index each * dasd_devmap_t is added to two linked lists, one to search by * the device number and one to search by the device index. As * soon as big minor numbers are available the device index list * can be removed since the device number will then be identical * to the device index.
*/ struct dasd_devmap { struct list_head list; char bus_id[DASD_BUS_ID_SIZE]; unsignedint devindex; unsignedshort features; struct dasd_device *device; struct dasd_copy_relation *copy; unsignedint aq_mask;
};
int dasd_probeonly = 0; /* is true, when probeonly mode is active */ int dasd_autodetect = 0; /* is true, when autodetection is active */ int dasd_nopav = 0; /* is true, when PAV is disabled */
EXPORT_SYMBOL_GPL(dasd_nopav); int dasd_nofcx; /* disable High Performance Ficon */
EXPORT_SYMBOL_GPL(dasd_nofcx);
/* * char *dasd[] is intended to hold the ranges supplied by the dasd= statement * it is named 'dasd' to directly be filled by insmod with the comma separated * strings when running as a module.
*/ staticchar *dasd[DASD_MAX_PARAMS];
module_param_array(dasd, charp, NULL, S_IRUGO);
/* * Single spinlock to protect devmap and servermap structures and lists.
*/ static DEFINE_SPINLOCK(dasd_devmap_lock);
/* * Hash lists for devmap structures.
*/ staticstruct list_head dasd_hashlists[256]; int dasd_max_devindex;
/* * Read a device busid/devno from a string.
*/ staticint dasd_busid(char *str, int *id0, int *id1, int *devno)
{ unsignedint val; char *tok;
/* Interpret ipldev busid */ if (strncmp(DASD_IPLDEV, str, strlen(DASD_IPLDEV)) == 0) { if (ipl_info.type != IPL_TYPE_CCW) {
pr_err("The IPL device is not a CCW device\n"); return -EINVAL;
}
*id0 = 0;
*id1 = ipl_info.data.ccw.dev_id.ssid;
*devno = ipl_info.data.ccw.dev_id.devno;
return 0;
}
/* Old style 0xXXXX or XXXX */ if (!kstrtouint(str, 16, &val)) {
*id0 = *id1 = 0; if (val > 0xffff) return -EINVAL;
*devno = val; return 0;
}
/* New style x.y.z busid */
tok = strsep(&str, "."); if (kstrtouint(tok, 16, &val) || val > 0xff) return -EINVAL;
*id0 = val;
tok = strsep(&str, "."); if (kstrtouint(tok, 16, &val) || val > 0xff) return -EINVAL;
*id1 = val;
tok = strsep(&str, "."); if (kstrtouint(tok, 16, &val) || val > 0xffff) return -EINVAL;
*devno = val;
return 0;
}
/* * Read colon separated list of dasd features.
*/ staticint __init dasd_feature_list(char *str)
{ int features, len, rc;
features = 0;
rc = 0;
if (!str) return DASD_FEATURE_DEFAULT;
while (1) { for (len = 0;
str[len] && str[len] != ':' && str[len] != ')'; len++); if (len == 2 && !strncmp(str, "ro", 2))
features |= DASD_FEATURE_READONLY; elseif (len == 4 && !strncmp(str, "diag", 4))
features |= DASD_FEATURE_USEDIAG; elseif (len == 3 && !strncmp(str, "raw", 3))
features |= DASD_FEATURE_USERAW; elseif (len == 6 && !strncmp(str, "erplog", 6))
features |= DASD_FEATURE_ERPLOG; elseif (len == 8 && !strncmp(str, "failfast", 8))
features |= DASD_FEATURE_FAILFAST; else {
pr_warn("%.*s is not a supported device option\n",
len, str);
rc = -EINVAL;
}
str += len; if (*str != ':') break;
str++;
}
return rc ? : features;
}
/* * Try to match the first element on the comma separated parse string * with one of the known keywords. If a keyword is found, take the approprate * action and return a pointer to the residual string. If the first element * could not be matched to any keyword then return an error code.
*/ staticint __init dasd_parse_keyword(char *keyword)
{ int length = strlen(keyword);
if (strncmp("autodetect", keyword, length) == 0) {
dasd_autodetect = 1;
pr_info("The autodetection mode has been activated\n"); return 0;
} if (strncmp("probeonly", keyword, length) == 0) {
dasd_probeonly = 1;
pr_info("The probeonly mode has been activated\n"); return 0;
} if (strncmp("nopav", keyword, length) == 0) { if (machine_is_vm())
pr_info("'nopav' is not supported on z/VM\n"); else {
dasd_nopav = 1;
pr_info("PAV support has be deactivated\n");
} return 0;
} if (strncmp("nofcx", keyword, length) == 0) {
dasd_nofcx = 1;
pr_info("High Performance FICON support has been " "deactivated\n"); return 0;
} if (strncmp("fixedbuffers", keyword, length) == 0) { if (dasd_page_cache) return 0;
dasd_page_cache =
kmem_cache_create("dasd_page_cache", PAGE_SIZE,
PAGE_SIZE, SLAB_CACHE_DMA,
NULL); if (!dasd_page_cache)
DBF_EVENT(DBF_WARNING, "%s", "Failed to create slab, " "fixed buffer mode disabled."); else
DBF_EVENT(DBF_INFO, "%s", "turning on fixed buffer mode"); return 0;
}
return -EINVAL;
}
/* * Split a string of a device range into its pieces and return the from, to, and * feature parts separately. * e.g.: * 0.0.1234-0.0.5678(ro:erplog) -> from: 0.0.1234 to: 0.0.5678 features: ro:erplog * 0.0.8765(raw) -> from: 0.0.8765 to: null features: raw * 0x4321 -> from: 0x4321 to: null features: null
*/ staticint __init dasd_evaluate_range_param(char *range, char **from_str, char **to_str, char **features_str)
{ int rc = 0;
/* Do we have a range or a single device? */ if (strchr(range, '-')) {
*from_str = strsep(&range, "-");
*to_str = strsep(&range, "(");
*features_str = strsep(&range, ")");
} else {
*from_str = strsep(&range, "(");
*features_str = strsep(&range, ")");
}
if (*features_str && !range) {
pr_warn("A closing parenthesis ')' is missing in the dasd= parameter\n");
rc = -EINVAL;
}
return rc;
}
/* * Try to interprete the range string as a device number or a range of devices. * If the interpretation is successful, create the matching dasd_devmap entries. * If interpretation fails or in case of an error, return an error code.
*/ staticint __init dasd_parse_range(constchar *range)
{ struct dasd_devmap *devmap; int from, from_id0, from_id1; int to, to_id0, to_id1; int features; char bus_id[DASD_BUS_ID_SIZE + 1]; char *features_str = NULL; char *from_str = NULL; char *to_str = NULL; int rc = 0; char *tmp;
tmp = kstrdup(range, GFP_KERNEL); if (!tmp) return -ENOMEM;
to = from;
to_id0 = from_id0;
to_id1 = from_id1; if (to_str) { if (dasd_busid(to_str, &to_id0, &to_id1, &to)) {
rc = -EINVAL; goto out;
} if (from_id0 != to_id0 || from_id1 != to_id1 || from > to) {
pr_err("%s is not a valid device range\n", range);
rc = -EINVAL; goto out;
}
}
features = dasd_feature_list(features_str); if (features < 0) {
rc = -EINVAL; goto out;
} /* each device in dasd= parameter should be set initially online */
features |= DASD_FEATURE_INITIAL_ONLINE; while (from <= to) {
sprintf(bus_id, "%01x.%01x.%04x", from_id0, from_id1, from++);
devmap = dasd_add_busid(bus_id, features); if (IS_ERR(devmap)) {
rc = PTR_ERR(devmap); goto out;
}
}
out:
kfree(tmp);
return rc;
}
/* * Parse parameters stored in dasd[] * The 'dasd=...' parameter allows to specify a comma separated list of * keywords and device ranges. The parameters in that list will be stored as * separate elementes in dasd[].
*/ int __init dasd_parse(void)
{ int rc, i; char *cur;
rc = 0; for (i = 0; i < DASD_MAX_PARAMS; i++) {
cur = dasd[i]; if (!cur) break; if (*cur == '\0') continue;
rc = dasd_parse_keyword(cur); if (rc)
rc = dasd_parse_range(cur);
if (rc) break;
}
return rc;
}
/* * Add a devmap for the device specified by busid. It is possible that * the devmap already exists (dasd= parameter). The order of the devices * added through this function will define the kdevs for the individual * devices.
*/ staticstruct dasd_devmap *
dasd_add_busid(constchar *bus_id, int features)
{ struct dasd_devmap *devmap, *new, *tmp; int hash;
new = kzalloc(sizeof(struct dasd_devmap), GFP_KERNEL); if (!new) return ERR_PTR(-ENOMEM);
spin_lock(&dasd_devmap_lock);
devmap = NULL;
hash = dasd_hash_busid(bus_id);
list_for_each_entry(tmp, &dasd_hashlists[hash], list) if (strncmp(tmp->bus_id, bus_id, DASD_BUS_ID_SIZE) == 0) {
devmap = tmp; break;
} if (!devmap) { /* This bus_id is new. */
new->devindex = dasd_max_devindex++;
strscpy(new->bus_id, bus_id, DASD_BUS_ID_SIZE);
new->features = features;
new->device = NULL;
list_add(&new->list, &dasd_hashlists[hash]);
devmap = new; new = NULL;
}
spin_unlock(&dasd_devmap_lock);
kfree(new); return devmap;
}
/* * Check if busid has been added to the list of dasd ranges.
*/ int
dasd_busid_known(constchar *bus_id)
{ return IS_ERR(dasd_find_busid(bus_id)) ? -ENOENT : 0;
}
/* * Forget all about the device numbers added so far. * This may only be called at module unload or system shutdown.
*/ staticvoid
dasd_forget_ranges(void)
{ struct dasd_devmap *devmap, *n; int i;
spin_lock(&dasd_devmap_lock); for (i = 0; i < 256; i++) {
list_for_each_entry_safe(devmap, n, &dasd_hashlists[i], list) {
BUG_ON(devmap->device != NULL);
list_del(&devmap->list);
kfree(devmap);
}
}
spin_unlock(&dasd_devmap_lock);
}
/* * Find the device struct by its device index.
*/ struct dasd_device *
dasd_device_from_devindex(int devindex)
{ struct dasd_devmap *devmap, *tmp; struct dasd_device *device; int i;
spin_lock(&dasd_devmap_lock);
devmap = NULL; for (i = 0; (i < 256) && !devmap; i++)
list_for_each_entry(tmp, &dasd_hashlists[i], list) if (tmp->devindex == devindex) { /* Found the devmap for the device. */
devmap = tmp; break;
} if (devmap && devmap->device) {
device = devmap->device;
dasd_get_device(device);
} else
device = ERR_PTR(-ENODEV);
spin_unlock(&dasd_devmap_lock); return device;
}
/* * Return devmap for cdev. If no devmap exists yet, create one and * connect it to the cdev.
*/ staticstruct dasd_devmap *
dasd_devmap_from_cdev(struct ccw_device *cdev)
{ struct dasd_devmap *devmap;
device->paths_info = kset_create_and_add("paths_info", NULL,
&device->cdev->dev.kobj); if (!device->paths_info)
dev_warn(&cdev->dev, "Could not create paths_info kset\n");
return device;
}
/* * allocate a PPRC data structure and call the discipline function to fill
*/ staticint dasd_devmap_get_pprc_status(struct dasd_device *device, struct dasd_pprc_data_sc4 **data)
{ struct dasd_pprc_data_sc4 *temp;
if (!device->discipline || !device->discipline->pprc_status) {
dev_warn(&device->cdev->dev, "Unable to query copy relation status\n"); return -EOPNOTSUPP;
}
temp = kzalloc(sizeof(*temp), GFP_KERNEL); if (!temp) return -ENOMEM;
/* get PPRC information from storage */ if (device->discipline->pprc_status(device, temp)) {
dev_warn(&device->cdev->dev, "Error during copy relation status query\n");
kfree(temp); return -EINVAL;
}
*data = temp;
return 0;
}
/* * find an entry in a PPRC device_info array by a given UID * depending on the primary/secondary state of the device it has to be * matched with the respective fields
*/ staticint dasd_devmap_entry_from_pprc_data(struct dasd_pprc_data_sc4 *data, struct dasd_uid uid, bool primary)
{ int i;
for (i = 0; i < DASD_CP_ENTRIES; i++) { if (primary) { if (data->dev_info[i].prim_cu_ssid == uid.ssid &&
data->dev_info[i].primary == uid.real_unit_addr) return i;
} else { if (data->dev_info[i].sec_cu_ssid == uid.ssid &&
data->dev_info[i].secondary == uid.real_unit_addr) return i;
}
} return -1;
}
/* * check the consistency of a specified copy relation by checking * the following things: * * - is the given device part of a copy pair setup * - does the state of the device match the state in the PPRC status data * - does the device UID match with the UID in the PPRC status data * - to prevent misrouted IO check if the given device is present in all * related PPRC status data
*/ staticint dasd_devmap_check_copy_relation(struct dasd_device *device, struct dasd_copy_entry *entry, struct dasd_pprc_data_sc4 *data, struct dasd_copy_relation *copy)
{ struct dasd_pprc_data_sc4 *tmp_dat; struct dasd_device *tmp_dev; struct dasd_uid uid; int i, j;
if (!device->discipline || !device->discipline->get_uid ||
device->discipline->get_uid(device, &uid)) return 1;
i = dasd_devmap_entry_from_pprc_data(data, uid, entry->primary); if (i < 0) {
dev_warn(&device->cdev->dev, "Device not part of a copy relation\n"); return 1;
}
/* double check which role the current device has */ if (entry->primary) { if (data->dev_info[i].flags & 0x80) {
dev_warn(&device->cdev->dev, "Copy pair secondary is setup as primary\n"); return 1;
} if (data->dev_info[i].prim_cu_ssid != uid.ssid ||
data->dev_info[i].primary != uid.real_unit_addr) {
dev_warn(&device->cdev->dev, "Primary device %s does not match copy pair status primary device %04x\n",
dev_name(&device->cdev->dev),
data->dev_info[i].prim_cu_ssid |
data->dev_info[i].primary); return 1;
}
} else { if (!(data->dev_info[i].flags & 0x80)) {
dev_warn(&device->cdev->dev, "Copy pair primary is setup as secondary\n"); return 1;
} if (data->dev_info[i].sec_cu_ssid != uid.ssid ||
data->dev_info[i].secondary != uid.real_unit_addr) {
dev_warn(&device->cdev->dev, "Secondary device %s does not match copy pair status secondary device %04x\n",
dev_name(&device->cdev->dev),
data->dev_info[i].sec_cu_ssid |
data->dev_info[i].secondary); return 1;
}
}
if (dasd_devmap_get_pprc_status(tmp_dev, &tmp_dat)) return 1;
if (dasd_devmap_entry_from_pprc_data(tmp_dat, uid, entry->primary) < 0) {
dev_warn(&tmp_dev->cdev->dev, "Copy pair relation does not contain device: %s\n",
dev_name(&device->cdev->dev));
kfree(tmp_dat); return 1;
}
kfree(tmp_dat);
} return 0;
}
/* delete device from copy relation entry */ staticvoid dasd_devmap_delete_copy_relation_device(struct dasd_device *device)
{ struct dasd_copy_relation *copy; int i;
if (!device->copy) return;
copy = device->copy; for (i = 0; i < DASD_CP_ENTRIES; i++) { if (copy->entry[i].device == device)
copy->entry[i].device = NULL;
}
dasd_put_device(device);
device->copy = NULL;
}
/* * read all required information for a copy relation setup and setup the device * accordingly
*/ int dasd_devmap_set_device_copy_relation(struct ccw_device *cdev, bool pprc_enabled)
{ struct dasd_pprc_data_sc4 *data = NULL; struct dasd_copy_entry *entry = NULL; struct dasd_copy_relation *copy; struct dasd_devmap *devmap; struct dasd_device *device; int i, rc = 0;
devmap = dasd_devmap_from_cdev(cdev); if (IS_ERR(devmap)) return PTR_ERR(devmap);
device = devmap->device; if (!device) return -ENODEV;
copy = devmap->copy; /* no copy pair setup for this device */ if (!copy) goto out;
rc = dasd_devmap_get_pprc_status(device, &data); if (rc) return rc;
/* print error if PPRC is requested but not enabled on storage server */ if (!pprc_enabled) {
dev_err(&cdev->dev, "Copy relation not enabled on storage server\n");
rc = -EINVAL; goto out;
}
/* Remove copy relation */
dasd_devmap_delete_copy_relation_device(device); /* * Drop ref_count by 3, one for the devmap reference, one for * the cdev reference and one for the passed reference.
*/
atomic_sub(3, &device->ref_count);
/* Wait for reference counter to drop to zero. */
wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
/* Put ccw_device structure. */
put_device(&cdev->dev);
/* Now the device structure can be freed. */
dasd_free_device(device);
}
/* * Reference counter dropped to zero. Wake up waiter * in dasd_delete_device.
*/ void
dasd_put_device_wake(struct dasd_device *device)
{
wake_up(&dasd_delete_wq);
}
EXPORT_SYMBOL_GPL(dasd_put_device_wake);
/* * Return dasd_device structure associated with cdev. * This function needs to be called with the ccw device * lock held. It can be used from interrupt context.
*/ struct dasd_device *
dasd_device_from_cdev_locked(struct ccw_device *cdev)
{ struct dasd_device *device = dev_get_drvdata(&cdev->dev);
if (!device) return ERR_PTR(-ENODEV);
dasd_get_device(device); return device;
}
/* * failfast controls the behaviour, if no path is available
*/ static ssize_t dasd_ff_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct dasd_devmap *devmap; int ff_flag;
/* * use_diag controls whether the driver should use diag rather than ssch * to talk to the device
*/ static ssize_t
dasd_use_diag_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct dasd_devmap *devmap; int use_diag;
/* * use_raw controls whether the driver should give access to raw eckd data or * operate in standard mode
*/ static ssize_t
dasd_use_raw_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct dasd_devmap *devmap; int use_raw;
device = dasd_device_from_cdev(to_ccwdev(dev)); if (!IS_ERR(device)) { switch (device->state) { case DASD_STATE_NEW:
len = sysfs_emit(buf, "new\n"); break; case DASD_STATE_KNOWN:
len = sysfs_emit(buf, "detected\n"); break; case DASD_STATE_BASIC:
len = sysfs_emit(buf, "basic\n"); break; case DASD_STATE_UNFMT:
len = sysfs_emit(buf, "unformatted\n"); break; case DASD_STATE_READY:
len = sysfs_emit(buf, "ready\n"); break; case DASD_STATE_ONLINE:
len = sysfs_emit(buf, "online\n"); break; default:
len = sysfs_emit(buf, "no stat\n"); break;
}
dasd_put_device(device);
} else
len = sysfs_emit(buf, "unknown\n"); return len;
}
/* * aq_mask controls if the DASD should be quiesced on certain triggers * The aq_mask attribute is interpreted as bitmap of the DASD_EER_* triggers.
*/ static ssize_t dasd_aq_mask_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct dasd_devmap *devmap; unsignedint aq_mask = 0;
devmap = dasd_find_busid(dev_name(dev)); if (!IS_ERR(devmap))
aq_mask = devmap->aq_mask;
/* * aq_requeue controls if requests are returned to the blocklayer on quiesce * or if requests are only not started
*/ static ssize_t dasd_aqr_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct dasd_devmap *devmap; int flag;
devmap = dasd_find_busid(dev_name(dev)); if (!IS_ERR(devmap))
flag = (devmap->features & DASD_FEATURE_REQUEUEQUIESCE) != 0; else
flag = (DASD_FEATURE_DEFAULT &
DASD_FEATURE_REQUEUEQUIESCE) != 0; return sysfs_emit(buf, "%d\n", flag);
}
/* * aq_timeouts controls how much retries have to time out until * a device gets autoquiesced
*/ static ssize_t
dasd_aq_timeouts_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct dasd_device *device; int len;
device = dasd_device_from_cdev(to_ccwdev(dev)); if (IS_ERR(device)) return -ENODEV;
len = sysfs_emit(buf, "%u\n", device->aq_timeouts);
dasd_put_device(device); return len;
}
/* * print copy relation in the form * primary,secondary[1] primary,secondary[2], ...
*/ static ssize_t
dasd_copy_pair_show(struct device *dev, struct device_attribute *attr, char *buf)
{ char prim_busid[DASD_BUS_ID_SIZE]; struct dasd_copy_relation *copy; struct dasd_devmap *devmap; int len = 0; int i;
devmap = dasd_find_busid(dev_name(dev)); if (IS_ERR(devmap)) return -ENODEV;
if (!devmap->copy) return -ENODEV;
copy = devmap->copy; /* find primary */ for (i = 0; i < DASD_CP_ENTRIES; i++) { if (copy->entry[i].configured && copy->entry[i].primary) {
strscpy(prim_busid, copy->entry[i].busid,
DASD_BUS_ID_SIZE); break;
}
} if (i == DASD_CP_ENTRIES) goto out;
/* print all secondary */ for (i = 0; i < DASD_CP_ENTRIES; i++) { if (copy->entry[i].configured && !copy->entry[i].primary)
len += sysfs_emit_at(buf, len, "%s,%s ", prim_busid,
copy->entry[i].busid);
}
len += sysfs_emit_at(buf, len, "\n");
out: return len;
}
/* find free entry */ for (i = 0; i < DASD_CP_ENTRIES; i++) { /* current bus_id already included, nothing to do */ if (copy->entry[i].configured &&
strncmp(copy->entry[i].busid, busid, DASD_BUS_ID_SIZE) == 0) return 0;
if (!copy->entry[i].configured) break;
} if (i == DASD_CP_ENTRIES) return -EINVAL;
staticint dasd_devmap_clear_copy_relation(struct device *dev)
{ struct dasd_copy_relation *copy; struct dasd_devmap *devmap; int i, rc = 1;
devmap = dasd_devmap_from_cdev(to_ccwdev(dev)); if (IS_ERR(devmap)) return 1;
spin_lock(&dasd_devmap_lock); if (!devmap->copy) goto out;
copy = devmap->copy; /* first check if all secondary devices are offline*/ for (i = 0; i < DASD_CP_ENTRIES; i++) { if (!copy->entry[i].configured) continue;
if (copy->entry[i].device == copy->active->device) continue;
if (copy->entry[i].device) goto out;
} /* clear all devmap entries */ for (i = 0; i < DASD_CP_ENTRIES; i++) { if (strlen(copy->entry[i].busid) == 0) continue; if (copy->entry[i].device) {
dasd_put_device(copy->entry[i].device);
copy->entry[i].device->copy = NULL;
copy->entry[i].device = NULL;
}
devmap = dasd_find_busid_locked(copy->entry[i].busid);
devmap->copy = NULL;
memset(copy->entry[i].busid, 0, DASD_BUS_ID_SIZE);
}
kfree(copy);
rc = 0;
out:
spin_unlock(&dasd_devmap_lock); return rc;
}
/* * parse BUSIDs from a copy pair
*/ staticint dasd_devmap_parse_busid(constchar *buf, char *prim_busid, char *sec_busid)
{ char *primary, *secondary, *tmp, *pt; int id0, id1, id2;
device = dasd_device_from_cdev(to_ccwdev(dev)); if (IS_ERR(device)) return -ENODEV;
if (!device->copy) {
len = sysfs_emit(buf, "none\n"); goto out;
}
copy = device->copy; /* only the active device is primary */ if (copy->active->device == device) {
len = sysfs_emit(buf, "primary\n"); goto out;
} for (i = 0; i < DASD_CP_ENTRIES; i++) { if (copy->entry[i].device == device) {
len = sysfs_emit(buf, "secondary\n"); goto out;
}
} /* not in the list, no COPY role */
len = sysfs_emit(buf, "none\n");
out:
dasd_put_device(device); return len;
} static DEVICE_ATTR(copy_role, 0444, dasd_copy_role_show, NULL);
/* * Return value of the specified feature.
*/ int
dasd_get_feature(struct ccw_device *cdev, int feature)
{ struct dasd_devmap *devmap;
devmap = dasd_find_busid(dev_name(&cdev->dev)); if (IS_ERR(devmap)) return PTR_ERR(devmap);
return ((devmap->features & feature) != 0);
}
/* * Set / reset given feature. * Flag indicates whether to set (!=0) or the reset (=0) the feature.
*/ int
dasd_set_feature(struct ccw_device *cdev, int feature, int flag)
{ struct dasd_devmap *devmap;
devmap = dasd_devmap_from_cdev(cdev); if (IS_ERR(devmap)) return PTR_ERR(devmap);
spin_lock(&dasd_devmap_lock); if (flag)
devmap->features |= feature; else
devmap->features &= ~feature; if (devmap->device)
devmap->device->features = devmap->features;
spin_unlock(&dasd_devmap_lock); return 0;
}
EXPORT_SYMBOL(dasd_set_feature);
void dasd_path_create_kobj(struct dasd_device *device, int chp)
{ int rc;
if (test_bit(DASD_FLAG_OFFLINE, &device->flags)) return; if (!device->paths_info) {
dev_warn(&device->cdev->dev, "Unable to create paths objects\n"); return;
} if (device->path[chp].in_sysfs) return; if (!device->path[chp].conf_data) return;
staticvoid dasd_path_remove_kobj(struct dasd_device *device, int chp)
{ if (device->path[chp].in_sysfs) {
kobject_put(&device->path[chp].kobj);
device->path[chp].in_sysfs = false;
}
}
/* * As we keep kobjects for the lifetime of a device, this function must not be * called anywhere but in the context of offlining a device.
*/ void dasd_path_remove_kobjects(struct dasd_device *device)
{ int i;
for (i = 0; i < 8; i++)
dasd_path_remove_kobj(device, i);
}
EXPORT_SYMBOL(dasd_path_remove_kobjects);
int
dasd_devmap_init(void)
{ int i;
/* Initialize devmap structures. */
dasd_max_devindex = 0; for (i = 0; i < 256; i++)
INIT_LIST_HEAD(&dasd_hashlists[i]); return 0;
}
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.