Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
# SPDX-License-Identifier: GPL-2.0
comment "S/390 block device drivers"
depends on S390 && BLOCK
config DCSSBLK
def_tristate m
prompt "DCSSBLK support"
depends on S390 && BLOCK && ZONE_DEVICE
select FS_DAX
help
Support for dcss block device
config DASD
def_tristate y
prompt "Support for DASD devices"
depends on CCW && BLOCK
help
Enable this option if you want to access DASDs directly utilizing
S/390s channel subsystem commands. This is necessary for running
natively on a single image or an LPAR.
config DASD_PROFILE
def_bool y
prompt "Profiling support for dasd devices"
depends on DASD
help
Enable this option if you want to see profiling information
in /proc/dasd/statistics.
config DASD_ECKD
def_tristate y
prompt "Support for ECKD Disks"
depends on DASD
help
ECKD devices are the most commonly used devices. You should enable
this option unless you are very sure to have no ECKD device.
config DASD_FBA
def_tristate y
prompt "Support for FBA Disks"
depends on DASD
help
Select this option to be able to access FBA devices. It is safe to
say "Y".
config DASD_DIAG
def_tristate y
prompt "Support for DIAG access to Disks"
depends on DASD
help
Select this option if you want to use Diagnose250 command to access
Disks under VM. If you are not running under VM or unsure what it is,
say "N".
config DASD_EER
def_bool y
prompt "Extended error reporting (EER)"
depends on DASD
help
This driver provides a character device interface to the
DASD extended error reporting. This is only needed if you want to
use applications written for the EER facility.
config SCM_BLOCK
def_tristate m
prompt "Support for Storage Class Memory"
depends on S390 && BLOCK && EADM_SCH && SCM_BUS
help
Block device driver for Storage Class Memory (SCM). This driver
provides a block device interface for each available SCM increment.
To compile this driver as a module, choose M here: the
module will be called scm_block.
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
#
# S/390 block devices
#
dasd_eckd_mod-objs := dasd_eckd.o dasd_3990_erp.o dasd_alias.o
dasd_fba_mod-objs := dasd_fba.o
dasd_diag_mod-objs := dasd_diag.o
dasd_mod-objs := dasd.o dasd_ioctl.o dasd_proc.o dasd_devmap.o \
dasd_genhd.o dasd_erp.o
ifdef CONFIG_DASD_EER
dasd_mod-objs += dasd_eer.o
endif
obj-$(CONFIG_DASD) += dasd_mod.o
obj-$(CONFIG_DASD_DIAG) += dasd_diag_mod.o
obj-$(CONFIG_DASD_ECKD) += dasd_eckd_mod.o
obj-$(CONFIG_DASD_FBA) += dasd_fba_mod.o
obj-$(CONFIG_DCSSBLK) += dcssblk.o
scm_block-objs := scm_drv.o scm_blk.o
obj-$(CONFIG_SCM_BLOCK) += scm_block.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+972
View File
@@ -0,0 +1,972 @@
// 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>
*/
#include <linux/list.h>
#include <linux/slab.h>
#include <asm/ebcdic.h>
#include "dasd_int.h"
#include "dasd_eckd.h"
/*
* 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.
*/
static void summary_unit_check_handling_work(struct work_struct *);
static void lcu_update_work(struct work_struct *);
static int _schedule_lcu_update(struct alias_lcu *, struct dasd_device *);
static struct alias_root aliastree = {
.serverlist = LIST_HEAD_INIT(aliastree.serverlist),
.lock = __SPIN_LOCK_UNLOCKED(aliastree.lock),
};
static struct alias_server *_find_server(struct dasd_uid *uid)
{
struct alias_server *pos;
list_for_each_entry(pos, &aliastree.serverlist, server) {
if (!strncmp(pos->uid.vendor, uid->vendor,
sizeof(uid->vendor))
&& !strncmp(pos->uid.serial, uid->serial,
sizeof(uid->serial)))
return pos;
}
return NULL;
}
static struct alias_lcu *_find_lcu(struct alias_server *server,
struct dasd_uid *uid)
{
struct alias_lcu *pos;
list_for_each_entry(pos, &server->lculist, lcu) {
if (pos->uid.ssid == uid->ssid)
return pos;
}
return NULL;
}
static struct alias_pav_group *_find_group(struct alias_lcu *lcu,
struct dasd_uid *uid)
{
struct alias_pav_group *pos;
__u8 search_unit_addr;
/* 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;
}
static struct alias_server *_allocate_server(struct dasd_uid *uid)
{
struct alias_server *server;
server = kzalloc_obj(*server);
if (!server)
return ERR_PTR(-ENOMEM);
memcpy(server->uid.vendor, uid->vendor, sizeof(uid->vendor));
memcpy(server->uid.serial, uid->serial, sizeof(uid->serial));
INIT_LIST_HEAD(&server->server);
INIT_LIST_HEAD(&server->lculist);
return server;
}
static void _free_server(struct alias_server *server)
{
kfree(server);
}
static struct alias_lcu *_allocate_lcu(struct dasd_uid *uid)
{
struct alias_lcu *lcu;
lcu = kzalloc_obj(*lcu);
if (!lcu)
return ERR_PTR(-ENOMEM);
lcu->uac = kzalloc_obj(*(lcu->uac), GFP_KERNEL | GFP_DMA);
if (!lcu->uac)
goto out_err1;
lcu->rsu_cqr = kzalloc_obj(*lcu->rsu_cqr, GFP_KERNEL | GFP_DMA);
if (!lcu->rsu_cqr)
goto out_err2;
lcu->rsu_cqr->cpaddr = kzalloc_obj(struct ccw1, GFP_KERNEL | GFP_DMA);
if (!lcu->rsu_cqr->cpaddr)
goto out_err3;
lcu->rsu_cqr->data = kzalloc(16, GFP_KERNEL | GFP_DMA);
if (!lcu->rsu_cqr->data)
goto out_err4;
memcpy(lcu->uid.vendor, uid->vendor, sizeof(uid->vendor));
memcpy(lcu->uid.serial, uid->serial, sizeof(uid->serial));
lcu->uid.ssid = uid->ssid;
lcu->pav = NO_PAV;
lcu->flags = NEED_UAC_UPDATE | UPDATE_PENDING;
INIT_LIST_HEAD(&lcu->lcu);
INIT_LIST_HEAD(&lcu->inactive_devices);
INIT_LIST_HEAD(&lcu->active_devices);
INIT_LIST_HEAD(&lcu->grouplist);
INIT_WORK(&lcu->suc_data.worker, summary_unit_check_handling_work);
INIT_DELAYED_WORK(&lcu->ruac_data.dwork, lcu_update_work);
spin_lock_init(&lcu->lock);
init_completion(&lcu->lcu_setup);
return lcu;
out_err4:
kfree(lcu->rsu_cqr->cpaddr);
out_err3:
kfree(lcu->rsu_cqr);
out_err2:
kfree(lcu->uac);
out_err1:
kfree(lcu);
return ERR_PTR(-ENOMEM);
}
static void _free_lcu(struct alias_lcu *lcu)
{
kfree(lcu->rsu_cqr->data);
kfree(lcu->rsu_cqr->cpaddr);
kfree(lcu->rsu_cqr);
kfree(lcu->uac);
kfree(lcu);
}
/*
* 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;
unsigned long 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);
}
}
lcu = _find_lcu(server, &uid);
if (!lcu) {
spin_unlock_irqrestore(&aliastree.lock, flags);
newlcu = _allocate_lcu(&uid);
if (IS_ERR(newlcu))
return PTR_ERR(newlcu);
spin_lock_irqsave(&aliastree.lock, flags);
lcu = _find_lcu(server, &uid);
if (!lcu) {
list_add(&newlcu->lcu, &server->lculist);
lcu = newlcu;
} else {
/* someone was faster */
_free_lcu(newlcu);
}
}
spin_lock(&lcu->lock);
list_add(&device->alias_list, &lcu->inactive_devices);
private->lcu = lcu;
spin_unlock(&lcu->lock);
spin_unlock_irqrestore(&aliastree.lock, flags);
return 0;
}
/*
* 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;
unsigned long 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.
*/
static int _add_device_to_lcu(struct alias_lcu *lcu,
struct dasd_device *device,
struct dasd_device *pos)
{
struct dasd_eckd_private *private = device->private;
struct alias_pav_group *group;
struct dasd_uid uid;
spin_lock(get_ccwdev_lock(device->cdev));
private->uid.type = lcu->uac->unit[private->uid.real_unit_addr].ua_type;
private->uid.base_unit_addr =
lcu->uac->unit[private->uid.real_unit_addr].base_ua;
uid = private->uid;
spin_unlock(get_ccwdev_lock(device->cdev));
/* if we have no PAV anyway, we don't need to bother with PAV groups */
if (lcu->pav == NO_PAV) {
list_move(&device->alias_list, &lcu->active_devices);
return 0;
}
group = _find_group(lcu, &uid);
if (!group) {
group = kzalloc_obj(*group, GFP_ATOMIC);
if (!group)
return -ENOMEM;
memcpy(group->uid.vendor, uid.vendor, sizeof(uid.vendor));
memcpy(group->uid.serial, uid.serial, sizeof(uid.serial));
group->uid.ssid = uid.ssid;
if (uid.type == UA_BASE_DEVICE)
group->uid.base_unit_addr = uid.real_unit_addr;
else
group->uid.base_unit_addr = uid.base_unit_addr;
memcpy(group->uid.vduit, uid.vduit, sizeof(uid.vduit));
INIT_LIST_HEAD(&group->group);
INIT_LIST_HEAD(&group->baselist);
INIT_LIST_HEAD(&group->aliaslist);
list_add(&group->group, &lcu->grouplist);
}
if (uid.type == UA_BASE_DEVICE)
list_move(&device->alias_list, &group->baselist);
else
list_move(&device->alias_list, &group->aliaslist);
private->pavgroup = group;
return 0;
};
static void _remove_device_from_lcu(struct alias_lcu *lcu,
struct dasd_device *device)
{
struct dasd_eckd_private *private = device->private;
struct alias_pav_group *group;
list_move(&device->alias_list, &lcu->inactive_devices);
group = private->pavgroup;
if (!group)
return;
private->pavgroup = NULL;
if (list_empty(&group->baselist) && list_empty(&group->aliaslist)) {
list_del(&group->group);
kfree(group);
return;
}
if (group->next == device)
group->next = NULL;
};
static int
suborder_not_supported(struct dasd_ccw_req *cqr)
{
char *sense;
char reason;
char msg_format;
char msg_no;
/*
* 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;
reason = sense[0];
msg_format = (sense[7] & 0xF0);
msg_no = (sense[7] & 0x0F);
/* command reject, Format 0 MSG 4 - invalid parameter */
if ((reason == 0x80) && (msg_format == 0x00) && (msg_no == 0x04))
return 1;
return 0;
}
static int read_unit_address_configuration(struct dasd_device *device,
struct alias_lcu *lcu)
{
struct dasd_psf_prssd_data *prssdp;
struct dasd_ccw_req *cqr;
struct ccw1 *ccw;
int rc;
unsigned long flags;
cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */,
(sizeof(struct dasd_psf_prssd_data)),
device, NULL);
if (IS_ERR(cqr))
return PTR_ERR(cqr);
cqr->startdev = device;
cqr->memdev = device;
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
cqr->retries = 10;
cqr->expires = 20 * HZ;
/* 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 */
ccw = cqr->cpaddr;
ccw->cmd_code = DASD_ECKD_CCW_PSF;
ccw->count = sizeof(struct dasd_psf_prssd_data);
ccw->flags |= CCW_FLAG_CC;
ccw->cda = virt_to_dma32(prssdp);
/* Read Subsystem Data - feature codes */
memset(lcu->uac, 0, sizeof(*(lcu->uac)));
ccw++;
ccw->cmd_code = DASD_ECKD_CCW_RSSD;
ccw->count = sizeof(*(lcu->uac));
ccw->cda = virt_to_dma32(lcu->uac);
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
/* 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;
}
static int _lcu_update(struct dasd_device *refdev, struct alias_lcu *lcu)
{
unsigned long flags;
struct alias_pav_group *pavgroup, *tempgroup;
struct dasd_device *device, *tempdev;
int i, rc;
struct dasd_eckd_private *private;
spin_lock_irqsave(&lcu->lock, flags);
list_for_each_entry_safe(pavgroup, tempgroup, &lcu->grouplist, group) {
list_for_each_entry_safe(device, tempdev, &pavgroup->baselist,
alias_list) {
list_move(&device->alias_list, &lcu->active_devices);
private = device->private;
private->pavgroup = NULL;
}
list_for_each_entry_safe(device, tempdev, &pavgroup->aliaslist,
alias_list) {
list_move(&device->alias_list, &lcu->active_devices);
private = device->private;
private->pavgroup = NULL;
}
list_del(&pavgroup->group);
kfree(pavgroup);
}
spin_unlock_irqrestore(&lcu->lock, flags);
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;
}
list_for_each_entry_safe(device, tempdev, &lcu->active_devices,
alias_list) {
_add_device_to_lcu(lcu, device, refdev);
}
out:
spin_unlock_irqrestore(&lcu->lock, flags);
return 0;
}
static void lcu_update_work(struct work_struct *work)
{
struct alias_lcu *lcu;
struct read_uac_work_data *ruac_data;
struct dasd_device *device;
unsigned long flags;
int rc;
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);
}
static int _schedule_lcu_update(struct alias_lcu *lcu,
struct dasd_device *device)
{
struct dasd_device *usedev = NULL;
struct alias_pav_group *group;
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);
else if (!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;
}
int dasd_alias_add_device(struct dasd_device *device)
{
struct dasd_eckd_private *private = device->private;
__u8 uaddr = private->uid.real_unit_addr;
struct alias_lcu *lcu = private->lcu;
unsigned long flags;
int rc;
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;
private->lcu->flags |= UPDATE_PENDING;
return dasd_alias_add_device(device);
}
int dasd_alias_remove_device(struct dasd_device *device)
{
struct dasd_eckd_private *private = device->private;
struct alias_lcu *lcu = private->lcu;
unsigned long flags;
/* nothing to do if already removed */
if (!lcu)
return 0;
spin_lock_irqsave(&lcu->lock, flags);
_remove_device_from_lcu(lcu, device);
spin_unlock_irqrestore(&lcu->lock, flags);
return 0;
}
struct dasd_device *dasd_alias_get_start_dev(struct dasd_device *base_device)
{
struct dasd_eckd_private *alias_priv, *private = base_device->private;
struct alias_lcu *lcu = private->lcu;
struct dasd_device *alias_device;
struct alias_pav_group *group;
unsigned long flags;
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;
}
spin_lock_irqsave(&lcu->lock, flags);
group = private->pavgroup;
if (!group) {
spin_unlock_irqrestore(&lcu->lock, flags);
return NULL;
}
alias_device = group->next;
if (!alias_device) {
if (list_empty(&group->aliaslist)) {
spin_unlock_irqrestore(&lcu->lock, flags);
return NULL;
} else {
alias_device = list_first_entry(&group->aliaslist,
struct dasd_device,
alias_list);
}
}
if (list_is_last(&alias_device->alias_list, &group->aliaslist))
group->next = list_first_entry(&group->aliaslist,
struct dasd_device, alias_list);
else
group->next = list_first_entry(&alias_device->alias_list,
struct dasd_device, alias_list);
spin_unlock_irqrestore(&lcu->lock, flags);
alias_priv = alias_device->private;
if ((alias_priv->count < private->count) && !alias_device->stopped &&
!test_bit(DASD_FLAG_OFFLINE, &alias_device->flags))
return alias_device;
else
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
*/
static int 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;
cqr = lcu->rsu_cqr;
memcpy((char *) &cqr->magic, "ECKD", 4);
ASCEBC((char *) &cqr->magic, 4);
ccw = cqr->cpaddr;
ccw->cmd_code = DASD_ECKD_CCW_RSCK;
ccw->flags = CCW_FLAG_SLI;
ccw->count = 16;
ccw->cda = virt_to_dma32(cqr->data);
((char *)cqr->data)[0] = reason;
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
cqr->retries = 255; /* set retry counter to enable basic ERP */
cqr->startdev = device;
cqr->memdev = device;
cqr->block = NULL;
cqr->expires = 5 * HZ;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
rc = dasd_sleep_on_immediatly(cqr);
return rc;
}
static void _restart_all_base_devices_on_lcu(struct alias_lcu *lcu)
{
struct alias_pav_group *pavgroup;
struct dasd_device *device;
struct dasd_eckd_private *private;
/* active and inactive list can contain alias as well as base devices */
list_for_each_entry(device, &lcu->active_devices, alias_list) {
private = device->private;
if (private->uid.type != UA_BASE_DEVICE)
continue;
dasd_schedule_block_bh(device->block);
dasd_schedule_device_bh(device);
}
list_for_each_entry(device, &lcu->inactive_devices, alias_list) {
private = device->private;
if (private->uid.type != UA_BASE_DEVICE)
continue;
dasd_schedule_block_bh(device->block);
dasd_schedule_device_bh(device);
}
list_for_each_entry(pavgroup, &lcu->grouplist, group) {
list_for_each_entry(device, &pavgroup->baselist, alias_list) {
dasd_schedule_block_bh(device->block);
dasd_schedule_device_bh(device);
}
}
}
static void flush_all_alias_devices_on_lcu(struct alias_lcu *lcu)
{
struct alias_pav_group *pavgroup;
struct dasd_device *device, *temp;
struct dasd_eckd_private *private;
unsigned long flags;
LIST_HEAD(active);
/*
* 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.
*/
spin_lock_irqsave(&lcu->lock, flags);
list_for_each_entry_safe(device, temp, &lcu->active_devices,
alias_list) {
private = device->private;
if (private->uid.type == UA_BASE_DEVICE)
continue;
list_move(&device->alias_list, &active);
}
list_for_each_entry(pavgroup, &lcu->grouplist, group) {
list_splice_init(&pavgroup->aliaslist, &active);
}
while (!list_empty(&active)) {
device = list_first_entry(&active, struct dasd_device,
alias_list);
spin_unlock_irqrestore(&lcu->lock, flags);
dasd_flush_device_queue(device);
spin_lock_irqsave(&lcu->lock, flags);
/*
* only move device around if it wasn't moved away while we
* were waiting for the flush
*/
if (device == list_first_entry(&active,
struct dasd_device, alias_list)) {
list_move(&device->alias_list, &lcu->active_devices);
private = device->private;
private->pavgroup = NULL;
}
}
spin_unlock_irqrestore(&lcu->lock, flags);
}
static void _stop_all_devices_on_lcu(struct alias_lcu *lcu)
{
struct alias_pav_group *pavgroup;
struct dasd_device *device;
list_for_each_entry(device, &lcu->active_devices, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
list_for_each_entry(device, &lcu->inactive_devices, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
list_for_each_entry(pavgroup, &lcu->grouplist, group) {
list_for_each_entry(device, &pavgroup->baselist, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
list_for_each_entry(device, &pavgroup->aliaslist, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_set_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
}
}
static void _unstop_all_devices_on_lcu(struct alias_lcu *lcu)
{
struct alias_pav_group *pavgroup;
struct dasd_device *device;
list_for_each_entry(device, &lcu->active_devices, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
list_for_each_entry(device, &lcu->inactive_devices, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
list_for_each_entry(pavgroup, &lcu->grouplist, group) {
list_for_each_entry(device, &pavgroup->baselist, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
list_for_each_entry(device, &pavgroup->aliaslist, alias_list) {
spin_lock(get_ccwdev_lock(device->cdev));
dasd_device_remove_stop_bits(device, DASD_STOPPED_SU);
spin_unlock(get_ccwdev_lock(device->cdev));
}
}
}
static void summary_unit_check_handling_work(struct work_struct *work)
{
struct alias_lcu *lcu;
struct summary_unit_check_work_data *suc_data;
unsigned long flags;
struct dasd_device *device;
suc_data = container_of(work, struct summary_unit_check_work_data,
worker);
lcu = container_of(suc_data, struct alias_lcu, suc_data);
device = suc_data->device;
/* 1. flush alias devices */
flush_all_alias_devices_on_lcu(lcu);
/* 2. reset summary unit check */
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
dasd_device_remove_stop_bits(device,
(DASD_STOPPED_SU | DASD_STOPPED_PENDING));
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
reset_summary_unit_check(lcu, device, suc_data->reason);
spin_lock_irqsave(&lcu->lock, flags);
_unstop_all_devices_on_lcu(lcu);
_restart_all_base_devices_on_lcu(lcu);
/* 3. read new alias configuration */
_schedule_lcu_update(lcu, device);
lcu->suc_data.device = NULL;
dasd_put_device(device);
spin_unlock_irqrestore(&lcu->lock, flags);
}
void dasd_alias_handle_summary_unit_check(struct work_struct *work)
{
struct dasd_device *device = container_of(work, struct dasd_device,
suc_work);
struct dasd_eckd_private *private = device->private;
struct alias_lcu *lcu;
unsigned long flags;
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);
};
File diff suppressed because it is too large Load Diff
+680
View File
@@ -0,0 +1,680 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Based on.......: linux/drivers/s390/block/mdisk.c
* ...............: by Hartmunt Penner <hpenner@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* Copyright IBM Corp. 1999, 2000
*
*/
#include <linux/kernel_stat.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/hdreg.h>
#include <linux/bio.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <asm/asm-extable.h>
#include <asm/machine.h>
#include <asm/dasd.h>
#include <asm/debug.h>
#include <asm/diag.h>
#include <asm/ebcdic.h>
#include <linux/io.h>
#include <asm/irq.h>
#include <asm/vtoc.h>
#include <asm/asm.h>
#include "dasd_int.h"
#include "dasd_diag.h"
MODULE_DESCRIPTION("S/390 Support for DIAG access to DASD Disks");
MODULE_LICENSE("GPL");
/* The maximum number of blocks per request (max_blocks) is dependent on the
* amount of storage that is available in the static I/O buffer for each
* device. Currently each device gets 2 pages. We want to fit two requests
* into the available memory so that we can immediately start the next if one
* finishes. */
#define DIAG_MAX_BLOCKS (((2 * PAGE_SIZE - sizeof(struct dasd_ccw_req) - \
sizeof(struct dasd_diag_req)) / \
sizeof(struct dasd_diag_bio)) / 2)
#define DIAG_MAX_RETRIES 32
#define DIAG_TIMEOUT 50
static struct dasd_discipline dasd_diag_discipline;
struct dasd_diag_private {
struct dasd_diag_characteristics rdc_data;
struct dasd_diag_rw_io iob;
struct dasd_diag_init_io iib;
blocknum_t pt_block;
struct ccw_dev_id dev_id;
};
struct dasd_diag_req {
unsigned int block_count;
struct dasd_diag_bio bio[];
};
static const u8 DASD_DIAG_CMS1[] = { 0xc3, 0xd4, 0xe2, 0xf1 };/* EBCDIC CMS1 */
/* Perform DIAG250 call with block I/O parameter list iob (input and output)
* and function code cmd.
* In case of an exception return 3. Otherwise return result of bitwise OR of
* resulting condition code and DIAG return code. */
static inline int __dia250(void *iob, int cmd)
{
union register_pair rx = { .even = (unsigned long)iob, };
int cc, exception;
typedef union {
struct dasd_diag_init_io init_io;
struct dasd_diag_rw_io rw_io;
} addr_type;
exception = 1;
asm_inline volatile(
" diag %[rx],%[cmd],0x250\n"
"0: lhi %[exc],0\n"
"1:\n"
CC_IPM(cc)
EX_TABLE(0b,1b)
: CC_OUT(cc, cc), [rx] "+d" (rx.pair),
"+m" (*(addr_type *)iob), [exc] "+d" (exception)
: [cmd] "d" (cmd)
: CC_CLOBBER);
cc = exception ? 3 : CC_TRANSFORM(cc);
return cc | rx.odd;
}
static inline int dia250(void *iob, int cmd)
{
diag_stat_inc(DIAG_STAT_X250);
return __dia250(iob, cmd);
}
/* Initialize block I/O to DIAG device using the specified blocksize and
* block offset. On success, return zero and set end_block to contain the
* number of blocks on the device minus the specified offset. Return non-zero
* otherwise. */
static inline int
mdsk_init_io(struct dasd_device *device, unsigned int blocksize,
blocknum_t offset, blocknum_t *end_block)
{
struct dasd_diag_private *private = device->private;
struct dasd_diag_init_io *iib = &private->iib;
int rc;
memset(iib, 0, sizeof (struct dasd_diag_init_io));
iib->dev_nr = private->dev_id.devno;
iib->block_size = blocksize;
iib->offset = offset;
iib->flaga = DASD_DIAG_FLAGA_DEFAULT;
rc = dia250(iib, INIT_BIO);
if ((rc & 3) == 0 && end_block)
*end_block = iib->end_block;
return rc;
}
/* Remove block I/O environment for device. Return zero on success, non-zero
* otherwise. */
static inline int
mdsk_term_io(struct dasd_device * device)
{
struct dasd_diag_private *private = device->private;
struct dasd_diag_init_io *iib = &private->iib;
int rc;
memset(iib, 0, sizeof (struct dasd_diag_init_io));
iib->dev_nr = private->dev_id.devno;
rc = dia250(iib, TERM_BIO);
return rc;
}
/* Error recovery for failed DIAG requests - try to reestablish the DIAG
* environment. */
static void
dasd_diag_erp(struct dasd_device *device)
{
int rc;
mdsk_term_io(device);
rc = mdsk_init_io(device, device->block->bp_block, 0, NULL);
if (rc == 4) {
if (!(test_and_set_bit(DASD_FLAG_DEVICE_RO, &device->flags)))
pr_warn("%s: The access mode of a DIAG device changed to read-only\n",
dev_name(&device->cdev->dev));
rc = 0;
}
if (rc)
pr_warn("%s: DIAG ERP failed with rc=%d\n",
dev_name(&device->cdev->dev), rc);
}
/* Start a given request at the device. Return zero on success, non-zero
* otherwise. */
static int
dasd_start_diag(struct dasd_ccw_req * cqr)
{
struct dasd_device *device;
struct dasd_diag_private *private;
struct dasd_diag_req *dreq;
int rc;
device = cqr->startdev;
if (cqr->retries < 0) {
DBF_DEV_EVENT(DBF_ERR, device, "DIAG start_IO: request %p "
"- no retry left)", cqr);
cqr->status = DASD_CQR_ERROR;
return -EIO;
}
private = device->private;
dreq = cqr->data;
private->iob.dev_nr = private->dev_id.devno;
private->iob.key = 0;
private->iob.flags = DASD_DIAG_RWFLAG_ASYNC;
private->iob.block_count = dreq->block_count;
private->iob.interrupt_params = (addr_t) cqr;
private->iob.bio_list = dreq->bio;
private->iob.flaga = DASD_DIAG_FLAGA_DEFAULT;
cqr->startclk = get_tod_clock();
cqr->starttime = jiffies;
cqr->retries--;
rc = dia250(&private->iob, RW_BIO);
switch (rc) {
case 0: /* Synchronous I/O finished successfully */
cqr->stopclk = get_tod_clock();
cqr->status = DASD_CQR_SUCCESS;
/* Indicate to calling function that only a dasd_schedule_bh()
and no timer is needed */
rc = -EACCES;
break;
case 8: /* Asynchronous I/O was started */
cqr->status = DASD_CQR_IN_IO;
rc = 0;
break;
default: /* Error condition */
cqr->status = DASD_CQR_QUEUED;
DBF_DEV_EVENT(DBF_WARNING, device, "dia250 returned rc=%d", rc);
dasd_diag_erp(device);
rc = -EIO;
break;
}
cqr->intrc = rc;
return rc;
}
/* Terminate given request at the device. */
static int
dasd_diag_term_IO(struct dasd_ccw_req * cqr)
{
struct dasd_device *device;
device = cqr->startdev;
mdsk_term_io(device);
mdsk_init_io(device, device->block->bp_block, 0, NULL);
cqr->status = DASD_CQR_CLEAR_PENDING;
cqr->stopclk = get_tod_clock();
dasd_schedule_device_bh(device);
return 0;
}
/* Handle external interruption. */
static void dasd_ext_handler(struct ext_code ext_code,
unsigned int param32, unsigned long param64)
{
struct dasd_ccw_req *cqr, *next;
struct dasd_device *device;
unsigned long expires;
unsigned long flags;
addr_t ip;
int rc;
switch (ext_code.subcode >> 8) {
case DASD_DIAG_CODE_31BIT:
ip = (addr_t) param32;
break;
case DASD_DIAG_CODE_64BIT:
ip = (addr_t) param64;
break;
default:
return;
}
inc_irq_stat(IRQEXT_DSD);
if (!ip) { /* no intparm: unsolicited interrupt */
DBF_EVENT(DBF_NOTICE, "%s", "caught unsolicited "
"interrupt");
return;
}
cqr = (struct dasd_ccw_req *) ip;
device = (struct dasd_device *) cqr->startdev;
if (strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
DBF_DEV_EVENT(DBF_WARNING, device,
" magic number of dasd_ccw_req 0x%08X doesn't"
" match discipline 0x%08X",
cqr->magic, *(int *) (&device->discipline->name));
return;
}
/* get irq lock to modify request queue */
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
/* Check for a pending clear operation */
if (cqr->status == DASD_CQR_CLEAR_PENDING) {
cqr->status = DASD_CQR_CLEARED;
dasd_device_clear_timer(device);
dasd_schedule_device_bh(device);
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
return;
}
cqr->stopclk = get_tod_clock();
expires = 0;
if ((ext_code.subcode & 0xff) == 0) {
cqr->status = DASD_CQR_SUCCESS;
/* Start first request on queue if possible -> fast_io. */
if (!list_empty(&device->ccw_queue)) {
next = list_entry(device->ccw_queue.next,
struct dasd_ccw_req, devlist);
if (next->status == DASD_CQR_QUEUED) {
rc = dasd_start_diag(next);
if (rc == 0)
expires = next->expires;
}
}
} else {
cqr->status = DASD_CQR_QUEUED;
DBF_DEV_EVENT(DBF_DEBUG, device, "interrupt status for "
"request %p was %d (%d retries left)", cqr,
ext_code.subcode & 0xff, cqr->retries);
dasd_diag_erp(device);
}
if (expires != 0)
dasd_device_set_timer(device, expires);
else
dasd_device_clear_timer(device);
dasd_schedule_device_bh(device);
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
}
/* Check whether device can be controlled by DIAG discipline. Return zero on
* success, non-zero otherwise. */
static int
dasd_diag_check_device(struct dasd_device *device)
{
struct dasd_diag_private *private = device->private;
struct dasd_diag_characteristics *rdc_data;
struct vtoc_cms_label *label;
struct dasd_block *block;
struct dasd_diag_bio *bio;
unsigned int sb, bsize;
blocknum_t end_block;
int rc;
if (private == NULL) {
private = kzalloc_obj(*private);
if (private == NULL) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s",
"Allocating memory for private DASD data "
"failed\n");
return -ENOMEM;
}
ccw_device_get_id(device->cdev, &private->dev_id);
device->private = private;
}
block = dasd_alloc_block();
if (IS_ERR(block)) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s",
"could not allocate dasd block structure");
device->private = NULL;
kfree(private);
return PTR_ERR(block);
}
device->block = block;
block->base = device;
/* Read Device Characteristics */
rdc_data = &private->rdc_data;
rdc_data->dev_nr = private->dev_id.devno;
rdc_data->rdc_len = sizeof (struct dasd_diag_characteristics);
rc = diag210((struct diag210 *) rdc_data);
if (rc) {
DBF_DEV_EVENT(DBF_WARNING, device, "failed to retrieve device "
"information (rc=%d)", rc);
rc = -EOPNOTSUPP;
goto out;
}
device->default_expires = DIAG_TIMEOUT;
device->default_retries = DIAG_MAX_RETRIES;
/* Figure out position of label block */
switch (private->rdc_data.vdev_class) {
case DEV_CLASS_FBA:
private->pt_block = 1;
break;
case DEV_CLASS_ECKD:
private->pt_block = 2;
break;
default:
pr_warn("%s: Device type %d is not supported in DIAG mode\n",
dev_name(&device->cdev->dev),
private->rdc_data.vdev_class);
rc = -EOPNOTSUPP;
goto out;
}
DBF_DEV_EVENT(DBF_INFO, device,
"%04X: %04X on real %04X/%02X",
rdc_data->dev_nr,
rdc_data->vdev_type,
rdc_data->rdev_type, rdc_data->rdev_model);
/* terminate all outstanding operations */
mdsk_term_io(device);
/* figure out blocksize of device */
label = (struct vtoc_cms_label *) get_zeroed_page(GFP_KERNEL);
if (label == NULL) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s",
"No memory to allocate initialization request");
rc = -ENOMEM;
goto out;
}
bio = kzalloc_obj(*bio);
if (bio == NULL) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s",
"No memory to allocate initialization bio");
rc = -ENOMEM;
goto out_label;
}
rc = 0;
end_block = 0;
/* try all sizes - needed for ECKD devices */
for (bsize = 512; bsize <= PAGE_SIZE; bsize <<= 1) {
mdsk_init_io(device, bsize, 0, &end_block);
memset(bio, 0, sizeof(*bio));
bio->type = MDSK_READ_REQ;
bio->block_number = private->pt_block + 1;
bio->buffer = label;
memset(&private->iob, 0, sizeof (struct dasd_diag_rw_io));
private->iob.dev_nr = rdc_data->dev_nr;
private->iob.key = 0;
private->iob.flags = 0; /* do synchronous io */
private->iob.block_count = 1;
private->iob.interrupt_params = 0;
private->iob.bio_list = bio;
private->iob.flaga = DASD_DIAG_FLAGA_DEFAULT;
rc = dia250(&private->iob, RW_BIO);
if (rc == 3) {
pr_warn("%s: A 64-bit DIAG call failed\n",
dev_name(&device->cdev->dev));
rc = -EOPNOTSUPP;
goto out_bio;
}
mdsk_term_io(device);
if (rc == 0)
break;
}
if (bsize > PAGE_SIZE) {
pr_warn("%s: Accessing the DASD failed because of an incorrect format (rc=%d)\n",
dev_name(&device->cdev->dev), rc);
rc = -EIO;
goto out_bio;
}
/* check for label block */
if (memcmp(label->label_id, DASD_DIAG_CMS1,
sizeof(DASD_DIAG_CMS1)) == 0) {
/* get formatted blocksize from label block */
bsize = (unsigned int) label->block_size;
block->blocks = (unsigned long) label->block_count;
} else
block->blocks = end_block;
block->bp_block = bsize;
block->s2b_shift = 0; /* bits to shift 512 to get a block */
for (sb = 512; sb < bsize; sb = sb << 1)
block->s2b_shift++;
rc = mdsk_init_io(device, block->bp_block, 0, NULL);
if (rc && (rc != 4)) {
pr_warn("%s: DIAG initialization failed with rc=%d\n",
dev_name(&device->cdev->dev), rc);
rc = -EIO;
} else {
if (rc == 4)
set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
pr_info("%s: New DASD with %ld byte/block, total size %ld "
"KB%s\n", dev_name(&device->cdev->dev),
(unsigned long) block->bp_block,
(unsigned long) (block->blocks <<
block->s2b_shift) >> 1,
(rc == 4) ? ", read-only device" : "");
rc = 0;
}
out_bio:
kfree(bio);
out_label:
free_page((long) label);
out:
if (rc) {
device->block = NULL;
dasd_free_block(block);
device->private = NULL;
kfree(private);
}
return rc;
}
/* Fill in virtual disk geometry for device. Return zero on success, non-zero
* otherwise. */
static int
dasd_diag_fill_geometry(struct dasd_block *block, struct hd_geometry *geo)
{
if (dasd_check_blocksize(block->bp_block) != 0)
return -EINVAL;
geo->cylinders = (block->blocks << block->s2b_shift) >> 10;
geo->heads = 16;
geo->sectors = 128 >> block->s2b_shift;
return 0;
}
static dasd_erp_fn_t
dasd_diag_erp_action(struct dasd_ccw_req * cqr)
{
return dasd_default_erp_action;
}
static dasd_erp_fn_t
dasd_diag_erp_postaction(struct dasd_ccw_req * cqr)
{
return dasd_default_erp_postaction;
}
/* Create DASD request from block device request. Return pointer to new
* request on success, ERR_PTR otherwise. */
static struct dasd_ccw_req *dasd_diag_build_cp(struct dasd_device *memdev,
struct dasd_block *block,
struct request *req)
{
struct dasd_ccw_req *cqr;
struct dasd_diag_req *dreq;
struct dasd_diag_bio *dbio;
struct req_iterator iter;
struct bio_vec bv;
char *dst;
unsigned int count;
sector_t recid, first_rec, last_rec;
unsigned int blksize, off;
unsigned char rw_cmd;
if (rq_data_dir(req) == READ)
rw_cmd = MDSK_READ_REQ;
else if (rq_data_dir(req) == WRITE)
rw_cmd = MDSK_WRITE_REQ;
else
return ERR_PTR(-EINVAL);
blksize = block->bp_block;
/* Calculate record id of first and last block. */
first_rec = blk_rq_pos(req) >> block->s2b_shift;
last_rec =
(blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift;
/* Check struct bio and count the number of blocks for the request. */
count = 0;
rq_for_each_segment(bv, req, iter) {
if (bv.bv_len & (blksize - 1))
/* Fba can only do full blocks. */
return ERR_PTR(-EINVAL);
count += bv.bv_len >> (block->s2b_shift + 9);
}
/* Paranoia. */
if (count != last_rec - first_rec + 1)
return ERR_PTR(-EINVAL);
/* Build the request */
cqr = dasd_smalloc_request(DASD_DIAG_MAGIC, 0, struct_size(dreq, bio, count),
memdev, blk_mq_rq_to_pdu(req));
if (IS_ERR(cqr))
return cqr;
dreq = (struct dasd_diag_req *) cqr->data;
dreq->block_count = count;
dbio = dreq->bio;
recid = first_rec;
rq_for_each_segment(bv, req, iter) {
dst = bvec_virt(&bv);
for (off = 0; off < bv.bv_len; off += blksize) {
memset(dbio, 0, sizeof (struct dasd_diag_bio));
dbio->type = rw_cmd;
dbio->block_number = recid + 1;
dbio->buffer = dst;
dbio++;
dst += blksize;
recid++;
}
}
cqr->retries = memdev->default_retries;
cqr->buildclk = get_tod_clock();
if (blk_noretry_request(req) ||
block->base->features & DASD_FEATURE_FAILFAST)
set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
cqr->startdev = memdev;
cqr->memdev = memdev;
cqr->block = block;
cqr->expires = memdev->default_expires * HZ;
cqr->status = DASD_CQR_FILLED;
return cqr;
}
/* Release DASD request. Return non-zero if request was successful, zero
* otherwise. */
static int
dasd_diag_free_cp(struct dasd_ccw_req *cqr, struct request *req)
{
int status;
status = cqr->status == DASD_CQR_DONE;
dasd_sfree_request(cqr, cqr->memdev);
return status;
}
static void dasd_diag_handle_terminated_request(struct dasd_ccw_req *cqr)
{
if (cqr->retries < 0)
cqr->status = DASD_CQR_FAILED;
else
cqr->status = DASD_CQR_FILLED;
};
/* Fill in IOCTL data for device. */
static int
dasd_diag_fill_info(struct dasd_device * device,
struct dasd_information2_t * info)
{
struct dasd_diag_private *private = device->private;
info->label_block = (unsigned int) private->pt_block;
info->FBA_layout = 1;
info->format = DASD_FORMAT_LDL;
info->characteristics_size = sizeof(private->rdc_data);
memcpy(info->characteristics, &private->rdc_data,
sizeof(private->rdc_data));
info->confdata_size = 0;
return 0;
}
static void
dasd_diag_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
struct irb *stat)
{
DBF_DEV_EVENT(DBF_WARNING, device, "%s",
"dump sense not available for DIAG data");
}
static unsigned int dasd_diag_max_sectors(struct dasd_block *block)
{
return DIAG_MAX_BLOCKS << block->s2b_shift;
}
static int dasd_diag_pe_handler(struct dasd_device *device,
__u8 tbvpm, __u8 fcsecpm)
{
return dasd_generic_verify_path(device, tbvpm);
}
static struct dasd_discipline dasd_diag_discipline = {
.owner = THIS_MODULE,
.name = "DIAG",
.ebcname = "DIAG",
.max_sectors = dasd_diag_max_sectors,
.check_device = dasd_diag_check_device,
.pe_handler = dasd_diag_pe_handler,
.fill_geometry = dasd_diag_fill_geometry,
.start_IO = dasd_start_diag,
.term_IO = dasd_diag_term_IO,
.handle_terminated_request = dasd_diag_handle_terminated_request,
.erp_action = dasd_diag_erp_action,
.erp_postaction = dasd_diag_erp_postaction,
.build_cp = dasd_diag_build_cp,
.free_cp = dasd_diag_free_cp,
.dump_sense = dasd_diag_dump_sense,
.fill_info = dasd_diag_fill_info,
};
static int __init
dasd_diag_init(void)
{
if (!machine_is_vm()) {
pr_info("Discipline %s cannot be used without z/VM\n",
dasd_diag_discipline.name);
return -ENODEV;
}
ASCEBC(dasd_diag_discipline.ebcname, 4);
irq_subclass_register(IRQ_SUBCLASS_SERVICE_SIGNAL);
register_external_irq(EXT_IRQ_CP_SERVICE, dasd_ext_handler);
dasd_diag_discipline_pointer = &dasd_diag_discipline;
return 0;
}
static void __exit
dasd_diag_cleanup(void)
{
unregister_external_irq(EXT_IRQ_CP_SERVICE, dasd_ext_handler);
irq_subclass_unregister(IRQ_SUBCLASS_SERVICE_SIGNAL);
dasd_diag_discipline_pointer = NULL;
}
module_init(dasd_diag_init);
module_exit(dasd_diag_cleanup);
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Based on.......: linux/drivers/s390/block/mdisk.h
* ...............: by Hartmunt Penner <hpenner@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* Copyright IBM Corp. 1999, 2000
*
*/
#define MDSK_WRITE_REQ 0x01
#define MDSK_READ_REQ 0x02
#define INIT_BIO 0x00
#define RW_BIO 0x01
#define TERM_BIO 0x02
#define DEV_CLASS_FBA 0x01
#define DEV_CLASS_ECKD 0x04
#define DASD_DIAG_CODE_31BIT 0x03
#define DASD_DIAG_CODE_64BIT 0x07
#define DASD_DIAG_RWFLAG_ASYNC 0x02
#define DASD_DIAG_RWFLAG_NOCACHE 0x01
#define DASD_DIAG_FLAGA_FORMAT_64BIT 0x80
struct dasd_diag_characteristics {
u16 dev_nr;
u16 rdc_len;
u8 vdev_class;
u8 vdev_type;
u8 vdev_status;
u8 vdev_flags;
u8 rdev_class;
u8 rdev_type;
u8 rdev_model;
u8 rdev_features;
} __attribute__ ((packed, aligned(4)));
#define DASD_DIAG_FLAGA_DEFAULT DASD_DIAG_FLAGA_FORMAT_64BIT
typedef u64 blocknum_t;
typedef s64 sblocknum_t;
struct dasd_diag_bio {
u8 type;
u8 status;
u8 spare1[2];
u32 alet;
blocknum_t block_number;
void *buffer;
} __attribute__ ((packed, aligned(8)));
struct dasd_diag_init_io {
u16 dev_nr;
u8 flaga;
u8 spare1[21];
u32 block_size;
u8 spare2[4];
blocknum_t offset;
sblocknum_t start_block;
blocknum_t end_block;
u8 spare3[8];
} __attribute__ ((packed, aligned(8)));
struct dasd_diag_rw_io {
u16 dev_nr;
u8 flaga;
u8 spare1[21];
u8 key;
u8 flags;
u8 spare2[2];
u32 block_count;
u32 alet;
u8 spare3[4];
u64 interrupt_params;
struct dasd_diag_bio *bio_list;
u8 spare4[8];
} __attribute__ ((packed, aligned(8)));
File diff suppressed because it is too large Load Diff
+711
View File
@@ -0,0 +1,711 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* Copyright IBM Corp. 1999, 2000
*
*/
#ifndef DASD_ECKD_H
#define DASD_ECKD_H
/*****************************************************************************
* SECTION: CCW Definitions
****************************************************************************/
#define DASD_ECKD_CCW_NOP 0x03
#define DASD_ECKD_CCW_WRITE 0x05
#define DASD_ECKD_CCW_READ 0x06
#define DASD_ECKD_CCW_WRITE_HOME_ADDRESS 0x09
#define DASD_ECKD_CCW_READ_HOME_ADDRESS 0x0a
#define DASD_ECKD_CCW_WRITE_KD 0x0d
#define DASD_ECKD_CCW_READ_KD 0x0e
#define DASD_ECKD_CCW_ERASE 0x11
#define DASD_ECKD_CCW_READ_COUNT 0x12
#define DASD_ECKD_CCW_SLCK 0x14
#define DASD_ECKD_CCW_WRITE_RECORD_ZERO 0x15
#define DASD_ECKD_CCW_READ_RECORD_ZERO 0x16
#define DASD_ECKD_CCW_WRITE_CKD 0x1d
#define DASD_ECKD_CCW_READ_CKD 0x1e
#define DASD_ECKD_CCW_PSF 0x27
#define DASD_ECKD_CCW_SNID 0x34
#define DASD_ECKD_CCW_RSSD 0x3e
#define DASD_ECKD_CCW_LOCATE_RECORD 0x47
#define DASD_ECKD_CCW_LOCATE_RECORD_EXT 0x4b
#define DASD_ECKD_CCW_SNSS 0x54
#define DASD_ECKD_CCW_DEFINE_EXTENT 0x63
#define DASD_ECKD_CCW_WRITE_MT 0x85
#define DASD_ECKD_CCW_READ_MT 0x86
#define DASD_ECKD_CCW_WRITE_KD_MT 0x8d
#define DASD_ECKD_CCW_READ_KD_MT 0x8e
#define DASD_ECKD_CCW_READ_COUNT_MT 0x92
#define DASD_ECKD_CCW_RELEASE 0x94
#define DASD_ECKD_CCW_WRITE_FULL_TRACK 0x95
#define DASD_ECKD_CCW_READ_CKD_MT 0x9e
#define DASD_ECKD_CCW_WRITE_CKD_MT 0x9d
#define DASD_ECKD_CCW_WRITE_TRACK_DATA 0xA5
#define DASD_ECKD_CCW_READ_TRACK_DATA 0xA6
#define DASD_ECKD_CCW_RESERVE 0xB4
#define DASD_ECKD_CCW_READ_TRACK 0xDE
#define DASD_ECKD_CCW_PFX 0xE7
#define DASD_ECKD_CCW_PFX_READ 0xEA
#define DASD_ECKD_CCW_RSCK 0xF9
#define DASD_ECKD_CCW_RCD 0xFA
#define DASD_ECKD_CCW_DSO 0xF7
/* Define Subsystem Function / Orders */
#define DSO_ORDER_RAS 0x81
/*
* Perform Subsystem Function / Orders
*/
#define PSF_ORDER_PRSSD 0x18
#define PSF_ORDER_CUIR_RESPONSE 0x1A
#define PSF_ORDER_SSC 0x1D
/*
* Perform Subsystem Function / Sub-Orders
*/
#define PSF_SUBORDER_QHA 0x1C /* Query Host Access */
#define PSF_SUBORDER_PPRCEQ 0x50 /* PPRC Extended Query */
#define PSF_SUBORDER_VSQ 0x52 /* Volume Storage Query */
#define PSF_SUBORDER_LCQ 0x53 /* Logical Configuration Query */
/*
* PPRC Extended Query Scopes
*/
#define PPRCEQ_SCOPE_4 0x04 /* Scope 4 for PPRC Extended Query */
/*
* CUIR response condition codes
*/
#define PSF_CUIR_INVALID 0x00
#define PSF_CUIR_COMPLETED 0x01
#define PSF_CUIR_NOT_SUPPORTED 0x02
#define PSF_CUIR_ERROR_IN_REQ 0x03
#define PSF_CUIR_DENIED 0x04
#define PSF_CUIR_LAST_PATH 0x05
#define PSF_CUIR_DEVICE_ONLINE 0x06
#define PSF_CUIR_VARY_FAILURE 0x07
#define PSF_CUIR_SOFTWARE_FAILURE 0x08
#define PSF_CUIR_NOT_RECOGNIZED 0x09
/*
* CUIR codes
*/
#define CUIR_QUIESCE 0x01
#define CUIR_RESUME 0x02
/*
* Out-of-space (OOS) Codes
*/
#define REPO_WARN 0x01
#define REPO_EXHAUST 0x02
#define POOL_WARN 0x03
#define POOL_EXHAUST 0x04
#define REPO_RELIEVE 0x05
#define POOL_RELIEVE 0x06
/*
* attention message definitions
*/
#define ATTENTION_LENGTH_CUIR 0x0e
#define ATTENTION_FORMAT_CUIR 0x01
#define ATTENTION_LENGTH_OOS 0x10
#define ATTENTION_FORMAT_OOS 0x06
#define DASD_ECKD_PG_GROUPED 0x10
/*
* Size that is reported for large volumes in the old 16-bit no_cyl field
*/
#define LV_COMPAT_CYL 0xFFFE
#define FCX_MAX_DATA_FACTOR 65536
#define DASD_ECKD_RCD_DATA_SIZE 256
#define DASD_ECKD_PATH_THRHLD 256
#define DASD_ECKD_PATH_INTERVAL 300
/*
* Maximum number of blocks to be chained
*/
#define DASD_ECKD_MAX_BLOCKS 190
#define DASD_ECKD_MAX_BLOCKS_RAW 256
/*****************************************************************************
* SECTION: Type Definitions
****************************************************************************/
struct eckd_count {
__u16 cyl;
__u16 head;
__u8 record;
__u8 kl;
__u16 dl;
} __attribute__ ((packed));
struct ch_t {
__u16 cyl;
__u16 head;
} __attribute__ ((packed));
struct chr_t {
__u16 cyl;
__u16 head;
__u8 record;
} __attribute__ ((packed));
struct DE_eckd_data {
struct {
unsigned char perm:2; /* Permissions on this extent */
unsigned char reserved:1;
unsigned char seek:2; /* Seek control */
unsigned char auth:2; /* Access authorization */
unsigned char pci:1; /* PCI Fetch mode */
} __attribute__ ((packed)) mask;
struct {
unsigned char mode:2; /* Architecture mode */
unsigned char ckd:1; /* CKD Conversion */
unsigned char operation:3; /* Operation mode */
unsigned char cfw:1; /* Cache fast write */
unsigned char dfw:1; /* DASD fast write */
} __attribute__ ((packed)) attributes;
__u16 blk_size; /* Blocksize */
__u16 fast_write_id;
__u8 ga_additional; /* Global Attributes Additional */
__u8 ga_extended; /* Global Attributes Extended */
struct ch_t beg_ext;
struct ch_t end_ext;
unsigned long ep_sys_time; /* Ext Parameter - System Time Stamp */
__u8 ep_format; /* Extended Parameter format byte */
__u8 ep_prio; /* Extended Parameter priority I/O byte */
__u8 ep_reserved1; /* Extended Parameter Reserved */
__u8 ep_rec_per_track; /* Number of records on a track */
__u8 ep_reserved[4]; /* Extended Parameter Reserved */
} __attribute__ ((packed));
struct LO_eckd_data {
struct {
unsigned char orientation:2;
unsigned char operation:6;
} __attribute__ ((packed)) operation;
struct {
unsigned char last_bytes_used:1;
unsigned char reserved:6;
unsigned char read_count_suffix:1;
} __attribute__ ((packed)) auxiliary;
__u8 unused;
__u8 count;
struct ch_t seek_addr;
struct chr_t search_arg;
__u8 sector;
__u16 length;
} __attribute__ ((packed));
struct LRE_eckd_data {
struct {
unsigned char orientation:2;
unsigned char operation:6;
} __attribute__ ((packed)) operation;
struct {
unsigned char length_valid:1;
unsigned char length_scope:1;
unsigned char imbedded_ccw_valid:1;
unsigned char check_bytes:2;
unsigned char imbedded_count_valid:1;
unsigned char reserved:1;
unsigned char read_count_suffix:1;
} __attribute__ ((packed)) auxiliary;
__u8 imbedded_ccw;
__u8 count;
struct ch_t seek_addr;
struct chr_t search_arg;
__u8 sector;
__u16 length;
__u8 imbedded_count;
__u8 extended_operation;
__u16 extended_parameter_length;
__u8 extended_parameter[];
} __attribute__ ((packed));
/* Prefix data for format 0x00 and 0x01 */
struct PFX_eckd_data {
unsigned char format;
struct {
unsigned char define_extent:1;
unsigned char time_stamp:1;
unsigned char verify_base:1;
unsigned char hyper_pav:1;
unsigned char reserved:4;
} __attribute__ ((packed)) validity;
__u8 base_address;
__u8 aux;
__u8 base_lss;
__u8 reserved[7];
struct DE_eckd_data define_extent;
struct LRE_eckd_data locate_record;
} __attribute__ ((packed));
struct dasd_eckd_characteristics {
__u16 cu_type;
struct {
unsigned char support:2;
unsigned char async:1;
unsigned char reserved:1;
unsigned char cache_info:1;
unsigned char model:3;
} __attribute__ ((packed)) cu_model;
__u16 dev_type;
__u8 dev_model;
struct {
unsigned char mult_burst:1;
unsigned char RT_in_LR:1;
unsigned char reserved1:1;
unsigned char RD_IN_LR:1;
unsigned char reserved2:4;
unsigned char reserved3:8;
unsigned char defect_wr:1;
unsigned char XRC_supported:1;
unsigned char PPRC_enabled:1;
unsigned char striping:1;
unsigned char reserved5:4;
unsigned char cfw:1;
unsigned char reserved6:2;
unsigned char cache:1;
unsigned char dual_copy:1;
unsigned char dfw:1;
unsigned char reset_alleg:1;
unsigned char sense_down:1;
} __attribute__ ((packed)) facilities;
__u8 dev_class;
__u8 unit_type;
__u16 no_cyl;
__u16 trk_per_cyl;
__u8 sec_per_trk;
__u8 byte_per_track[3];
__u16 home_bytes;
__u8 formula;
union {
struct {
__u8 f1;
__u16 f2;
__u16 f3;
} __attribute__ ((packed)) f_0x01;
struct {
__u8 f1;
__u8 f2;
__u8 f3;
__u8 f4;
__u8 f5;
} __attribute__ ((packed)) f_0x02;
} __attribute__ ((packed)) factors;
__u16 first_alt_trk;
__u16 no_alt_trk;
__u16 first_dia_trk;
__u16 no_dia_trk;
__u16 first_sup_trk;
__u16 no_sup_trk;
__u8 MDR_ID;
__u8 OBR_ID;
__u8 director;
__u8 rd_trk_set;
__u16 max_rec_zero;
__u8 reserved1;
__u8 RWANY_in_LR;
__u8 factor6;
__u8 factor7;
__u8 factor8;
__u8 reserved2[3];
__u8 reserved3[6];
__u32 long_no_cyl;
} __attribute__ ((packed));
/* elements of the configuration data */
struct dasd_ned {
struct {
__u8 identifier:2;
__u8 token_id:1;
__u8 sno_valid:1;
__u8 subst_sno:1;
__u8 recNED:1;
__u8 emuNED:1;
__u8 reserved:1;
} __attribute__ ((packed)) flags;
__u8 descriptor;
__u8 dev_class;
__u8 reserved;
__u8 dev_type[6];
__u8 dev_model[3];
__u8 HDA_manufacturer[3];
struct {
__u8 HDA_location[2];
__u8 HDA_seqno[12];
} serial;
__u8 ID;
__u8 unit_addr;
} __attribute__ ((packed));
struct dasd_sneq {
struct {
__u8 identifier:2;
__u8 reserved:6;
} __attribute__ ((packed)) flags;
__u8 res1;
__u16 format;
__u8 res2[4]; /* byte 4- 7 */
__u8 sua_flags; /* byte 8 */
__u8 base_unit_addr; /* byte 9 */
__u8 res3[22]; /* byte 10-31 */
} __attribute__ ((packed));
struct vd_sneq {
struct {
__u8 identifier:2;
__u8 reserved:6;
} __attribute__ ((packed)) flags;
__u8 res1;
__u16 format;
__u8 res2[4]; /* byte 4- 7 */
__u8 uit[16]; /* byte 8-23 */
__u8 res3[8]; /* byte 24-31 */
} __attribute__ ((packed));
struct dasd_gneq {
struct {
__u8 identifier:2;
__u8 reserved:6;
} __attribute__ ((packed)) flags;
__u8 record_selector;
__u8 reserved[4];
struct {
__u8 value:2;
__u8 number:6;
} __attribute__ ((packed)) timeout;
__u8 reserved3;
__u16 subsystemID;
__u8 reserved2[22];
} __attribute__ ((packed));
struct dasd_rssd_features {
char feature[256];
} __attribute__((packed));
struct dasd_rssd_messages {
__u16 length;
__u8 format;
__u8 code;
__u32 message_id;
__u8 flags;
char messages[4087];
} __packed;
/*
* Read Subsystem Data - Volume Storage Query
*/
struct dasd_rssd_vsq {
struct {
__u8 tse:1;
__u8 space_not_available:1;
__u8 ese:1;
__u8 unused:5;
} __packed vol_info;
__u8 unused1;
__u16 extent_pool_id;
__u8 warn_cap_limit;
__u8 warn_cap_guaranteed;
__u16 unused2;
__u32 limit_capacity;
__u32 guaranteed_capacity;
__u32 space_allocated;
__u32 space_configured;
__u32 logical_capacity;
} __packed;
/*
* Extent Pool Summary
*/
struct dasd_ext_pool_sum {
__u16 pool_id;
__u8 repo_warn_thrshld;
__u8 warn_thrshld;
struct {
__u8 type:1; /* 0 - CKD / 1 - FB */
__u8 track_space_efficient:1;
__u8 extent_space_efficient:1;
__u8 standard_volume:1;
__u8 extent_size_valid:1;
__u8 capacity_at_warnlevel:1;
__u8 pool_oos:1;
__u8 unused0:1;
__u8 unused1;
} __packed flags;
struct {
__u8 reserved0:1;
__u8 size_1G:1;
__u8 reserved1:5;
__u8 size_16M:1;
} __packed extent_size;
__u8 unused;
} __packed;
/*
* Read Subsystem Data-Response - Logical Configuration Query - Header
*/
struct dasd_rssd_lcq {
__u16 data_length; /* Length of data returned */
__u16 pool_count; /* Count of extent pools returned - Max: 448 */
struct {
__u8 pool_info_valid:1; /* Detailed Information valid */
__u8 pool_id_volume:1;
__u8 pool_id_cec:1;
__u8 unused0:5;
__u8 unused1;
} __packed header_flags;
char sfi_type[6]; /* Storage Facility Image Type (EBCDIC) */
char sfi_model[3]; /* Storage Facility Image Model (EBCDIC) */
__u8 sfi_seq_num[10]; /* Storage Facility Image Sequence Number */
__u8 reserved[7];
struct dasd_ext_pool_sum ext_pool_sum[448];
} __packed;
struct dasd_oos_message {
__u16 length;
__u8 format;
__u8 code;
__u8 percentage_empty;
__u8 reserved;
__u16 ext_pool_id;
__u16 token;
__u8 unused[6];
} __packed;
struct dasd_cuir_message {
__u16 length;
__u8 format;
__u8 code;
__u32 message_id;
__u8 flags;
__u8 neq_map[3];
__u8 ned_map;
__u8 record_selector;
} __packed;
struct dasd_psf_cuir_response {
__u8 order;
__u8 flags;
__u8 cc;
__u8 chpid;
__u16 device_nr;
__u16 reserved;
__u32 message_id;
__u64 system_id;
__u8 cssid;
__u8 ssid;
} __packed;
struct dasd_ckd_path_group_entry {
__u8 status_flags;
__u8 pgid[11];
__u8 sysplex_name[8];
__u32 timestamp;
__u32 cylinder;
__u8 reserved[4];
} __packed;
struct dasd_ckd_host_information {
__u8 access_flags;
__u8 entry_size;
__u16 entry_count;
__u8 entry[16390];
} __packed;
struct dasd_psf_query_host_access {
__u8 access_flag;
__u8 version;
__u16 CKD_length;
__u16 SCSI_length;
__u8 unused[10];
__u8 host_access_information[16394];
} __packed;
/*
* Perform Subsystem Function - Prepare for Read Subsystem Data
*/
struct dasd_psf_prssd_data {
unsigned char order;
unsigned char flags;
unsigned char reserved1;
unsigned char reserved2;
unsigned char lss;
unsigned char volume;
unsigned char suborder;
unsigned char varies[5];
} __attribute__ ((packed));
/*
* Perform Subsystem Function - Set Subsystem Characteristics
*/
struct dasd_psf_ssc_data {
unsigned char order;
unsigned char flags;
unsigned char cu_type[4];
unsigned char suborder;
unsigned char reserved[59];
} __attribute__((packed));
/* Maximum number of extents for a single Release Allocated Space command */
#define DASD_ECKD_RAS_EXTS_MAX 110U
struct dasd_dso_ras_ext_range {
struct ch_t beg_ext;
struct ch_t end_ext;
} __packed;
/*
* Define Subsystem Operation - Release Allocated Space
*/
struct dasd_dso_ras_data {
__u8 order;
struct {
__u8 message:1; /* Must be zero */
__u8 reserved1:2;
__u8 vol_type:1; /* 0 - CKD/FBA, 1 - FB */
__u8 reserved2:4;
} __packed flags;
/* Operation Flags to specify scope */
struct {
__u8 reserved1:2;
/* Release Space by Extent */
__u8 by_extent:1; /* 0 - entire volume, 1 - specified extents */
__u8 guarantee_init:1;
__u8 force_release:1; /* Internal - will be ignored */
__u16 reserved2:11;
} __packed op_flags;
__u8 lss;
__u8 dev_addr;
__u32 reserved1;
__u8 reserved2[10];
__u16 nr_exts; /* Defines number of ext_scope - max 110 */
__u16 reserved3;
} __packed;
/*
* some structures and definitions for alias handling
*/
struct dasd_unit_address_configuration {
struct {
char ua_type;
char base_ua;
} unit[256];
} __attribute__((packed));
#define MAX_DEVICES_PER_LCU 256
/* flags on the LCU */
#define NEED_UAC_UPDATE 0x01
#define UPDATE_PENDING 0x02
enum pavtype {NO_PAV, BASE_PAV, HYPER_PAV};
struct alias_root {
struct list_head serverlist;
spinlock_t lock;
};
struct alias_server {
struct list_head server;
struct dasd_uid uid;
struct list_head lculist;
};
struct summary_unit_check_work_data {
char reason;
struct dasd_device *device;
struct work_struct worker;
};
struct read_uac_work_data {
struct dasd_device *device;
struct delayed_work dwork;
};
struct alias_lcu {
struct list_head lcu;
struct dasd_uid uid;
enum pavtype pav;
char flags;
spinlock_t lock;
struct list_head grouplist;
struct list_head active_devices;
struct list_head inactive_devices;
struct dasd_unit_address_configuration *uac;
struct summary_unit_check_work_data suc_data;
struct read_uac_work_data ruac_data;
struct dasd_ccw_req *rsu_cqr;
struct completion lcu_setup;
};
struct alias_pav_group {
struct list_head group;
struct dasd_uid uid;
struct alias_lcu *lcu;
struct list_head baselist;
struct list_head aliaslist;
struct dasd_device *next;
};
struct dasd_conf_data {
struct dasd_ned neds[5];
u8 reserved[64];
struct dasd_gneq gneq;
} __packed;
struct dasd_conf {
u8 *data;
int len;
/* pointers to specific parts in the conf_data */
struct dasd_ned *ned;
struct dasd_sneq *sneq;
struct vd_sneq *vdsneq;
struct dasd_gneq *gneq;
};
struct dasd_eckd_private {
struct dasd_eckd_characteristics rdc_data;
struct dasd_conf conf;
struct eckd_count count_area[5];
int init_cqr_status;
int uses_cdl;
struct attrib_data_t attrib; /* e.g. cache operations */
struct dasd_rssd_features features;
struct dasd_rssd_vsq vsq;
struct dasd_ext_pool_sum eps;
u32 real_cyl;
/* alias management */
struct dasd_uid uid;
struct alias_pav_group *pavgroup;
struct alias_lcu *lcu;
int count;
u32 fcx_max_data;
char suc_reason;
};
int dasd_alias_make_device_known_to_lcu(struct dasd_device *);
void dasd_alias_disconnect_device_from_lcu(struct dasd_device *);
int dasd_alias_add_device(struct dasd_device *);
int dasd_alias_remove_device(struct dasd_device *);
struct dasd_device *dasd_alias_get_start_dev(struct dasd_device *);
void dasd_alias_handle_summary_unit_check(struct work_struct *);
void dasd_eckd_reset_ccw_to_base_io(struct dasd_ccw_req *);
int dasd_alias_update_add_device(struct dasd_device *);
#endif /* DASD_ECKD_H */
+719
View File
@@ -0,0 +1,719 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Character device driver for extended error reporting.
*
* Copyright IBM Corp. 2005
* extended error reporting for DASD ECKD devices
* Author(s): Stefan Weinhuber <wein@de.ibm.com>
*/
#include <linux/export.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <asm/ebcdic.h>
#include "dasd_int.h"
#include "dasd_eckd.h"
/*
* SECTION: the internal buffer
*/
/*
* The internal buffer is meant to store obaque blobs of data, so it does
* not know of higher level concepts like triggers.
* It consists of a number of pages that are used as a ringbuffer. Each data
* blob is stored in a simple record that consists of an integer, which
* contains the size of the following data, and the data bytes themselfes.
*
* To allow for multiple independent readers we create one internal buffer
* each time the device is opened and destroy the buffer when the file is
* closed again. The number of pages used for this buffer is determined by
* the module parmeter eer_pages.
*
* One record can be written to a buffer by using the functions
* - dasd_eer_start_record (one time per record to write the size to the
* buffer and reserve the space for the data)
* - dasd_eer_write_buffer (one or more times per record to write the data)
* The data can be written in several steps but you will have to compute
* the total size up front for the invocation of dasd_eer_start_record.
* If the ringbuffer is full, dasd_eer_start_record will remove the required
* number of old records.
*
* A record is typically read in two steps, first read the integer that
* specifies the size of the following data, then read the data.
* Both can be done by
* - dasd_eer_read_buffer
*
* For all mentioned functions you need to get the bufferlock first and keep
* it until a complete record is written or read.
*
* All information necessary to keep track of an internal buffer is kept in
* a struct eerbuffer. The buffer specific to a file pointer is strored in
* the private_data field of that file. To be able to write data to all
* existing buffers, each buffer is also added to the bufferlist.
* If the user does not want to read a complete record in one go, we have to
* keep track of the rest of the record. residual stores the number of bytes
* that are still to deliver. If the rest of the record is invalidated between
* two reads then residual will be set to -1 so that the next read will fail.
* All entries in the eerbuffer structure are protected with the bufferlock.
* To avoid races between writing to a buffer on the one side and creating
* and destroying buffers on the other side, the bufferlock must also be used
* to protect the bufferlist.
*/
static int eer_pages = 5;
module_param(eer_pages, int, S_IRUGO|S_IWUSR);
struct eerbuffer {
struct list_head list;
char **buffer;
int buffersize;
int buffer_page_count;
int head;
int tail;
int residual;
};
static LIST_HEAD(bufferlist);
static DEFINE_SPINLOCK(bufferlock);
static DECLARE_WAIT_QUEUE_HEAD(dasd_eer_read_wait_queue);
/*
* How many free bytes are available on the buffer.
* Needs to be called with bufferlock held.
*/
static int dasd_eer_get_free_bytes(struct eerbuffer *eerb)
{
if (eerb->head < eerb->tail)
return eerb->tail - eerb->head - 1;
return eerb->buffersize - eerb->head + eerb->tail -1;
}
/*
* How many bytes of buffer space are used.
* Needs to be called with bufferlock held.
*/
static int dasd_eer_get_filled_bytes(struct eerbuffer *eerb)
{
if (eerb->head >= eerb->tail)
return eerb->head - eerb->tail;
return eerb->buffersize - eerb->tail + eerb->head;
}
/*
* The dasd_eer_write_buffer function just copies count bytes of data
* to the buffer. Make sure to call dasd_eer_start_record first, to
* make sure that enough free space is available.
* Needs to be called with bufferlock held.
*/
static void dasd_eer_write_buffer(struct eerbuffer *eerb,
char *data, int count)
{
unsigned long headindex,localhead;
unsigned long rest, len;
char *nextdata;
nextdata = data;
rest = count;
while (rest > 0) {
headindex = eerb->head / PAGE_SIZE;
localhead = eerb->head % PAGE_SIZE;
len = min(rest, PAGE_SIZE - localhead);
memcpy(eerb->buffer[headindex]+localhead, nextdata, len);
nextdata += len;
rest -= len;
eerb->head += len;
if (eerb->head == eerb->buffersize)
eerb->head = 0; /* wrap around */
BUG_ON(eerb->head > eerb->buffersize);
}
}
/*
* Needs to be called with bufferlock held.
*/
static int dasd_eer_read_buffer(struct eerbuffer *eerb, char *data, int count)
{
unsigned long tailindex,localtail;
unsigned long rest, len, finalcount;
char *nextdata;
finalcount = min(count, dasd_eer_get_filled_bytes(eerb));
nextdata = data;
rest = finalcount;
while (rest > 0) {
tailindex = eerb->tail / PAGE_SIZE;
localtail = eerb->tail % PAGE_SIZE;
len = min(rest, PAGE_SIZE - localtail);
memcpy(nextdata, eerb->buffer[tailindex] + localtail, len);
nextdata += len;
rest -= len;
eerb->tail += len;
if (eerb->tail == eerb->buffersize)
eerb->tail = 0; /* wrap around */
BUG_ON(eerb->tail > eerb->buffersize);
}
return finalcount;
}
/*
* Whenever you want to write a blob of data to the internal buffer you
* have to start by using this function first. It will write the number
* of bytes that will be written to the buffer. If necessary it will remove
* old records to make room for the new one.
* Needs to be called with bufferlock held.
*/
static int dasd_eer_start_record(struct eerbuffer *eerb, int count)
{
int tailcount;
if (count + sizeof(count) > eerb->buffersize)
return -ENOMEM;
while (dasd_eer_get_free_bytes(eerb) < count + sizeof(count)) {
if (eerb->residual > 0) {
eerb->tail += eerb->residual;
if (eerb->tail >= eerb->buffersize)
eerb->tail -= eerb->buffersize;
eerb->residual = -1;
}
dasd_eer_read_buffer(eerb, (char *) &tailcount,
sizeof(tailcount));
eerb->tail += tailcount;
if (eerb->tail >= eerb->buffersize)
eerb->tail -= eerb->buffersize;
}
dasd_eer_write_buffer(eerb, (char*) &count, sizeof(count));
return 0;
};
/*
* Release pages that are not used anymore.
*/
static void dasd_eer_free_buffer_pages(char **buf, int no_pages)
{
int i;
for (i = 0; i < no_pages; i++)
kfree(buf[i]);
}
/*
* Allocate a new set of memory pages.
*/
static int dasd_eer_allocate_buffer_pages(char **buf, int no_pages)
{
int i;
for (i = 0; i < no_pages; i++) {
buf[i] = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf[i]) {
dasd_eer_free_buffer_pages(buf, i);
return -ENOMEM;
}
}
return 0;
}
/*
* SECTION: The extended error reporting functionality
*/
/*
* When a DASD device driver wants to report an error, it calls the
* function dasd_eer_write and gives the respective trigger ID as
* parameter. Currently there are four kinds of triggers:
*
* DASD_EER_FATALERROR: all kinds of unrecoverable I/O problems
* DASD_EER_PPRCSUSPEND: PPRC was suspended
* DASD_EER_NOPATH: There is no path to the device left.
* DASD_EER_STATECHANGE: The state of the device has changed.
*
* For the first three triggers all required information can be supplied by
* the caller. For these triggers a record is written by the function
* dasd_eer_write_standard_trigger.
*
* The DASD_EER_STATECHANGE trigger is special since a sense subsystem
* status ccw need to be executed to gather the necessary sense data first.
* The dasd_eer_snss function will queue the SNSS request and the request
* callback will then call dasd_eer_write with the DASD_EER_STATCHANGE
* trigger.
*
* To avoid memory allocations at runtime, the necessary memory is allocated
* when the extended error reporting is enabled for a device (by
* dasd_eer_probe). There is one sense subsystem status request for each
* eer enabled DASD device. The presence of the cqr in device->eer_cqr
* indicates that eer is enable for the device. The use of the snss request
* is protected by the DASD_FLAG_EER_IN_USE bit. When this flag indicates
* that the cqr is currently in use, dasd_eer_snss cannot start a second
* request but sets the DASD_FLAG_EER_SNSS flag instead. The callback of
* the SNSS request will check the bit and call dasd_eer_snss again.
*/
#define SNSS_DATA_SIZE 44
#define DASD_EER_BUSID_SIZE 10
struct dasd_eer_header {
__u32 total_size;
__u32 trigger;
__u64 tv_sec;
__u64 tv_usec;
char busid[DASD_EER_BUSID_SIZE];
} __attribute__ ((packed));
/*
* The following function can be used for those triggers that have
* all necessary data available when the function is called.
* If the parameter cqr is not NULL, the chain of requests will be searched
* for valid sense data, and all valid sense data sets will be added to
* the triggers data.
*/
static void dasd_eer_write_standard_trigger(struct dasd_device *device,
struct dasd_ccw_req *cqr,
int trigger)
{
struct dasd_ccw_req *temp_cqr;
int data_size;
struct timespec64 ts;
struct dasd_eer_header header;
unsigned long flags;
struct eerbuffer *eerb;
char *sense;
/* go through cqr chain and count the valid sense data sets */
data_size = 0;
for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers)
if (dasd_get_sense(&temp_cqr->irb))
data_size += 32;
header.total_size = sizeof(header) + data_size + 4; /* "EOR" */
header.trigger = trigger;
ktime_get_real_ts64(&ts);
header.tv_sec = ts.tv_sec;
header.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
strscpy(header.busid, dev_name(&device->cdev->dev),
DASD_EER_BUSID_SIZE);
spin_lock_irqsave(&bufferlock, flags);
list_for_each_entry(eerb, &bufferlist, list) {
dasd_eer_start_record(eerb, header.total_size);
dasd_eer_write_buffer(eerb, (char *) &header, sizeof(header));
for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers) {
sense = dasd_get_sense(&temp_cqr->irb);
if (sense)
dasd_eer_write_buffer(eerb, sense, 32);
}
dasd_eer_write_buffer(eerb, "EOR", 4);
}
spin_unlock_irqrestore(&bufferlock, flags);
wake_up_interruptible(&dasd_eer_read_wait_queue);
}
/*
* This function writes a DASD_EER_STATECHANGE trigger.
*/
static void dasd_eer_write_snss_trigger(struct dasd_device *device,
struct dasd_ccw_req *cqr,
int trigger)
{
int data_size;
int snss_rc;
struct timespec64 ts;
struct dasd_eer_header header;
unsigned long flags;
struct eerbuffer *eerb;
snss_rc = (cqr->status == DASD_CQR_DONE) ? 0 : -EIO;
if (snss_rc)
data_size = 0;
else
data_size = SNSS_DATA_SIZE;
header.total_size = sizeof(header) + data_size + 4; /* "EOR" */
header.trigger = DASD_EER_STATECHANGE;
ktime_get_real_ts64(&ts);
header.tv_sec = ts.tv_sec;
header.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
strscpy(header.busid, dev_name(&device->cdev->dev),
DASD_EER_BUSID_SIZE);
spin_lock_irqsave(&bufferlock, flags);
list_for_each_entry(eerb, &bufferlist, list) {
dasd_eer_start_record(eerb, header.total_size);
dasd_eer_write_buffer(eerb, (char *) &header , sizeof(header));
if (!snss_rc)
dasd_eer_write_buffer(eerb, cqr->data, SNSS_DATA_SIZE);
dasd_eer_write_buffer(eerb, "EOR", 4);
}
spin_unlock_irqrestore(&bufferlock, flags);
wake_up_interruptible(&dasd_eer_read_wait_queue);
}
/*
* This function is called for all triggers. It calls the appropriate
* function that writes the actual trigger records.
*/
void dasd_eer_write(struct dasd_device *device, struct dasd_ccw_req *cqr,
unsigned int id)
{
if (!device->eer_cqr)
return;
switch (id) {
case DASD_EER_FATALERROR:
case DASD_EER_PPRCSUSPEND:
dasd_eer_write_standard_trigger(device, cqr, id);
break;
case DASD_EER_NOPATH:
case DASD_EER_NOSPC:
case DASD_EER_AUTOQUIESCE:
dasd_eer_write_standard_trigger(device, NULL, id);
break;
case DASD_EER_STATECHANGE:
dasd_eer_write_snss_trigger(device, cqr, id);
break;
default: /* unknown trigger, so we write it without any sense data */
dasd_eer_write_standard_trigger(device, NULL, id);
break;
}
}
EXPORT_SYMBOL(dasd_eer_write);
/*
* Start a sense subsystem status request.
* Needs to be called with the device held.
*/
void dasd_eer_snss(struct dasd_device *device)
{
struct dasd_ccw_req *cqr;
cqr = device->eer_cqr;
if (!cqr) /* Device not eer enabled. */
return;
if (test_and_set_bit(DASD_FLAG_EER_IN_USE, &device->flags)) {
/* Sense subsystem status request in use. */
set_bit(DASD_FLAG_EER_SNSS, &device->flags);
return;
}
/* cdev is already locked, can't use dasd_add_request_head */
clear_bit(DASD_FLAG_EER_SNSS, &device->flags);
cqr->status = DASD_CQR_QUEUED;
list_add(&cqr->devlist, &device->ccw_queue);
dasd_schedule_device_bh(device);
}
/*
* Callback function for use with sense subsystem status request.
*/
static void dasd_eer_snss_cb(struct dasd_ccw_req *cqr, void *data)
{
struct dasd_device *device = cqr->startdev;
unsigned long flags;
dasd_eer_write(device, cqr, DASD_EER_STATECHANGE);
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
if (device->eer_cqr == cqr) {
clear_bit(DASD_FLAG_EER_IN_USE, &device->flags);
if (test_bit(DASD_FLAG_EER_SNSS, &device->flags))
/* Another SNSS has been requested in the meantime. */
dasd_eer_snss(device);
cqr = NULL;
}
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
if (cqr)
/*
* Extended error recovery has been switched off while
* the SNSS request was running. It could even have
* been switched off and on again in which case there
* is a new ccw in device->eer_cqr. Free the "old"
* snss request now.
*/
dasd_sfree_request(cqr, device);
}
/*
* Enable error reporting on a given device.
*/
int dasd_eer_enable(struct dasd_device *device)
{
struct dasd_ccw_req *cqr = NULL;
unsigned long flags;
struct ccw1 *ccw;
int rc = 0;
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
if (device->eer_cqr)
goto out;
else if (!device->discipline ||
strcmp(device->discipline->name, "ECKD"))
rc = -EMEDIUMTYPE;
else if (test_bit(DASD_FLAG_OFFLINE, &device->flags))
rc = -EBUSY;
if (rc)
goto out;
cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* SNSS */,
SNSS_DATA_SIZE, device, NULL);
if (IS_ERR(cqr)) {
rc = -ENOMEM;
cqr = NULL;
goto out;
}
cqr->startdev = device;
cqr->retries = 255;
cqr->expires = 10 * HZ;
clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
set_bit(DASD_CQR_ALLOW_SLOCK, &cqr->flags);
ccw = cqr->cpaddr;
ccw->cmd_code = DASD_ECKD_CCW_SNSS;
ccw->count = SNSS_DATA_SIZE;
ccw->flags = 0;
ccw->cda = virt_to_dma32(cqr->data);
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
cqr->callback = dasd_eer_snss_cb;
if (!device->eer_cqr) {
device->eer_cqr = cqr;
cqr = NULL;
}
out:
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
if (cqr)
dasd_sfree_request(cqr, device);
return rc;
}
/*
* Disable error reporting on a given device.
*/
void dasd_eer_disable(struct dasd_device *device)
{
struct dasd_ccw_req *cqr;
unsigned long flags;
int in_use;
if (!device->eer_cqr)
return;
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
cqr = device->eer_cqr;
device->eer_cqr = NULL;
clear_bit(DASD_FLAG_EER_SNSS, &device->flags);
in_use = test_and_clear_bit(DASD_FLAG_EER_IN_USE, &device->flags);
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
if (cqr && !in_use)
dasd_sfree_request(cqr, device);
}
/*
* SECTION: the device operations
*/
/*
* On the one side we need a lock to access our internal buffer, on the
* other side a copy_to_user can sleep. So we need to copy the data we have
* to transfer in a readbuffer, which is protected by the readbuffer_mutex.
*/
static char readbuffer[PAGE_SIZE];
static DEFINE_MUTEX(readbuffer_mutex);
static int dasd_eer_open(struct inode *inp, struct file *filp)
{
struct eerbuffer *eerb;
unsigned long flags;
eerb = kzalloc_obj(struct eerbuffer);
if (!eerb)
return -ENOMEM;
eerb->buffer_page_count = eer_pages;
if (eerb->buffer_page_count < 1 ||
eerb->buffer_page_count > INT_MAX / PAGE_SIZE) {
kfree(eerb);
DBF_EVENT(DBF_WARNING, "can't open device since module "
"parameter eer_pages is smaller than 1 or"
" bigger than %d", (int)(INT_MAX / PAGE_SIZE));
return -EINVAL;
}
eerb->buffersize = eerb->buffer_page_count * PAGE_SIZE;
eerb->buffer = kmalloc_array(eerb->buffer_page_count, sizeof(char *),
GFP_KERNEL);
if (!eerb->buffer) {
kfree(eerb);
return -ENOMEM;
}
if (dasd_eer_allocate_buffer_pages(eerb->buffer,
eerb->buffer_page_count)) {
kfree(eerb->buffer);
kfree(eerb);
return -ENOMEM;
}
filp->private_data = eerb;
spin_lock_irqsave(&bufferlock, flags);
list_add(&eerb->list, &bufferlist);
spin_unlock_irqrestore(&bufferlock, flags);
return nonseekable_open(inp,filp);
}
static int dasd_eer_close(struct inode *inp, struct file *filp)
{
struct eerbuffer *eerb;
unsigned long flags;
eerb = (struct eerbuffer *) filp->private_data;
spin_lock_irqsave(&bufferlock, flags);
list_del(&eerb->list);
spin_unlock_irqrestore(&bufferlock, flags);
dasd_eer_free_buffer_pages(eerb->buffer, eerb->buffer_page_count);
kfree(eerb->buffer);
kfree(eerb);
return 0;
}
static ssize_t dasd_eer_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
int tc,rc;
int tailcount,effective_count;
unsigned long flags;
struct eerbuffer *eerb;
eerb = (struct eerbuffer *) filp->private_data;
if (mutex_lock_interruptible(&readbuffer_mutex))
return -ERESTARTSYS;
spin_lock_irqsave(&bufferlock, flags);
if (eerb->residual < 0) { /* the remainder of this record */
/* has been deleted */
eerb->residual = 0;
spin_unlock_irqrestore(&bufferlock, flags);
mutex_unlock(&readbuffer_mutex);
return -EIO;
} else if (eerb->residual > 0) {
/* OK we still have a second half of a record to deliver */
effective_count = min(eerb->residual, (int) count);
eerb->residual -= effective_count;
} else {
tc = 0;
while (!tc) {
tc = dasd_eer_read_buffer(eerb, (char *) &tailcount,
sizeof(tailcount));
if (!tc) {
/* no data available */
spin_unlock_irqrestore(&bufferlock, flags);
mutex_unlock(&readbuffer_mutex);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
rc = wait_event_interruptible(
dasd_eer_read_wait_queue,
eerb->head != eerb->tail);
if (rc)
return rc;
if (mutex_lock_interruptible(&readbuffer_mutex))
return -ERESTARTSYS;
spin_lock_irqsave(&bufferlock, flags);
}
}
WARN_ON(tc != sizeof(tailcount));
effective_count = min(tailcount,(int)count);
eerb->residual = tailcount - effective_count;
}
tc = dasd_eer_read_buffer(eerb, readbuffer, effective_count);
WARN_ON(tc != effective_count);
spin_unlock_irqrestore(&bufferlock, flags);
if (copy_to_user(buf, readbuffer, effective_count)) {
mutex_unlock(&readbuffer_mutex);
return -EFAULT;
}
mutex_unlock(&readbuffer_mutex);
return effective_count;
}
static __poll_t dasd_eer_poll(struct file *filp, poll_table *ptable)
{
__poll_t mask;
unsigned long flags;
struct eerbuffer *eerb;
eerb = (struct eerbuffer *) filp->private_data;
poll_wait(filp, &dasd_eer_read_wait_queue, ptable);
spin_lock_irqsave(&bufferlock, flags);
if (eerb->head != eerb->tail)
mask = EPOLLIN | EPOLLRDNORM ;
else
mask = 0;
spin_unlock_irqrestore(&bufferlock, flags);
return mask;
}
static const struct file_operations dasd_eer_fops = {
.open = &dasd_eer_open,
.release = &dasd_eer_close,
.read = &dasd_eer_read,
.poll = &dasd_eer_poll,
.owner = THIS_MODULE,
.llseek = noop_llseek,
};
static struct miscdevice *dasd_eer_dev = NULL;
int __init dasd_eer_init(void)
{
int rc;
dasd_eer_dev = kzalloc_obj(*dasd_eer_dev);
if (!dasd_eer_dev)
return -ENOMEM;
dasd_eer_dev->minor = MISC_DYNAMIC_MINOR;
dasd_eer_dev->name = "dasd_eer";
dasd_eer_dev->fops = &dasd_eer_fops;
rc = misc_register(dasd_eer_dev);
if (rc) {
kfree(dasd_eer_dev);
dasd_eer_dev = NULL;
DBF_EVENT(DBF_ERR, "%s", "dasd_eer_init could not "
"register misc device");
return rc;
}
return 0;
}
void dasd_eer_exit(void)
{
if (dasd_eer_dev) {
misc_deregister(dasd_eer_dev);
kfree(dasd_eer_dev);
dasd_eer_dev = NULL;
}
}
+199
View File
@@ -0,0 +1,199 @@
// 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
*
*/
#include <linux/export.h>
#include <linux/ctype.h>
#include <linux/init.h>
#include <asm/debug.h>
#include <asm/ebcdic.h>
#include <linux/uaccess.h>
#include "dasd_int.h"
struct dasd_ccw_req *
dasd_alloc_erp_request(unsigned int magic, int cplength, int datasize,
struct dasd_device * device)
{
unsigned long flags;
struct dasd_ccw_req *cqr;
char *data;
int size;
/* Sanity checks */
BUG_ON(datasize > PAGE_SIZE ||
(cplength*sizeof(struct ccw1)) > PAGE_SIZE);
size = (sizeof(struct dasd_ccw_req) + 7L) & -8L;
if (cplength > 0)
size += cplength * sizeof(struct ccw1);
if (datasize > 0)
size += datasize;
spin_lock_irqsave(&device->mem_lock, flags);
cqr = (struct dasd_ccw_req *)
dasd_alloc_chunk(&device->erp_chunks, size);
spin_unlock_irqrestore(&device->mem_lock, flags);
if (cqr == NULL)
return ERR_PTR(-ENOMEM);
memset(cqr, 0, sizeof(struct dasd_ccw_req));
INIT_LIST_HEAD(&cqr->devlist);
INIT_LIST_HEAD(&cqr->blocklist);
data = (char *) cqr + ((sizeof(struct dasd_ccw_req) + 7L) & -8L);
cqr->cpaddr = NULL;
if (cplength > 0) {
cqr->cpaddr = (struct ccw1 *) data;
data += cplength*sizeof(struct ccw1);
memset(cqr->cpaddr, 0, cplength*sizeof(struct ccw1));
}
cqr->data = NULL;
if (datasize > 0) {
cqr->data = data;
memset(cqr->data, 0, datasize);
}
cqr->magic = magic;
ASCEBC((char *) &cqr->magic, 4);
set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
dasd_get_device(device);
return cqr;
}
void
dasd_free_erp_request(struct dasd_ccw_req *cqr, struct dasd_device * device)
{
unsigned long flags;
spin_lock_irqsave(&device->mem_lock, flags);
dasd_free_chunk(&device->erp_chunks, cqr);
spin_unlock_irqrestore(&device->mem_lock, flags);
atomic_dec(&device->ref_count);
}
/*
* dasd_default_erp_action just retries the current cqr
*/
struct dasd_ccw_req *
dasd_default_erp_action(struct dasd_ccw_req *cqr)
{
struct dasd_device *device;
device = cqr->startdev;
/* just retry - there is nothing to save ... I got no sense data.... */
if (cqr->retries > 0) {
DBF_DEV_EVENT(DBF_DEBUG, device,
"default ERP called (%i retries left)",
cqr->retries);
if (!test_bit(DASD_CQR_VERIFY_PATH, &cqr->flags))
cqr->lpm = dasd_path_get_opm(device);
cqr->status = DASD_CQR_FILLED;
} else {
pr_err("%s: default ERP has run out of retries and failed\n",
dev_name(&device->cdev->dev));
cqr->status = DASD_CQR_FAILED;
cqr->stopclk = get_tod_clock();
}
return cqr;
} /* end dasd_default_erp_action */
/*
* DESCRIPTION
* Frees all ERPs of the current ERP Chain and set the status
* of the original CQR either to DASD_CQR_DONE if ERP was successful
* or to DASD_CQR_FAILED if ERP was NOT successful.
* NOTE: This function is only called if no discipline postaction
* is available
*
* PARAMETER
* erp current erp_head
*
* RETURN VALUES
* cqr pointer to the original CQR
*/
struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *cqr)
{
int success;
unsigned long startclk, stopclk;
struct dasd_device *startdev;
BUG_ON(cqr->refers == NULL || cqr->function == NULL);
success = cqr->status == DASD_CQR_DONE;
startclk = cqr->startclk;
stopclk = cqr->stopclk;
startdev = cqr->startdev;
/* free all ERPs - but NOT the original cqr */
while (cqr->refers != NULL) {
struct dasd_ccw_req *refers;
refers = cqr->refers;
/* remove the request from the block queue */
list_del(&cqr->blocklist);
/* free the finished erp request */
dasd_free_erp_request(cqr, cqr->memdev);
cqr = refers;
}
/* set corresponding status to original cqr */
cqr->startclk = startclk;
cqr->stopclk = stopclk;
cqr->startdev = startdev;
if (success)
cqr->status = DASD_CQR_DONE;
else {
cqr->status = DASD_CQR_FAILED;
cqr->stopclk = get_tod_clock();
}
return cqr;
} /* end default_erp_postaction */
void
dasd_log_sense(struct dasd_ccw_req *cqr, struct irb *irb)
{
struct dasd_device *device;
device = cqr->startdev;
if (cqr->intrc == -ETIMEDOUT) {
dev_err(&device->cdev->dev,
"A timeout error occurred for cqr %px\n", cqr);
return;
}
if (cqr->intrc == -ENOLINK) {
dev_err(&device->cdev->dev,
"A transport error occurred for cqr %px\n", cqr);
return;
}
/* dump sense data */
if (device->discipline && device->discipline->dump_sense)
device->discipline->dump_sense(device, cqr, irb);
}
void
dasd_log_sense_dbf(struct dasd_ccw_req *cqr, struct irb *irb)
{
struct dasd_device *device;
device = cqr->startdev;
/* dump sense data to s390 debugfeature*/
if (device->discipline && device->discipline->dump_sense_dbf)
device->discipline->dump_sense_dbf(device, irb, "log");
}
EXPORT_SYMBOL(dasd_log_sense_dbf);
EXPORT_SYMBOL(dasd_default_erp_action);
EXPORT_SYMBOL(dasd_default_erp_postaction);
EXPORT_SYMBOL(dasd_alloc_erp_request);
EXPORT_SYMBOL(dasd_free_erp_request);
EXPORT_SYMBOL(dasd_log_sense);
+811
View File
@@ -0,0 +1,811 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* Copyright IBM Corp. 1999, 2009
*/
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <asm/debug.h>
#include <linux/slab.h>
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/bio.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <asm/idals.h>
#include <asm/ebcdic.h>
#include <asm/ccwdev.h>
#include "dasd_int.h"
#include "dasd_fba.h"
#define FBA_DEFAULT_RETRIES 32
#define DASD_FBA_CCW_WRITE 0x41
#define DASD_FBA_CCW_READ 0x42
#define DASD_FBA_CCW_LOCATE 0x43
#define DASD_FBA_CCW_DEFINE_EXTENT 0x63
MODULE_DESCRIPTION("S/390 DASD FBA Disks device driver");
MODULE_LICENSE("GPL");
static struct dasd_discipline dasd_fba_discipline;
static void *dasd_fba_zero_page;
struct dasd_fba_private {
struct dasd_fba_characteristics rdc_data;
};
static struct ccw_device_id dasd_fba_ids[] = {
{ CCW_DEVICE_DEVTYPE (0x6310, 0, 0x9336, 0), .driver_info = 0x1},
{ CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3370, 0), .driver_info = 0x2},
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(ccw, dasd_fba_ids);
static int
dasd_fba_set_online(struct ccw_device *cdev)
{
return dasd_generic_set_online(cdev, &dasd_fba_discipline);
}
static struct ccw_driver dasd_fba_driver = {
.driver = {
.name = "dasd-fba",
.owner = THIS_MODULE,
.dev_groups = dasd_dev_groups,
},
.ids = dasd_fba_ids,
.probe = dasd_generic_probe,
.remove = dasd_generic_remove,
.set_offline = dasd_generic_set_offline,
.set_online = dasd_fba_set_online,
.notify = dasd_generic_notify,
.path_event = dasd_generic_path_event,
.int_class = IRQIO_DAS,
};
static void
define_extent(struct ccw1 * ccw, struct DE_fba_data *data, int rw,
int blksize, int beg, int nr)
{
ccw->cmd_code = DASD_FBA_CCW_DEFINE_EXTENT;
ccw->flags = 0;
ccw->count = 16;
ccw->cda = virt_to_dma32(data);
memset(data, 0, sizeof (struct DE_fba_data));
if (rw == WRITE)
(data->mask).perm = 0x0;
else if (rw == READ)
(data->mask).perm = 0x1;
else
data->mask.perm = 0x2;
data->blk_size = blksize;
data->ext_loc = beg;
data->ext_end = nr - 1;
}
static void
locate_record(struct ccw1 * ccw, struct LO_fba_data *data, int rw,
int block_nr, int block_ct)
{
ccw->cmd_code = DASD_FBA_CCW_LOCATE;
ccw->flags = 0;
ccw->count = 8;
ccw->cda = virt_to_dma32(data);
memset(data, 0, sizeof (struct LO_fba_data));
if (rw == WRITE)
data->operation.cmd = 0x5;
else if (rw == READ)
data->operation.cmd = 0x6;
else
data->operation.cmd = 0x8;
data->blk_nr = block_nr;
data->blk_ct = block_ct;
}
static int
dasd_fba_check_characteristics(struct dasd_device *device)
{
struct dasd_fba_private *private = device->private;
struct ccw_device *cdev = device->cdev;
struct dasd_block *block;
int readonly, rc;
if (!private) {
private = kzalloc_obj(*private, GFP_KERNEL | GFP_DMA);
if (!private) {
dev_warn(&device->cdev->dev,
"Allocating memory for private DASD "
"data failed\n");
return -ENOMEM;
}
device->private = private;
} else {
memset(private, 0, sizeof(*private));
}
block = dasd_alloc_block();
if (IS_ERR(block)) {
DBF_EVENT_DEVID(DBF_WARNING, cdev, "%s", "could not allocate "
"dasd block structure");
device->private = NULL;
kfree(private);
return PTR_ERR(block);
}
device->block = block;
block->base = device;
/* Read Device Characteristics */
rc = dasd_generic_read_dev_chars(device, DASD_FBA_MAGIC,
&private->rdc_data, 32);
if (rc) {
DBF_EVENT_DEVID(DBF_WARNING, cdev, "Read device "
"characteristics returned error %d", rc);
device->block = NULL;
dasd_free_block(block);
device->private = NULL;
kfree(private);
return rc;
}
device->default_expires = DASD_EXPIRES;
device->default_retries = FBA_DEFAULT_RETRIES;
dasd_path_set_opm(device, LPM_ANYPATH);
readonly = dasd_device_is_ro(device);
if (readonly)
set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
/* FBA supports discard, set the according feature bit */
dasd_set_feature(cdev, DASD_FEATURE_DISCARD, 1);
dev_info(&device->cdev->dev,
"New FBA DASD %04X/%02X (CU %04X/%02X) with %d MB "
"and %d B/blk%s\n",
cdev->id.dev_type,
cdev->id.dev_model,
cdev->id.cu_type,
cdev->id.cu_model,
((private->rdc_data.blk_bdsa *
(private->rdc_data.blk_size >> 9)) >> 11),
private->rdc_data.blk_size,
readonly ? ", read-only device" : "");
return 0;
}
static int dasd_fba_do_analysis(struct dasd_block *block)
{
struct dasd_fba_private *private = block->base->private;
int sb, rc;
rc = dasd_check_blocksize(private->rdc_data.blk_size);
if (rc) {
DBF_DEV_EVENT(DBF_WARNING, block->base, "unknown blocksize %d",
private->rdc_data.blk_size);
return rc;
}
block->blocks = private->rdc_data.blk_bdsa;
block->bp_block = private->rdc_data.blk_size;
block->s2b_shift = 0; /* bits to shift 512 to get a block */
for (sb = 512; sb < private->rdc_data.blk_size; sb = sb << 1)
block->s2b_shift++;
return 0;
}
static int dasd_fba_fill_geometry(struct dasd_block *block,
struct hd_geometry *geo)
{
if (dasd_check_blocksize(block->bp_block) != 0)
return -EINVAL;
geo->cylinders = (block->blocks << block->s2b_shift) >> 10;
geo->heads = 16;
geo->sectors = 128 >> block->s2b_shift;
return 0;
}
static dasd_erp_fn_t
dasd_fba_erp_action(struct dasd_ccw_req * cqr)
{
return dasd_default_erp_action;
}
static dasd_erp_fn_t
dasd_fba_erp_postaction(struct dasd_ccw_req * cqr)
{
if (cqr->function == dasd_default_erp_action)
return dasd_default_erp_postaction;
DBF_DEV_EVENT(DBF_WARNING, cqr->startdev, "unknown ERP action %p",
cqr->function);
return NULL;
}
static void dasd_fba_check_for_device_change(struct dasd_device *device,
struct dasd_ccw_req *cqr,
struct irb *irb)
{
char mask;
/* first of all check for state change pending interrupt */
mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP;
if ((irb->scsw.cmd.dstat & mask) == mask)
dasd_generic_handle_state_change(device);
};
/*
* Builds a CCW with no data payload
*/
static void ccw_write_no_data(struct ccw1 *ccw)
{
ccw->cmd_code = DASD_FBA_CCW_WRITE;
ccw->flags |= CCW_FLAG_SLI;
ccw->count = 0;
}
/*
* Builds a CCW that writes only zeroes.
*/
static void ccw_write_zero(struct ccw1 *ccw, int count)
{
ccw->cmd_code = DASD_FBA_CCW_WRITE;
ccw->flags |= CCW_FLAG_SLI;
ccw->count = count;
ccw->cda = virt_to_dma32(dasd_fba_zero_page);
}
/*
* Helper function to count the amount of necessary CCWs within a given range
* with 4k alignment and command chaining in mind.
*/
static int count_ccws(sector_t first_rec, sector_t last_rec,
unsigned int blocks_per_page)
{
sector_t wz_stop = 0, d_stop = 0;
int cur_pos = 0;
int count = 0;
if (first_rec % blocks_per_page != 0) {
wz_stop = first_rec + blocks_per_page -
(first_rec % blocks_per_page) - 1;
if (wz_stop > last_rec)
wz_stop = last_rec;
cur_pos = wz_stop - first_rec + 1;
count++;
}
if (last_rec - (first_rec + cur_pos) + 1 >= blocks_per_page) {
if ((last_rec - blocks_per_page + 1) % blocks_per_page != 0)
d_stop = last_rec - ((last_rec - blocks_per_page + 1) %
blocks_per_page);
else
d_stop = last_rec;
cur_pos += d_stop - (first_rec + cur_pos) + 1;
count++;
}
if (cur_pos == 0 || first_rec + cur_pos - 1 < last_rec)
count++;
return count;
}
/*
* This function builds a CCW request for block layer discard requests.
* Each page in the z/VM hypervisor that represents certain records of an FBA
* device will be padded with zeros. This is a special behaviour of the WRITE
* command which is triggered when no data payload is added to the CCW.
*
* Note: Due to issues in some z/VM versions, we can't fully utilise this
* special behaviour. We have to keep a 4k (or 8 block) alignment in mind to
* work around those issues and write actual zeroes to the unaligned parts in
* the request. This workaround might be removed in the future.
*/
static struct dasd_ccw_req *dasd_fba_build_cp_discard(
struct dasd_device *memdev,
struct dasd_block *block,
struct request *req)
{
struct LO_fba_data *LO_data;
struct dasd_ccw_req *cqr;
struct ccw1 *ccw;
sector_t wz_stop = 0, d_stop = 0;
sector_t first_rec, last_rec;
unsigned int blksize = block->bp_block;
unsigned int blocks_per_page;
int wz_count = 0;
int d_count = 0;
int cur_pos = 0; /* Current position within the extent */
int count = 0;
int cplength;
int datasize;
int nr_ccws;
first_rec = blk_rq_pos(req) >> block->s2b_shift;
last_rec =
(blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift;
count = last_rec - first_rec + 1;
blocks_per_page = BLOCKS_PER_PAGE(blksize);
nr_ccws = count_ccws(first_rec, last_rec, blocks_per_page);
/* define extent + nr_ccws * locate record + nr_ccws * single CCW */
cplength = 1 + 2 * nr_ccws;
datasize = sizeof(struct DE_fba_data) +
nr_ccws * (sizeof(struct LO_fba_data) + sizeof(struct ccw1));
cqr = dasd_smalloc_request(DASD_FBA_MAGIC, cplength, datasize, memdev,
blk_mq_rq_to_pdu(req));
if (IS_ERR(cqr))
return cqr;
ccw = cqr->cpaddr;
define_extent(ccw++, cqr->data, WRITE, blksize, first_rec, count);
LO_data = cqr->data + sizeof(struct DE_fba_data);
/* First part is not aligned. Calculate range to write zeroes. */
if (first_rec % blocks_per_page != 0) {
wz_stop = first_rec + blocks_per_page -
(first_rec % blocks_per_page) - 1;
if (wz_stop > last_rec)
wz_stop = last_rec;
wz_count = wz_stop - first_rec + 1;
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw++, LO_data++, WRITE, cur_pos, wz_count);
ccw[-1].flags |= CCW_FLAG_CC;
ccw_write_zero(ccw++, wz_count * blksize);
cur_pos = wz_count;
}
/* We can do proper discard when we've got at least blocks_per_page blocks. */
if (last_rec - (first_rec + cur_pos) + 1 >= blocks_per_page) {
/* is last record at page boundary? */
if ((last_rec - blocks_per_page + 1) % blocks_per_page != 0)
d_stop = last_rec - ((last_rec - blocks_per_page + 1) %
blocks_per_page);
else
d_stop = last_rec;
d_count = d_stop - (first_rec + cur_pos) + 1;
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw++, LO_data++, WRITE, cur_pos, d_count);
ccw[-1].flags |= CCW_FLAG_CC;
ccw_write_no_data(ccw++);
cur_pos += d_count;
}
/* We might still have some bits left which need to be zeroed. */
if (cur_pos == 0 || first_rec + cur_pos - 1 < last_rec) {
if (d_stop != 0)
wz_count = last_rec - d_stop;
else if (wz_stop != 0)
wz_count = last_rec - wz_stop;
else
wz_count = count;
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw++, LO_data++, WRITE, cur_pos, wz_count);
ccw[-1].flags |= CCW_FLAG_CC;
ccw_write_zero(ccw++, wz_count * blksize);
}
if (blk_noretry_request(req) ||
block->base->features & DASD_FEATURE_FAILFAST)
set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
cqr->startdev = memdev;
cqr->memdev = memdev;
cqr->block = block;
cqr->expires = memdev->default_expires * HZ; /* default 5 minutes */
cqr->retries = memdev->default_retries;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
return cqr;
}
static struct dasd_ccw_req *dasd_fba_build_cp_regular(
struct dasd_device *memdev,
struct dasd_block *block,
struct request *req)
{
struct dasd_fba_private *private = block->base->private;
dma64_t *idaws;
struct LO_fba_data *LO_data;
struct dasd_ccw_req *cqr;
struct ccw1 *ccw;
struct req_iterator iter;
struct bio_vec bv;
char *dst;
int count, cidaw, cplength, datasize;
sector_t recid, first_rec, last_rec;
unsigned int blksize, off;
unsigned char cmd;
if (rq_data_dir(req) == READ) {
cmd = DASD_FBA_CCW_READ;
} else if (rq_data_dir(req) == WRITE) {
cmd = DASD_FBA_CCW_WRITE;
} else
return ERR_PTR(-EINVAL);
blksize = block->bp_block;
/* Calculate record id of first and last block. */
first_rec = blk_rq_pos(req) >> block->s2b_shift;
last_rec =
(blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift;
/* Check struct bio and count the number of blocks for the request. */
count = 0;
cidaw = 0;
rq_for_each_segment(bv, req, iter) {
if (bv.bv_len & (blksize - 1))
/* Fba can only do full blocks. */
return ERR_PTR(-EINVAL);
count += bv.bv_len >> (block->s2b_shift + 9);
if (idal_is_needed (page_address(bv.bv_page), bv.bv_len))
cidaw += bv.bv_len / blksize;
}
/* Paranoia. */
if (count != last_rec - first_rec + 1)
return ERR_PTR(-EINVAL);
/* 1x define extent + 1x locate record + number of blocks */
cplength = 2 + count;
/* 1x define extent + 1x locate record */
datasize = sizeof(struct DE_fba_data) + sizeof(struct LO_fba_data) +
cidaw * sizeof(unsigned long);
/*
* Find out number of additional locate record ccws if the device
* can't do data chaining.
*/
if (private->rdc_data.mode.bits.data_chain == 0) {
cplength += count - 1;
datasize += (count - 1)*sizeof(struct LO_fba_data);
}
/* Allocate the ccw request. */
cqr = dasd_smalloc_request(DASD_FBA_MAGIC, cplength, datasize, memdev,
blk_mq_rq_to_pdu(req));
if (IS_ERR(cqr))
return cqr;
ccw = cqr->cpaddr;
/* First ccw is define extent. */
define_extent(ccw++, cqr->data, rq_data_dir(req),
block->bp_block, blk_rq_pos(req), blk_rq_sectors(req));
/* Build locate_record + read/write ccws. */
idaws = (dma64_t *)(cqr->data + sizeof(struct DE_fba_data));
LO_data = (struct LO_fba_data *) (idaws + cidaw);
/* Locate record for all blocks for smart devices. */
if (private->rdc_data.mode.bits.data_chain != 0) {
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw++, LO_data++, rq_data_dir(req), 0, count);
}
recid = first_rec;
rq_for_each_segment(bv, req, iter) {
dst = bvec_virt(&bv);
if (dasd_page_cache) {
char *copy = kmem_cache_alloc(dasd_page_cache,
GFP_DMA | __GFP_NOWARN);
if (copy && rq_data_dir(req) == WRITE)
memcpy(copy + bv.bv_offset, dst, bv.bv_len);
if (copy)
dst = copy + bv.bv_offset;
}
for (off = 0; off < bv.bv_len; off += blksize) {
/* Locate record for stupid devices. */
if (private->rdc_data.mode.bits.data_chain == 0) {
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw, LO_data++,
rq_data_dir(req),
recid - first_rec, 1);
ccw->flags = CCW_FLAG_CC;
ccw++;
} else {
if (recid > first_rec)
ccw[-1].flags |= CCW_FLAG_DC;
else
ccw[-1].flags |= CCW_FLAG_CC;
}
ccw->cmd_code = cmd;
ccw->count = block->bp_block;
if (idal_is_needed(dst, blksize)) {
ccw->cda = virt_to_dma32(idaws);
ccw->flags = CCW_FLAG_IDA;
idaws = idal_create_words(idaws, dst, blksize);
} else {
ccw->cda = virt_to_dma32(dst);
ccw->flags = 0;
}
ccw++;
dst += blksize;
recid++;
}
}
if (blk_noretry_request(req) ||
block->base->features & DASD_FEATURE_FAILFAST)
set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags);
cqr->startdev = memdev;
cqr->memdev = memdev;
cqr->block = block;
cqr->expires = memdev->default_expires * HZ; /* default 5 minutes */
cqr->retries = memdev->default_retries;
cqr->buildclk = get_tod_clock();
cqr->status = DASD_CQR_FILLED;
return cqr;
}
static struct dasd_ccw_req *dasd_fba_build_cp(struct dasd_device *memdev,
struct dasd_block *block,
struct request *req)
{
if (req_op(req) == REQ_OP_DISCARD || req_op(req) == REQ_OP_WRITE_ZEROES)
return dasd_fba_build_cp_discard(memdev, block, req);
else
return dasd_fba_build_cp_regular(memdev, block, req);
}
static int
dasd_fba_free_cp(struct dasd_ccw_req *cqr, struct request *req)
{
struct dasd_fba_private *private = cqr->block->base->private;
struct ccw1 *ccw;
struct req_iterator iter;
struct bio_vec bv;
char *dst, *cda;
unsigned int blksize, off;
int status;
if (!dasd_page_cache)
goto out;
blksize = cqr->block->bp_block;
ccw = cqr->cpaddr;
/* Skip over define extent & locate record. */
ccw++;
if (private->rdc_data.mode.bits.data_chain != 0)
ccw++;
rq_for_each_segment(bv, req, iter) {
dst = bvec_virt(&bv);
for (off = 0; off < bv.bv_len; off += blksize) {
/* Skip locate record. */
if (private->rdc_data.mode.bits.data_chain == 0)
ccw++;
if (dst) {
if (ccw->flags & CCW_FLAG_IDA)
cda = dma64_to_virt(*((dma64_t *)dma32_to_virt(ccw->cda)));
else
cda = dma32_to_virt(ccw->cda);
if (dst != cda) {
if (rq_data_dir(req) == READ)
memcpy(dst, cda, bv.bv_len);
kmem_cache_free(dasd_page_cache,
(void *)((addr_t)cda & PAGE_MASK));
}
dst = NULL;
}
ccw++;
}
}
out:
status = cqr->status == DASD_CQR_DONE;
dasd_sfree_request(cqr, cqr->memdev);
return status;
}
static void dasd_fba_handle_terminated_request(struct dasd_ccw_req *cqr)
{
if (cqr->retries < 0)
cqr->status = DASD_CQR_FAILED;
else
cqr->status = DASD_CQR_FILLED;
};
static int
dasd_fba_fill_info(struct dasd_device * device,
struct dasd_information2_t * info)
{
struct dasd_fba_private *private = device->private;
info->label_block = 1;
info->FBA_layout = 1;
info->format = DASD_FORMAT_LDL;
info->characteristics_size = sizeof(private->rdc_data);
memcpy(info->characteristics, &private->rdc_data,
sizeof(private->rdc_data));
info->confdata_size = 0;
return 0;
}
static void
dasd_fba_dump_sense_dbf(struct dasd_device *device, struct irb *irb,
char *reason)
{
u64 *sense;
sense = (u64 *) dasd_get_sense(irb);
if (sense) {
DBF_DEV_EVENT(DBF_EMERG, device,
"%s: %s %02x%02x%02x %016llx %016llx %016llx "
"%016llx", reason,
scsw_is_tm(&irb->scsw) ? "t" : "c",
scsw_cc(&irb->scsw), scsw_cstat(&irb->scsw),
scsw_dstat(&irb->scsw), sense[0], sense[1],
sense[2], sense[3]);
} else {
DBF_DEV_EVENT(DBF_EMERG, device, "%s",
"SORRY - NO VALID SENSE AVAILABLE\n");
}
}
static void
dasd_fba_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
struct irb *irb)
{
struct ccw1 *act, *end, *last;
int len, sl, sct, count;
struct device *dev;
char *page;
dev = &device->cdev->dev;
page = (char *) get_zeroed_page(GFP_ATOMIC);
if (page == NULL) {
DBF_DEV_EVENT(DBF_WARNING, device, "%s",
"No memory to dump sense data");
return;
}
len = sprintf(page, "I/O status report:\n");
len += sprintf(page + len, "in req: %px CS: 0x%02X DS: 0x%02X\n",
req, irb->scsw.cmd.cstat, irb->scsw.cmd.dstat);
len += sprintf(page + len, "Failing CCW: %px\n",
(void *)(u64)dma32_to_u32(irb->scsw.cmd.cpa));
if (irb->esw.esw0.erw.cons) {
for (sl = 0; sl < 4; sl++) {
len += sprintf(page + len, "Sense(hex) %2d-%2d:",
(8 * sl), ((8 * sl) + 7));
for (sct = 0; sct < 8; sct++) {
len += sprintf(page + len, " %02x",
irb->ecw[8 * sl + sct]);
}
len += sprintf(page + len, "\n");
}
} else {
len += sprintf(page + len, "SORRY - NO VALID SENSE AVAILABLE\n");
}
dev_err(dev, "%s", page);
/* dump the Channel Program */
/* print first CCWs (maximum 8) */
act = req->cpaddr;
for (last = act; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++);
end = min(act + 8, last);
len = sprintf(page, "Related CP in req: %px\n", req);
while (act <= end) {
len += sprintf(page + len, "CCW %px: %08X %08X DAT:",
act, ((int *) act)[0], ((int *) act)[1]);
for (count = 0; count < 32 && count < act->count;
count += sizeof(int))
len += sprintf(page + len, " %08X",
((int *)dma32_to_virt(act->cda))
[(count>>2)]);
len += sprintf(page + len, "\n");
act++;
}
dev_err(dev, "%s", page);
/* print failing CCW area */
len = 0;
if (act < ((struct ccw1 *)dma32_to_virt(irb->scsw.cmd.cpa)) - 2) {
act = ((struct ccw1 *)dma32_to_virt(irb->scsw.cmd.cpa)) - 2;
len += sprintf(page + len, "......\n");
}
end = min((struct ccw1 *)dma32_to_virt(irb->scsw.cmd.cpa) + 2, last);
while (act <= end) {
len += sprintf(page + len, "CCW %px: %08X %08X DAT:",
act, ((int *) act)[0], ((int *) act)[1]);
for (count = 0; count < 32 && count < act->count;
count += sizeof(int))
len += sprintf(page + len, " %08X",
((int *)dma32_to_virt(act->cda))
[(count>>2)]);
len += sprintf(page + len, "\n");
act++;
}
/* print last CCWs */
if (act < last - 2) {
act = last - 2;
len += sprintf(page + len, "......\n");
}
while (act <= last) {
len += sprintf(page + len, "CCW %px: %08X %08X DAT:",
act, ((int *) act)[0], ((int *) act)[1]);
for (count = 0; count < 32 && count < act->count;
count += sizeof(int))
len += sprintf(page + len, " %08X",
((int *)dma32_to_virt(act->cda))
[(count>>2)]);
len += sprintf(page + len, "\n");
act++;
}
if (len > 0)
dev_err(dev, "%s", page);
free_page((unsigned long) page);
}
static unsigned int dasd_fba_max_sectors(struct dasd_block *block)
{
return DASD_FBA_MAX_BLOCKS << block->s2b_shift;
}
static int dasd_fba_pe_handler(struct dasd_device *device,
__u8 tbvpm, __u8 fcsecpm)
{
return dasd_generic_verify_path(device, tbvpm);
}
static struct dasd_discipline dasd_fba_discipline = {
.owner = THIS_MODULE,
.name = "FBA ",
.ebcname = "FBA ",
.has_discard = true,
.check_device = dasd_fba_check_characteristics,
.do_analysis = dasd_fba_do_analysis,
.pe_handler = dasd_fba_pe_handler,
.max_sectors = dasd_fba_max_sectors,
.fill_geometry = dasd_fba_fill_geometry,
.start_IO = dasd_start_IO,
.term_IO = dasd_term_IO,
.handle_terminated_request = dasd_fba_handle_terminated_request,
.erp_action = dasd_fba_erp_action,
.erp_postaction = dasd_fba_erp_postaction,
.check_for_device_change = dasd_fba_check_for_device_change,
.build_cp = dasd_fba_build_cp,
.free_cp = dasd_fba_free_cp,
.dump_sense = dasd_fba_dump_sense,
.dump_sense_dbf = dasd_fba_dump_sense_dbf,
.fill_info = dasd_fba_fill_info,
};
static int __init
dasd_fba_init(void)
{
int ret;
ASCEBC(dasd_fba_discipline.ebcname, 4);
dasd_fba_zero_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (!dasd_fba_zero_page)
return -ENOMEM;
ret = ccw_driver_register(&dasd_fba_driver);
if (!ret)
wait_for_device_probe();
return ret;
}
static void __exit
dasd_fba_cleanup(void)
{
ccw_driver_unregister(&dasd_fba_driver);
free_page((unsigned long)dasd_fba_zero_page);
}
module_init(dasd_fba_init);
module_exit(dasd_fba_cleanup);
+77
View File
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* Copyright IBM Corp. 1999, 2000
*
*/
#ifndef DASD_FBA_H
#define DASD_FBA_H
/*
* Maximum number of blocks to be chained
*/
#define DASD_FBA_MAX_BLOCKS 96
struct DE_fba_data {
struct {
unsigned char perm:2; /* Permissions on this extent */
unsigned char zero:2; /* Must be zero */
unsigned char da:1; /* usually zero */
unsigned char diag:1; /* allow diagnose */
unsigned char zero2:2; /* zero */
} __attribute__ ((packed)) mask;
__u8 zero; /* Must be zero */
__u16 blk_size; /* Blocksize */
__u32 ext_loc; /* Extent locator */
__u32 ext_beg; /* logical number of block 0 in extent */
__u32 ext_end; /* logocal number of last block in extent */
} __attribute__ ((packed));
struct LO_fba_data {
struct {
unsigned char zero:4;
unsigned char cmd:4;
} __attribute__ ((packed)) operation;
__u8 auxiliary;
__u16 blk_ct;
__u32 blk_nr;
} __attribute__ ((packed));
struct dasd_fba_characteristics {
union {
__u8 c;
struct {
unsigned char reserved:1;
unsigned char overrunnable:1;
unsigned char burst_byte:1;
unsigned char data_chain:1;
unsigned char zeros:4;
} __attribute__ ((packed)) bits;
} __attribute__ ((packed)) mode;
union {
__u8 c;
struct {
unsigned char zero0:1;
unsigned char removable:1;
unsigned char shared:1;
unsigned char zero1:1;
unsigned char mam:1;
unsigned char zeros:3;
} __attribute__ ((packed)) bits;
} __attribute__ ((packed)) features;
__u8 dev_class;
__u8 unit_type;
__u16 blk_size;
__u32 blk_per_cycl;
__u32 blk_per_bound;
__u32 blk_bdsa;
__u32 reserved0;
__u16 reserved1;
__u16 blk_ce;
__u32 reserved2;
__u16 reserved3;
} __attribute__ ((packed));
#endif /* DASD_FBA_H */
+233
View File
@@ -0,0 +1,233 @@
// 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
*
* gendisk related functions for the dasd driver.
*
*/
#include <linux/interrupt.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/blkpg.h>
#include <linux/uaccess.h>
#include "dasd_int.h"
static unsigned int queue_depth = 32;
static unsigned int nr_hw_queues = 4;
static void dasd_gd_free(struct gendisk *gdp);
module_param(queue_depth, uint, 0444);
MODULE_PARM_DESC(queue_depth, "Default queue depth for new DASD devices");
module_param(nr_hw_queues, uint, 0444);
MODULE_PARM_DESC(nr_hw_queues, "Default number of hardware queues for new DASD devices");
/*
* Set device name.
* dasda - dasdz : 26 devices
* dasdaa - dasdzz : 676 devices, added up = 702
* dasdaaa - dasdzzz : 17576 devices, added up = 18278
* dasdaaaa - dasdzzzz : 456976 devices, added up = 475252
*/
static int dasd_name_format(char *prefix, int index, char *buf, int buflen)
{
const int base = 'z' - 'a' + 1;
char *begin = buf + strlen(prefix);
char *end = buf + buflen;
char *p;
int unit;
p = end - 1;
*p = '\0';
unit = base;
do {
if (p == begin)
return -EINVAL;
*--p = 'a' + (index % unit);
index = (index / unit) - 1;
} while (index >= 0);
memmove(begin, p, end - p);
memcpy(buf, prefix, strlen(prefix));
return 0;
}
/*
* Allocate and register gendisk structure for device.
*/
int dasd_gendisk_alloc(struct dasd_block *block)
{
struct queue_limits lim = {
/*
* With page sized segments, each segment can be translated into
* one idaw/tidaw.
*/
.max_segment_size = PAGE_SIZE,
.seg_boundary_mask = PAGE_SIZE - 1,
.max_segments = USHRT_MAX,
};
struct gendisk *gdp;
struct dasd_device *base;
unsigned int devindex;
int rc;
/* Make sure the minor for this device exists. */
base = block->base;
devindex = base->devindex;
if (devindex >= DASD_PER_MAJOR)
return -EBUSY;
block->tag_set.ops = &dasd_mq_ops;
block->tag_set.cmd_size = sizeof(struct dasd_ccw_req);
block->tag_set.nr_hw_queues = nr_hw_queues;
block->tag_set.queue_depth = queue_depth;
block->tag_set.numa_node = NUMA_NO_NODE;
rc = blk_mq_alloc_tag_set(&block->tag_set);
if (rc)
return rc;
gdp = blk_mq_alloc_disk(&block->tag_set, &lim, block);
if (IS_ERR(gdp)) {
blk_mq_free_tag_set(&block->tag_set);
return PTR_ERR(gdp);
}
/* Initialize gendisk structure. */
gdp->major = DASD_MAJOR;
gdp->first_minor = devindex << DASD_PARTN_BITS;
gdp->minors = 1 << DASD_PARTN_BITS;
gdp->fops = &dasd_device_operations;
rc = dasd_name_format("dasd", devindex, gdp->disk_name, sizeof(gdp->disk_name));
if (rc) {
DBF_DEV_EVENT(DBF_ERR, block->base,
"setting disk name failed, rc %d", rc);
dasd_gd_free(gdp);
return rc;
}
if (base->features & DASD_FEATURE_READONLY ||
test_bit(DASD_FLAG_DEVICE_RO, &base->flags))
set_disk_ro(gdp, 1);
dasd_add_link_to_gendisk(gdp, base);
block->gdp = gdp;
set_capacity(block->gdp, 0);
rc = device_add_disk(&base->cdev->dev, block->gdp, NULL);
if (rc) {
dasd_gendisk_free(block);
return rc;
}
return 0;
}
/*
* Free gendisk structure
*/
static void dasd_gd_free(struct gendisk *gd)
{
del_gendisk(gd);
gd->private_data = NULL;
put_disk(gd);
}
/*
* Unregister and free gendisk structure for device.
*/
void dasd_gendisk_free(struct dasd_block *block)
{
if (block->gdp) {
dasd_gd_free(block->gdp);
block->gdp = NULL;
blk_mq_free_tag_set(&block->tag_set);
}
}
/*
* Trigger a partition detection.
*/
int dasd_scan_partitions(struct dasd_block *block)
{
struct file *bdev_file;
int rc;
bdev_file = bdev_file_open_by_dev(disk_devt(block->gdp), BLK_OPEN_READ,
NULL, NULL);
if (IS_ERR(bdev_file)) {
DBF_DEV_EVENT(DBF_ERR, block->base,
"scan partitions error, blkdev_get returned %ld",
PTR_ERR(bdev_file));
return -ENODEV;
}
mutex_lock(&block->gdp->open_mutex);
rc = bdev_disk_changed(block->gdp, false);
mutex_unlock(&block->gdp->open_mutex);
if (rc)
DBF_DEV_EVENT(DBF_ERR, block->base,
"scan partitions error, rc %d", rc);
/*
* Since the matching fput() call to the
* bdev_file_open_by_path() in this function is not called before
* dasd_destroy_partitions the offline open_count limit needs to be
* increased from 0 to 1. This is done by setting device->bdev_file
* (see dasd_generic_set_offline). As long as the partition detection
* is running no offline should be allowed. That is why the assignment
* to block->bdev_file is done AFTER the BLKRRPART ioctl.
*/
block->bdev_file = bdev_file;
return 0;
}
/*
* Remove all inodes in the system for a device, delete the
* partitions and make device unusable by setting its size to zero.
*/
void dasd_destroy_partitions(struct dasd_block *block)
{
struct file *bdev_file;
/*
* Get the bdev_file pointer from the device structure and clear
* device->bdev_file to lower the offline open_count limit again.
*/
bdev_file = block->bdev_file;
block->bdev_file = NULL;
mutex_lock(&file_bdev(bdev_file)->bd_disk->open_mutex);
bdev_disk_changed(file_bdev(bdev_file)->bd_disk, true);
mutex_unlock(&file_bdev(bdev_file)->bd_disk->open_mutex);
/* Matching blkdev_put to the blkdev_get in dasd_scan_partitions. */
fput(bdev_file);
}
int dasd_gendisk_init(void)
{
int rc;
/* Register to static dasd major 94 */
rc = register_blkdev(DASD_MAJOR, "dasd");
if (rc != 0) {
pr_warn("Registering the device driver with major number %d failed\n",
DASD_MAJOR);
return rc;
}
return 0;
}
void dasd_gendisk_exit(void)
{
unregister_blkdev(DASD_MAJOR, "dasd");
}
File diff suppressed because it is too large Load Diff
+726
View File
@@ -0,0 +1,726 @@
// 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
*
* i/o controls for the dasd driver.
*/
#include <linux/interrupt.h>
#include <linux/export.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/blkpg.h>
#include <linux/slab.h>
#include <asm/ccwdev.h>
#include <asm/schid.h>
#include <asm/cmb.h>
#include <linux/uaccess.h>
#include <linux/dasd_mod.h>
#include "dasd_int.h"
static int
dasd_ioctl_api_version(void __user *argp)
{
int ver = DASD_API_VERSION;
return put_user(ver, (int __user *)argp);
}
/*
* Enable device.
* used by dasdfmt after BIODASDDISABLE to retrigger blocksize detection
*/
static int
dasd_ioctl_enable(struct block_device *bdev)
{
struct dasd_device *base;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
dasd_enable_device(base);
dasd_put_device(base);
return 0;
}
/*
* Disable device.
* Used by dasdfmt. Disable I/O operations but allow ioctls.
*/
static int
dasd_ioctl_disable(struct block_device *bdev)
{
struct dasd_device *base;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
/*
* Man this is sick. We don't do a real disable but only downgrade
* the device to DASD_STATE_BASIC. The reason is that dasdfmt uses
* BIODASDDISABLE to disable accesses to the device via the block
* device layer but it still wants to do i/o on the device by
* using the BIODASDFMT ioctl. Therefore the correct state for the
* device is DASD_STATE_BASIC that allows to do basic i/o.
*/
dasd_set_target_state(base, DASD_STATE_BASIC);
/*
* Set i_size to zero, since read, write, etc. check against this
* value.
*/
set_capacity(bdev->bd_disk, 0);
dasd_put_device(base);
return 0;
}
/*
* Quiesce device.
*/
static int dasd_ioctl_quiesce(struct dasd_block *block)
{
unsigned long flags;
struct dasd_device *base;
base = block->base;
if (!capable (CAP_SYS_ADMIN))
return -EACCES;
pr_info("%s: The DASD has been put in the quiesce "
"state\n", dev_name(&base->cdev->dev));
spin_lock_irqsave(get_ccwdev_lock(base->cdev), flags);
dasd_device_set_stop_bits(base, DASD_STOPPED_QUIESCE);
spin_unlock_irqrestore(get_ccwdev_lock(base->cdev), flags);
return 0;
}
/*
* Resume device.
*/
static int dasd_ioctl_resume(struct dasd_block *block)
{
unsigned long flags;
struct dasd_device *base;
base = block->base;
if (!capable (CAP_SYS_ADMIN))
return -EACCES;
pr_info("%s: I/O operations have been resumed "
"on the DASD\n", dev_name(&base->cdev->dev));
spin_lock_irqsave(get_ccwdev_lock(base->cdev), flags);
dasd_device_remove_stop_bits(base, DASD_STOPPED_QUIESCE);
spin_unlock_irqrestore(get_ccwdev_lock(base->cdev), flags);
dasd_schedule_block_bh(block);
dasd_schedule_device_bh(base);
return 0;
}
/*
* Abort all failfast I/O on a device.
*/
static int dasd_ioctl_abortio(struct dasd_block *block)
{
unsigned long flags;
struct dasd_device *base;
struct dasd_ccw_req *cqr, *n;
base = block->base;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (test_and_set_bit(DASD_FLAG_ABORTALL, &base->flags))
return 0;
DBF_DEV_EVENT(DBF_NOTICE, base, "%s", "abortall flag set");
spin_lock_irqsave(&block->request_queue_lock, flags);
spin_lock(&block->queue_lock);
list_for_each_entry_safe(cqr, n, &block->ccw_queue, blocklist) {
if (test_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags) &&
cqr->callback_data &&
cqr->callback_data != DASD_SLEEPON_START_TAG &&
cqr->callback_data != DASD_SLEEPON_END_TAG) {
spin_unlock(&block->queue_lock);
blk_abort_request(cqr->callback_data);
spin_lock(&block->queue_lock);
}
}
spin_unlock(&block->queue_lock);
spin_unlock_irqrestore(&block->request_queue_lock, flags);
dasd_schedule_block_bh(block);
return 0;
}
/*
* Allow I/O on a device
*/
static int dasd_ioctl_allowio(struct dasd_block *block)
{
struct dasd_device *base;
base = block->base;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (test_and_clear_bit(DASD_FLAG_ABORTALL, &base->flags))
DBF_DEV_EVENT(DBF_NOTICE, base, "%s", "abortall flag unset");
return 0;
}
/*
* performs formatting of _device_ according to _fdata_
* Note: The discipline's format_function is assumed to deliver formatting
* commands to format multiple units of the device. In terms of the ECKD
* devices this means CCWs are generated to format multiple tracks.
*/
static int
dasd_format(struct dasd_block *block, struct format_data_t *fdata)
{
struct dasd_device *base;
int rc;
base = block->base;
if (base->discipline->format_device == NULL)
return -EPERM;
if (base->state != DASD_STATE_BASIC) {
pr_warn("%s: The DASD cannot be formatted while it is enabled\n",
dev_name(&base->cdev->dev));
return -EBUSY;
}
DBF_DEV_EVENT(DBF_NOTICE, base,
"formatting units %u to %u (%u B blocks) flags %u",
fdata->start_unit,
fdata->stop_unit, fdata->blksize, fdata->intensity);
/* Since dasdfmt keeps the device open after it was disabled,
* there still exists an inode for this device.
* We must update i_blkbits, otherwise we might get errors when
* enabling the device later.
*/
if (fdata->start_unit == 0) {
block->gdp->part0->bd_mapping->host->i_blkbits =
blksize_bits(fdata->blksize);
}
rc = base->discipline->format_device(base, fdata, 1);
if (rc == -EAGAIN)
rc = base->discipline->format_device(base, fdata, 0);
return rc;
}
static int dasd_check_format(struct dasd_block *block,
struct format_check_t *cdata)
{
struct dasd_device *base;
int rc;
base = block->base;
if (!base->discipline->check_device_format)
return -ENOTTY;
rc = base->discipline->check_device_format(base, cdata, 1);
if (rc == -EAGAIN)
rc = base->discipline->check_device_format(base, cdata, 0);
return rc;
}
/*
* Format device.
*/
static int
dasd_ioctl_format(struct block_device *bdev, void __user *argp)
{
struct dasd_device *base;
struct format_data_t fdata;
int rc;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (!argp)
return -EINVAL;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
if (base->features & DASD_FEATURE_READONLY ||
test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) {
dasd_put_device(base);
return -EROFS;
}
if (copy_from_user(&fdata, argp, sizeof(struct format_data_t))) {
dasd_put_device(base);
return -EFAULT;
}
if (bdev_is_partition(bdev)) {
pr_warn("%s: The specified DASD is a partition and cannot be formatted\n",
dev_name(&base->cdev->dev));
dasd_put_device(base);
return -EINVAL;
}
rc = dasd_format(base->block, &fdata);
dasd_put_device(base);
return rc;
}
/*
* Check device format
*/
static int dasd_ioctl_check_format(struct block_device *bdev, void __user *argp)
{
struct format_check_t cdata;
struct dasd_device *base;
int rc = 0;
if (!argp)
return -EINVAL;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
if (bdev_is_partition(bdev)) {
pr_warn("%s: The specified DASD is a partition and cannot be checked\n",
dev_name(&base->cdev->dev));
rc = -EINVAL;
goto out_err;
}
if (copy_from_user(&cdata, argp, sizeof(cdata))) {
rc = -EFAULT;
goto out_err;
}
rc = dasd_check_format(base->block, &cdata);
if (rc)
goto out_err;
if (copy_to_user(argp, &cdata, sizeof(cdata)))
rc = -EFAULT;
out_err:
dasd_put_device(base);
return rc;
}
static int dasd_release_space(struct dasd_device *device,
struct format_data_t *rdata)
{
if (!device->discipline->is_ese || !device->discipline->is_ese(device))
return -ENOTSUPP;
if (!device->discipline->release_space)
return -ENOTSUPP;
return device->discipline->release_space(device, rdata);
}
/*
* Release allocated space
*/
static int dasd_ioctl_release_space(struct block_device *bdev, void __user *argp)
{
struct format_data_t rdata;
struct dasd_device *base;
int rc = 0;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (!argp)
return -EINVAL;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
if (base->features & DASD_FEATURE_READONLY ||
test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) {
rc = -EROFS;
goto out_err;
}
if (bdev_is_partition(bdev)) {
pr_warn("%s: The specified DASD is a partition and tracks cannot be released\n",
dev_name(&base->cdev->dev));
rc = -EINVAL;
goto out_err;
}
if (copy_from_user(&rdata, argp, sizeof(rdata))) {
rc = -EFAULT;
goto out_err;
}
rc = dasd_release_space(base, &rdata);
out_err:
dasd_put_device(base);
return rc;
}
/*
* Swap driver iternal copy relation.
*/
static int
dasd_ioctl_copy_pair_swap(struct block_device *bdev, void __user *argp)
{
struct dasd_copypair_swap_data_t data;
struct dasd_device *device;
int rc;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
device = dasd_device_from_gendisk(bdev->bd_disk);
if (!device)
return -ENODEV;
if (copy_from_user(&data, argp, sizeof(struct dasd_copypair_swap_data_t))) {
dasd_put_device(device);
return -EFAULT;
}
if (memchr_inv(data.reserved, 0, sizeof(data.reserved))) {
pr_warn("%s: Invalid swap data specified\n",
dev_name(&device->cdev->dev));
dasd_put_device(device);
return DASD_COPYPAIRSWAP_INVALID;
}
if (bdev_is_partition(bdev)) {
pr_warn("%s: The specified DASD is a partition and cannot be swapped\n",
dev_name(&device->cdev->dev));
dasd_put_device(device);
return DASD_COPYPAIRSWAP_INVALID;
}
if (!device->copy) {
pr_warn("%s: The specified DASD has no copy pair set up\n",
dev_name(&device->cdev->dev));
dasd_put_device(device);
return -ENODEV;
}
if (!device->discipline->copy_pair_swap) {
dasd_put_device(device);
return -EOPNOTSUPP;
}
rc = device->discipline->copy_pair_swap(device, data.primary,
data.secondary);
dasd_put_device(device);
return rc;
}
#ifdef CONFIG_DASD_PROFILE
/*
* Reset device profile information
*/
static int dasd_ioctl_reset_profile(struct dasd_block *block)
{
dasd_profile_reset(&block->profile);
return 0;
}
/*
* Return device profile information
*/
static int dasd_ioctl_read_profile(struct dasd_block *block, void __user *argp)
{
struct dasd_profile_info_t *data;
int rc = 0;
data = kmalloc_obj(*data);
if (!data)
return -ENOMEM;
spin_lock_bh(&block->profile.lock);
if (block->profile.data) {
data->dasd_io_reqs = block->profile.data->dasd_io_reqs;
data->dasd_io_sects = block->profile.data->dasd_io_sects;
memcpy(data->dasd_io_secs, block->profile.data->dasd_io_secs,
sizeof(data->dasd_io_secs));
memcpy(data->dasd_io_times, block->profile.data->dasd_io_times,
sizeof(data->dasd_io_times));
memcpy(data->dasd_io_timps, block->profile.data->dasd_io_timps,
sizeof(data->dasd_io_timps));
memcpy(data->dasd_io_time1, block->profile.data->dasd_io_time1,
sizeof(data->dasd_io_time1));
memcpy(data->dasd_io_time2, block->profile.data->dasd_io_time2,
sizeof(data->dasd_io_time2));
memcpy(data->dasd_io_time2ps,
block->profile.data->dasd_io_time2ps,
sizeof(data->dasd_io_time2ps));
memcpy(data->dasd_io_time3, block->profile.data->dasd_io_time3,
sizeof(data->dasd_io_time3));
memcpy(data->dasd_io_nr_req,
block->profile.data->dasd_io_nr_req,
sizeof(data->dasd_io_nr_req));
spin_unlock_bh(&block->profile.lock);
} else {
spin_unlock_bh(&block->profile.lock);
rc = -EIO;
goto out;
}
if (copy_to_user(argp, data, sizeof(*data)))
rc = -EFAULT;
out:
kfree(data);
return rc;
}
#else
static int dasd_ioctl_reset_profile(struct dasd_block *block)
{
return -ENOTTY;
}
static int dasd_ioctl_read_profile(struct dasd_block *block, void __user *argp)
{
return -ENOTTY;
}
#endif
/*
* Return dasd information. Used for BIODASDINFO and BIODASDINFO2.
*/
static int __dasd_ioctl_information(struct dasd_block *block,
struct dasd_information2_t *dasd_info)
{
struct subchannel_id sch_id;
struct ccw_dev_id dev_id;
struct dasd_device *base;
struct ccw_device *cdev;
struct list_head *l;
unsigned long flags;
int rc;
base = block->base;
if (!base->discipline || !base->discipline->fill_info)
return -EINVAL;
rc = base->discipline->fill_info(base, dasd_info);
if (rc)
return rc;
cdev = base->cdev;
ccw_device_get_id(cdev, &dev_id);
ccw_device_get_schid(cdev, &sch_id);
dasd_info->devno = dev_id.devno;
dasd_info->schid = sch_id.sch_no;
dasd_info->cu_type = cdev->id.cu_type;
dasd_info->cu_model = cdev->id.cu_model;
dasd_info->dev_type = cdev->id.dev_type;
dasd_info->dev_model = cdev->id.dev_model;
dasd_info->status = base->state;
/*
* The open_count is increased for every opener, that includes
* the blkdev_get in dasd_scan_partitions.
* This must be hidden from user-space.
*/
dasd_info->open_count = atomic_read(&block->open_count);
if (!block->bdev_file)
dasd_info->open_count++;
/*
* check if device is really formatted
* LDL / CDL was returned by 'fill_info'
*/
if ((base->state < DASD_STATE_READY) ||
(dasd_check_blocksize(block->bp_block)))
dasd_info->format = DASD_FORMAT_NONE;
dasd_info->features |=
((base->features & DASD_FEATURE_READONLY) != 0);
memcpy(dasd_info->type, base->discipline->name, 4);
spin_lock_irqsave(get_ccwdev_lock(base->cdev), flags);
list_for_each(l, &base->ccw_queue)
dasd_info->chanq_len++;
spin_unlock_irqrestore(get_ccwdev_lock(base->cdev), flags);
return 0;
}
static int dasd_ioctl_information(struct dasd_block *block, void __user *argp,
size_t copy_size)
{
struct dasd_information2_t *dasd_info;
int error;
dasd_info = kzalloc_obj(*dasd_info);
if (!dasd_info)
return -ENOMEM;
error = __dasd_ioctl_information(block, dasd_info);
if (!error && copy_to_user(argp, dasd_info, copy_size))
error = -EFAULT;
kfree(dasd_info);
return error;
}
/*
* Set read only
*/
int dasd_set_read_only(struct block_device *bdev, bool ro)
{
struct dasd_device *base;
int rc;
/* do not manipulate hardware state for partitions */
if (bdev_is_partition(bdev))
return 0;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
if (!ro && test_bit(DASD_FLAG_DEVICE_RO, &base->flags))
rc = -EROFS;
else
rc = dasd_set_feature(base->cdev, DASD_FEATURE_READONLY, ro);
dasd_put_device(base);
return rc;
}
static int dasd_ioctl_readall_cmb(struct dasd_block *block, unsigned int cmd,
struct cmbdata __user *argp)
{
size_t size = _IOC_SIZE(cmd);
struct cmbdata data;
int ret;
ret = cmf_readall(block->base->cdev, &data);
if (!ret && copy_to_user(argp, &data, min(size, sizeof(*argp))))
return -EFAULT;
return ret;
}
int dasd_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg)
{
struct dasd_block *block;
struct dasd_device *base;
void __user *argp;
int rc;
argp = (void __user *)arg;
if ((_IOC_DIR(cmd) != _IOC_NONE) && !arg)
return -EINVAL;
base = dasd_device_from_gendisk(bdev->bd_disk);
if (!base)
return -ENODEV;
block = base->block;
rc = 0;
switch (cmd) {
case BIODASDDISABLE:
rc = dasd_ioctl_disable(bdev);
break;
case BIODASDENABLE:
rc = dasd_ioctl_enable(bdev);
break;
case BIODASDQUIESCE:
rc = dasd_ioctl_quiesce(block);
break;
case BIODASDRESUME:
rc = dasd_ioctl_resume(block);
break;
case BIODASDABORTIO:
rc = dasd_ioctl_abortio(block);
break;
case BIODASDALLOWIO:
rc = dasd_ioctl_allowio(block);
break;
case BIODASDFMT:
rc = dasd_ioctl_format(bdev, argp);
break;
case BIODASDCHECKFMT:
rc = dasd_ioctl_check_format(bdev, argp);
break;
case BIODASDINFO:
rc = dasd_ioctl_information(block, argp,
sizeof(struct dasd_information_t));
break;
case BIODASDINFO2:
rc = dasd_ioctl_information(block, argp,
sizeof(struct dasd_information2_t));
break;
case BIODASDPRRD:
rc = dasd_ioctl_read_profile(block, argp);
break;
case BIODASDPRRST:
rc = dasd_ioctl_reset_profile(block);
break;
case DASDAPIVER:
rc = dasd_ioctl_api_version(argp);
break;
case BIODASDCMFENABLE:
rc = enable_cmf(base->cdev);
break;
case BIODASDCMFDISABLE:
rc = disable_cmf(base->cdev);
break;
case BIODASDREADALLCMB:
rc = dasd_ioctl_readall_cmb(block, cmd, argp);
break;
case BIODASDRAS:
rc = dasd_ioctl_release_space(bdev, argp);
break;
case BIODASDCOPYPAIRSWAP:
rc = dasd_ioctl_copy_pair_swap(bdev, argp);
break;
default:
/* if the discipline has an ioctl method try it. */
rc = -ENOTTY;
if (base->discipline->ioctl)
rc = base->discipline->ioctl(block, cmd, argp);
}
dasd_put_device(base);
return rc;
}
/**
* dasd_biodasdinfo() - fill out the dasd information structure
* @disk: [in] pointer to gendisk structure that references a DASD
* @info: [out] pointer to the dasd_information2_t structure
*
* Provide access to DASD specific information.
* The gendisk structure is checked if it belongs to the DASD driver by
* comparing the gendisk->fops pointer.
* If it does not belong to the DASD driver -EINVAL is returned.
* Otherwise the provided dasd_information2_t structure is filled out.
*
* Returns:
* %0 on success and a negative error value on failure.
*/
int dasd_biodasdinfo(struct gendisk *disk, struct dasd_information2_t *info)
{
struct dasd_device *base;
int error;
if (disk->fops != &dasd_device_operations)
return -EINVAL;
base = dasd_device_from_gendisk(disk);
if (!base)
return -ENODEV;
error = __dasd_ioctl_information(base->block, info);
dasd_put_device(base);
return error;
}
/* export that symbol_get in partition detection is possible */
EXPORT_SYMBOL_GPL(dasd_biodasdinfo);
+368
View File
@@ -0,0 +1,368 @@
// 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, 2002
*
* /proc interface for the dasd driver.
*
*/
#include <linux/ctype.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/seq_file.h>
#include <linux/vmalloc.h>
#include <linux/proc_fs.h>
#include <asm/debug.h>
#include <linux/uaccess.h>
#include "dasd_int.h"
static struct proc_dir_entry *dasd_proc_root_entry = NULL;
static struct proc_dir_entry *dasd_devices_entry = NULL;
static struct proc_dir_entry *dasd_statistics_entry = NULL;
static int
dasd_devices_show(struct seq_file *m, void *v)
{
struct dasd_device *device;
struct dasd_block *block;
char *substr;
device = dasd_device_from_devindex((unsigned long) v - 1);
if (IS_ERR(device))
return 0;
if (device->block)
block = device->block;
else {
dasd_put_device(device);
return 0;
}
/* Print device number. */
seq_printf(m, "%s", dev_name(&device->cdev->dev));
/* Print discipline string. */
if (device->discipline != NULL)
seq_printf(m, "(%s)", device->discipline->name);
else
seq_printf(m, "(none)");
/* Print kdev. */
if (block->gdp)
seq_printf(m, " at (%3d:%6d)",
MAJOR(disk_devt(block->gdp)),
MINOR(disk_devt(block->gdp)));
else
seq_printf(m, " at (???:??????)");
/* Print device name. */
if (block->gdp)
seq_printf(m, " is %-8s", block->gdp->disk_name);
else
seq_printf(m, " is ????????");
/* Print devices features. */
substr = (device->features & DASD_FEATURE_READONLY) ? "(ro)" : " ";
seq_printf(m, "%4s: ", substr);
/* Print device status information. */
switch (device->state) {
case DASD_STATE_NEW:
seq_printf(m, "new");
break;
case DASD_STATE_KNOWN:
seq_printf(m, "detected");
break;
case DASD_STATE_BASIC:
seq_printf(m, "basic");
break;
case DASD_STATE_UNFMT:
seq_printf(m, "unformatted");
break;
case DASD_STATE_READY:
case DASD_STATE_ONLINE:
seq_printf(m, "active ");
if (dasd_check_blocksize(block->bp_block))
seq_printf(m, "n/f ");
else
seq_printf(m,
"at blocksize: %u, %lu blocks, %lu MB",
block->bp_block, block->blocks,
((block->bp_block >> 9) *
block->blocks) >> 11);
break;
default:
seq_printf(m, "no stat");
break;
}
dasd_put_device(device);
if (dasd_probeonly)
seq_printf(m, "(probeonly)");
seq_printf(m, "\n");
return 0;
}
static void *dasd_devices_start(struct seq_file *m, loff_t *pos)
{
if (*pos >= dasd_max_devindex)
return NULL;
return (void *)((unsigned long) *pos + 1);
}
static void *dasd_devices_next(struct seq_file *m, void *v, loff_t *pos)
{
++*pos;
return dasd_devices_start(m, pos);
}
static void dasd_devices_stop(struct seq_file *m, void *v)
{
}
static const struct seq_operations dasd_devices_seq_ops = {
.start = dasd_devices_start,
.next = dasd_devices_next,
.stop = dasd_devices_stop,
.show = dasd_devices_show,
};
#ifdef CONFIG_DASD_PROFILE
static int dasd_stats_all_block_on(void)
{
int i, rc;
struct dasd_device *device;
rc = 0;
for (i = 0; i < dasd_max_devindex; ++i) {
device = dasd_device_from_devindex(i);
if (IS_ERR(device))
continue;
if (device->block)
rc = dasd_profile_on(&device->block->profile);
dasd_put_device(device);
if (rc)
return rc;
}
return 0;
}
static void dasd_stats_all_block_off(void)
{
int i;
struct dasd_device *device;
for (i = 0; i < dasd_max_devindex; ++i) {
device = dasd_device_from_devindex(i);
if (IS_ERR(device))
continue;
if (device->block)
dasd_profile_off(&device->block->profile);
dasd_put_device(device);
}
}
static void dasd_stats_all_block_reset(void)
{
int i;
struct dasd_device *device;
for (i = 0; i < dasd_max_devindex; ++i) {
device = dasd_device_from_devindex(i);
if (IS_ERR(device))
continue;
if (device->block)
dasd_profile_reset(&device->block->profile);
dasd_put_device(device);
}
}
static void dasd_statistics_array(struct seq_file *m, unsigned int *array, int factor)
{
int i;
for (i = 0; i < 32; i++) {
seq_printf(m, "%7d ", array[i] / factor);
if (i == 15)
seq_putc(m, '\n');
}
seq_putc(m, '\n');
}
#endif /* CONFIG_DASD_PROFILE */
static int dasd_stats_proc_show(struct seq_file *m, void *v)
{
#ifdef CONFIG_DASD_PROFILE
struct dasd_profile_info *prof;
int factor;
spin_lock_bh(&dasd_global_profile.lock);
prof = dasd_global_profile.data;
if (!prof) {
spin_unlock_bh(&dasd_global_profile.lock);
seq_printf(m, "Statistics are off - they might be "
"switched on using 'echo set on > "
"/proc/dasd/statistics'\n");
return 0;
}
/* prevent counter 'overflow' on output */
for (factor = 1; (prof->dasd_io_reqs / factor) > 9999999;
factor *= 10);
seq_printf(m, "%d dasd I/O requests\n", prof->dasd_io_reqs);
seq_printf(m, "with %u sectors(512B each)\n",
prof->dasd_io_sects);
seq_printf(m, "Scale Factor is %d\n", factor);
seq_printf(m,
" __<4 ___8 __16 __32 __64 _128 "
" _256 _512 __1k __2k __4k __8k "
" _16k _32k _64k 128k\n");
seq_printf(m,
" _256 _512 __1M __2M __4M __8M "
" _16M _32M _64M 128M 256M 512M "
" __1G __2G __4G " " _>4G\n");
seq_printf(m, "Histogram of sizes (512B secs)\n");
dasd_statistics_array(m, prof->dasd_io_secs, factor);
seq_printf(m, "Histogram of I/O times (microseconds)\n");
dasd_statistics_array(m, prof->dasd_io_times, factor);
seq_printf(m, "Histogram of I/O times per sector\n");
dasd_statistics_array(m, prof->dasd_io_timps, factor);
seq_printf(m, "Histogram of I/O time till ssch\n");
dasd_statistics_array(m, prof->dasd_io_time1, factor);
seq_printf(m, "Histogram of I/O time between ssch and irq\n");
dasd_statistics_array(m, prof->dasd_io_time2, factor);
seq_printf(m, "Histogram of I/O time between ssch "
"and irq per sector\n");
dasd_statistics_array(m, prof->dasd_io_time2ps, factor);
seq_printf(m, "Histogram of I/O time between irq and end\n");
dasd_statistics_array(m, prof->dasd_io_time3, factor);
seq_printf(m, "# of req in chanq at enqueuing (1..32) \n");
dasd_statistics_array(m, prof->dasd_io_nr_req, factor);
spin_unlock_bh(&dasd_global_profile.lock);
#else
seq_printf(m, "Statistics are not activated in this kernel\n");
#endif
return 0;
}
static int dasd_stats_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, dasd_stats_proc_show, NULL);
}
static ssize_t dasd_stats_proc_write(struct file *file,
const char __user *user_buf, size_t user_len, loff_t *pos)
{
#ifdef CONFIG_DASD_PROFILE
char *buffer, *str;
int rc;
if (user_len > 65536)
user_len = 65536;
buffer = dasd_get_user_string(user_buf, user_len);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
/* check for valid verbs */
str = skip_spaces(buffer);
if (strncmp(str, "set", 3) == 0 && isspace(str[3])) {
/* 'set xxx' was given */
str = skip_spaces(str + 4);
if (strcmp(str, "on") == 0) {
/* switch on statistics profiling */
rc = dasd_stats_all_block_on();
if (rc) {
dasd_stats_all_block_off();
goto out_error;
}
rc = dasd_profile_on(&dasd_global_profile);
if (rc) {
dasd_stats_all_block_off();
goto out_error;
}
dasd_profile_reset(&dasd_global_profile);
dasd_global_profile_level = DASD_PROFILE_ON;
pr_info("The statistics feature has been switched "
"on\n");
} else if (strcmp(str, "off") == 0) {
/* switch off statistics profiling */
dasd_global_profile_level = DASD_PROFILE_OFF;
dasd_profile_off(&dasd_global_profile);
dasd_stats_all_block_off();
pr_info("The statistics feature has been switched "
"off\n");
} else
goto out_parse_error;
} else if (strncmp(str, "reset", 5) == 0) {
/* reset the statistics */
dasd_profile_reset(&dasd_global_profile);
dasd_stats_all_block_reset();
pr_info("The statistics have been reset\n");
} else
goto out_parse_error;
vfree(buffer);
return user_len;
out_parse_error:
rc = -EINVAL;
pr_warn("%s is not a supported value for /proc/dasd/statistics\n", str);
out_error:
vfree(buffer);
return rc;
#else
pr_warn("/proc/dasd/statistics: is not activated in this kernel\n");
return user_len;
#endif /* CONFIG_DASD_PROFILE */
}
static const struct proc_ops dasd_stats_proc_ops = {
.proc_open = dasd_stats_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
.proc_write = dasd_stats_proc_write,
};
/*
* Create dasd proc-fs entries.
* In case creation failed, cleanup and return -ENOENT.
*/
int
dasd_proc_init(void)
{
dasd_proc_root_entry = proc_mkdir("dasd", NULL);
if (!dasd_proc_root_entry)
goto out_nodasd;
dasd_devices_entry = proc_create_seq("devices", 0444,
dasd_proc_root_entry,
&dasd_devices_seq_ops);
if (!dasd_devices_entry)
goto out_nodevices;
dasd_statistics_entry = proc_create("statistics",
S_IFREG | S_IRUGO | S_IWUSR,
dasd_proc_root_entry,
&dasd_stats_proc_ops);
if (!dasd_statistics_entry)
goto out_nostatistics;
return 0;
out_nostatistics:
remove_proc_entry("devices", dasd_proc_root_entry);
out_nodevices:
remove_proc_entry("dasd", NULL);
dasd_proc_root_entry = NULL;
out_nodasd:
return -ENOENT;
}
void
dasd_proc_exit(void)
{
if (!dasd_proc_root_entry)
return;
remove_proc_entry("devices", dasd_proc_root_entry);
remove_proc_entry("statistics", dasd_proc_root_entry);
remove_proc_entry("dasd", NULL);
dasd_proc_root_entry = NULL;
}
File diff suppressed because it is too large Load Diff
+581
View File
@@ -0,0 +1,581 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Block driver for s390 storage class memory.
*
* Copyright IBM Corp. 2012
* Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com>
*/
#define pr_fmt(fmt) "scm_block: " fmt
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/mempool.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/io.h>
#include <asm/eadm.h>
#include "scm_blk.h"
debug_info_t *scm_debug;
static int scm_major;
static mempool_t *aidaw_pool;
static DEFINE_SPINLOCK(list_lock);
static LIST_HEAD(inactive_requests);
static unsigned int nr_requests = 64;
static unsigned int nr_requests_per_io = 8;
static atomic_t nr_devices = ATOMIC_INIT(0);
module_param(nr_requests, uint, S_IRUGO);
MODULE_PARM_DESC(nr_requests, "Number of parallel requests.");
module_param(nr_requests_per_io, uint, S_IRUGO);
MODULE_PARM_DESC(nr_requests_per_io, "Number of requests per IO.");
MODULE_DESCRIPTION("Block driver for s390 storage class memory.");
MODULE_LICENSE("GPL");
MODULE_ALIAS("scm:scmdev*");
static void __scm_free_rq(struct scm_request *scmrq)
{
struct aob_rq_header *aobrq = to_aobrq(scmrq);
free_page((unsigned long) scmrq->aob);
kfree(scmrq->request);
kfree(aobrq);
}
static void scm_free_rqs(void)
{
struct list_head *iter, *safe;
struct scm_request *scmrq;
spin_lock_irq(&list_lock);
list_for_each_safe(iter, safe, &inactive_requests) {
scmrq = list_entry(iter, struct scm_request, list);
list_del(&scmrq->list);
__scm_free_rq(scmrq);
}
spin_unlock_irq(&list_lock);
mempool_destroy(aidaw_pool);
}
static int __scm_alloc_rq(void)
{
struct aob_rq_header *aobrq;
struct scm_request *scmrq;
aobrq = kzalloc(sizeof(*aobrq) + sizeof(*scmrq), GFP_KERNEL);
if (!aobrq)
return -ENOMEM;
scmrq = (void *) aobrq->data;
scmrq->aob = (void *) get_zeroed_page(GFP_DMA);
if (!scmrq->aob)
goto free;
scmrq->request = kzalloc_objs(scmrq->request[0], nr_requests_per_io);
if (!scmrq->request)
goto free;
INIT_LIST_HEAD(&scmrq->list);
spin_lock_irq(&list_lock);
list_add(&scmrq->list, &inactive_requests);
spin_unlock_irq(&list_lock);
return 0;
free:
__scm_free_rq(scmrq);
return -ENOMEM;
}
static int scm_alloc_rqs(unsigned int nrqs)
{
int ret = 0;
aidaw_pool = mempool_create_page_pool(max(nrqs/8, 1U), 0);
if (!aidaw_pool)
return -ENOMEM;
while (nrqs-- && !ret)
ret = __scm_alloc_rq();
return ret;
}
static struct scm_request *scm_request_fetch(void)
{
struct scm_request *scmrq = NULL;
spin_lock_irq(&list_lock);
if (list_empty(&inactive_requests))
goto out;
scmrq = list_first_entry(&inactive_requests, struct scm_request, list);
list_del(&scmrq->list);
out:
spin_unlock_irq(&list_lock);
return scmrq;
}
static void scm_request_done(struct scm_request *scmrq)
{
unsigned long flags;
struct msb *msb;
u64 aidaw;
int i;
for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++) {
msb = &scmrq->aob->msb[i];
aidaw = (u64)dma64_to_virt(msb->data_addr);
if ((msb->flags & MSB_FLAG_IDA) && aidaw &&
IS_ALIGNED(aidaw, PAGE_SIZE))
mempool_free(virt_to_page((void *)aidaw), aidaw_pool);
}
spin_lock_irqsave(&list_lock, flags);
list_add(&scmrq->list, &inactive_requests);
spin_unlock_irqrestore(&list_lock, flags);
}
static bool scm_permit_request(struct scm_blk_dev *bdev, struct request *req)
{
return rq_data_dir(req) != WRITE || bdev->state != SCM_WR_PROHIBIT;
}
static inline struct aidaw *scm_aidaw_alloc(void)
{
struct page *page = mempool_alloc(aidaw_pool, GFP_ATOMIC);
return page ? page_address(page) : NULL;
}
static inline unsigned long scm_aidaw_bytes(struct aidaw *aidaw)
{
unsigned long _aidaw = (unsigned long) aidaw;
unsigned long bytes = ALIGN(_aidaw, PAGE_SIZE) - _aidaw;
return (bytes / sizeof(*aidaw)) * PAGE_SIZE;
}
struct aidaw *scm_aidaw_fetch(struct scm_request *scmrq, unsigned int bytes)
{
struct aidaw *aidaw;
if (scm_aidaw_bytes(scmrq->next_aidaw) >= bytes)
return scmrq->next_aidaw;
aidaw = scm_aidaw_alloc();
if (aidaw)
memset(aidaw, 0, PAGE_SIZE);
return aidaw;
}
static int scm_request_prepare(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
struct scm_device *scmdev = bdev->gendisk->private_data;
int pos = scmrq->aob->request.msb_count;
struct msb *msb = &scmrq->aob->msb[pos];
struct request *req = scmrq->request[pos];
struct req_iterator iter;
struct aidaw *aidaw;
struct bio_vec bv;
aidaw = scm_aidaw_fetch(scmrq, blk_rq_bytes(req));
if (!aidaw)
return -ENOMEM;
msb->bs = MSB_BS_4K;
scmrq->aob->request.msb_count++;
msb->scm_addr = scmdev->address + ((u64) blk_rq_pos(req) << 9);
msb->oc = (rq_data_dir(req) == READ) ? MSB_OC_READ : MSB_OC_WRITE;
msb->flags |= MSB_FLAG_IDA;
msb->data_addr = virt_to_dma64(aidaw);
rq_for_each_segment(bv, req, iter) {
WARN_ON(bv.bv_offset);
msb->blk_count += bv.bv_len >> 12;
aidaw->data_addr = virt_to_dma64(page_address(bv.bv_page));
aidaw++;
}
scmrq->next_aidaw = aidaw;
return 0;
}
static inline void scm_request_set(struct scm_request *scmrq,
struct request *req)
{
scmrq->request[scmrq->aob->request.msb_count] = req;
}
static inline void scm_request_init(struct scm_blk_dev *bdev,
struct scm_request *scmrq)
{
struct aob_rq_header *aobrq = to_aobrq(scmrq);
struct aob *aob = scmrq->aob;
memset(scmrq->request, 0,
nr_requests_per_io * sizeof(scmrq->request[0]));
memset(aob, 0, sizeof(*aob));
aobrq->scmdev = bdev->scmdev;
aob->request.cmd_code = ARQB_CMD_MOVE;
aob->request.data = (u64) aobrq;
scmrq->bdev = bdev;
scmrq->retries = 4;
scmrq->error = BLK_STS_OK;
/* We don't use all msbs - place aidaws at the end of the aob page. */
scmrq->next_aidaw = (void *) &aob->msb[nr_requests_per_io];
}
static void scm_request_requeue(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
int i;
for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++)
blk_mq_requeue_request(scmrq->request[i], false);
atomic_dec(&bdev->queued_reqs);
scm_request_done(scmrq);
blk_mq_kick_requeue_list(bdev->rq);
}
static void scm_request_finish(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
blk_status_t *error;
int i;
for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++) {
error = blk_mq_rq_to_pdu(scmrq->request[i]);
*error = scmrq->error;
if (likely(!blk_should_fake_timeout(scmrq->request[i]->q)))
blk_mq_complete_request(scmrq->request[i]);
}
atomic_dec(&bdev->queued_reqs);
scm_request_done(scmrq);
}
static void scm_request_start(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
atomic_inc(&bdev->queued_reqs);
if (eadm_start_aob(scmrq->aob)) {
SCM_LOG(5, "no subchannel");
scm_request_requeue(scmrq);
}
}
struct scm_queue {
struct scm_request *scmrq;
spinlock_t lock;
};
static blk_status_t scm_blk_request(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *qd)
{
struct scm_device *scmdev = hctx->queue->queuedata;
struct scm_blk_dev *bdev = dev_get_drvdata(&scmdev->dev);
struct scm_queue *sq = hctx->driver_data;
struct request *req = qd->rq;
struct scm_request *scmrq;
spin_lock(&sq->lock);
if (!scm_permit_request(bdev, req)) {
spin_unlock(&sq->lock);
return BLK_STS_RESOURCE;
}
scmrq = sq->scmrq;
if (!scmrq) {
scmrq = scm_request_fetch();
if (!scmrq) {
SCM_LOG(5, "no request");
spin_unlock(&sq->lock);
return BLK_STS_RESOURCE;
}
scm_request_init(bdev, scmrq);
sq->scmrq = scmrq;
}
scm_request_set(scmrq, req);
if (scm_request_prepare(scmrq)) {
SCM_LOG(5, "aidaw alloc failed");
scm_request_set(scmrq, NULL);
if (scmrq->aob->request.msb_count)
scm_request_start(scmrq);
sq->scmrq = NULL;
spin_unlock(&sq->lock);
return BLK_STS_RESOURCE;
}
blk_mq_start_request(req);
if (qd->last || scmrq->aob->request.msb_count == nr_requests_per_io) {
scm_request_start(scmrq);
sq->scmrq = NULL;
}
spin_unlock(&sq->lock);
return BLK_STS_OK;
}
static int scm_blk_init_hctx(struct blk_mq_hw_ctx *hctx, void *data,
unsigned int idx)
{
struct scm_queue *qd = kzalloc_obj(*qd);
if (!qd)
return -ENOMEM;
spin_lock_init(&qd->lock);
hctx->driver_data = qd;
return 0;
}
static void scm_blk_exit_hctx(struct blk_mq_hw_ctx *hctx, unsigned int idx)
{
struct scm_queue *qd = hctx->driver_data;
WARN_ON(qd->scmrq);
kfree(hctx->driver_data);
hctx->driver_data = NULL;
}
static void __scmrq_log_error(struct scm_request *scmrq)
{
struct aob *aob = scmrq->aob;
if (scmrq->error == BLK_STS_TIMEOUT)
SCM_LOG(1, "Request timeout");
else {
SCM_LOG(1, "Request error");
SCM_LOG_HEX(1, &aob->response, sizeof(aob->response));
}
if (scmrq->retries)
SCM_LOG(1, "Retry request");
else
pr_err("An I/O operation to SCM failed with rc=%d\n",
scmrq->error);
}
static void scm_blk_handle_error(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
unsigned long flags;
if (scmrq->error != BLK_STS_IOERR)
goto restart;
/* For -EIO the response block is valid. */
switch (scmrq->aob->response.eqc) {
case EQC_WR_PROHIBIT:
spin_lock_irqsave(&bdev->lock, flags);
if (bdev->state != SCM_WR_PROHIBIT)
pr_info("%lx: Write access to the SCM increment is suspended\n",
(unsigned long) bdev->scmdev->address);
bdev->state = SCM_WR_PROHIBIT;
spin_unlock_irqrestore(&bdev->lock, flags);
goto requeue;
default:
break;
}
restart:
if (!eadm_start_aob(scmrq->aob))
return;
requeue:
scm_request_requeue(scmrq);
}
void scm_blk_irq(struct scm_device *scmdev, void *data, blk_status_t error)
{
struct scm_request *scmrq = data;
scmrq->error = error;
if (error) {
__scmrq_log_error(scmrq);
if (scmrq->retries-- > 0) {
scm_blk_handle_error(scmrq);
return;
}
}
scm_request_finish(scmrq);
}
static void scm_blk_request_done(struct request *req)
{
blk_status_t *error = blk_mq_rq_to_pdu(req);
blk_mq_end_request(req, *error);
}
static const struct block_device_operations scm_blk_devops = {
.owner = THIS_MODULE,
};
static const struct blk_mq_ops scm_mq_ops = {
.queue_rq = scm_blk_request,
.complete = scm_blk_request_done,
.init_hctx = scm_blk_init_hctx,
.exit_hctx = scm_blk_exit_hctx,
};
int scm_blk_dev_setup(struct scm_blk_dev *bdev, struct scm_device *scmdev)
{
struct queue_limits lim = {
.logical_block_size = 1 << 12,
};
unsigned int devindex;
int len, ret;
lim.max_segments = min(scmdev->nr_max_block,
(unsigned int) (PAGE_SIZE / sizeof(struct aidaw)));
lim.max_hw_sectors = lim.max_segments << 3; /* 8 * 512 = blk_size */
devindex = atomic_inc_return(&nr_devices) - 1;
/* scma..scmz + scmaa..scmzz */
if (devindex > 701) {
ret = -ENODEV;
goto out;
}
bdev->scmdev = scmdev;
bdev->state = SCM_OPER;
spin_lock_init(&bdev->lock);
atomic_set(&bdev->queued_reqs, 0);
bdev->tag_set.ops = &scm_mq_ops;
bdev->tag_set.cmd_size = sizeof(blk_status_t);
bdev->tag_set.nr_hw_queues = nr_requests;
bdev->tag_set.queue_depth = nr_requests_per_io * nr_requests;
bdev->tag_set.numa_node = NUMA_NO_NODE;
ret = blk_mq_alloc_tag_set(&bdev->tag_set);
if (ret)
goto out;
bdev->gendisk = blk_mq_alloc_disk(&bdev->tag_set, &lim, scmdev);
if (IS_ERR(bdev->gendisk)) {
ret = PTR_ERR(bdev->gendisk);
goto out_tag;
}
bdev->gendisk->private_data = scmdev;
bdev->gendisk->fops = &scm_blk_devops;
bdev->gendisk->major = scm_major;
bdev->gendisk->first_minor = devindex * SCM_NR_PARTS;
bdev->gendisk->minors = SCM_NR_PARTS;
len = snprintf(bdev->gendisk->disk_name, DISK_NAME_LEN, "scm");
if (devindex > 25) {
len += snprintf(bdev->gendisk->disk_name + len,
DISK_NAME_LEN - len, "%c",
'a' + (devindex / 26) - 1);
devindex = devindex % 26;
}
snprintf(bdev->gendisk->disk_name + len, DISK_NAME_LEN - len, "%c",
'a' + devindex);
/* 512 byte sectors */
set_capacity(bdev->gendisk, scmdev->size >> 9);
ret = device_add_disk(&scmdev->dev, bdev->gendisk, NULL);
if (ret)
goto out_cleanup_disk;
return 0;
out_cleanup_disk:
put_disk(bdev->gendisk);
out_tag:
blk_mq_free_tag_set(&bdev->tag_set);
out:
atomic_dec(&nr_devices);
return ret;
}
void scm_blk_dev_cleanup(struct scm_blk_dev *bdev)
{
del_gendisk(bdev->gendisk);
put_disk(bdev->gendisk);
blk_mq_free_tag_set(&bdev->tag_set);
}
void scm_blk_set_available(struct scm_blk_dev *bdev)
{
unsigned long flags;
spin_lock_irqsave(&bdev->lock, flags);
if (bdev->state == SCM_WR_PROHIBIT)
pr_info("%lx: Write access to the SCM increment is restored\n",
(unsigned long) bdev->scmdev->address);
bdev->state = SCM_OPER;
spin_unlock_irqrestore(&bdev->lock, flags);
}
static bool __init scm_blk_params_valid(void)
{
if (!nr_requests_per_io || nr_requests_per_io > 64)
return false;
return true;
}
static int __init scm_blk_init(void)
{
int ret = -EINVAL;
if (!scm_blk_params_valid())
goto out;
ret = register_blkdev(0, "scm");
if (ret < 0)
goto out;
scm_major = ret;
ret = scm_alloc_rqs(nr_requests);
if (ret)
goto out_free;
scm_debug = debug_register("scm_log", 16, 1, 16);
if (!scm_debug) {
ret = -ENOMEM;
goto out_free;
}
debug_register_view(scm_debug, &debug_hex_ascii_view);
debug_set_level(scm_debug, 2);
ret = scm_drv_init();
if (ret)
goto out_dbf;
return ret;
out_dbf:
debug_unregister(scm_debug);
out_free:
scm_free_rqs();
unregister_blkdev(scm_major, "scm");
out:
return ret;
}
module_init(scm_blk_init);
static void __exit scm_blk_cleanup(void)
{
scm_drv_cleanup();
debug_unregister(scm_debug);
scm_free_rqs();
unregister_blkdev(scm_major, "scm");
}
module_exit(scm_blk_cleanup);
+76
View File
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef SCM_BLK_H
#define SCM_BLK_H
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/list.h>
#include <asm/debug.h>
#include <asm/eadm.h>
#define SCM_NR_PARTS 8
#define SCM_QUEUE_DELAY 5
struct scm_blk_dev {
struct request_queue *rq;
struct gendisk *gendisk;
struct blk_mq_tag_set tag_set;
struct scm_device *scmdev;
spinlock_t lock;
atomic_t queued_reqs;
enum {SCM_OPER, SCM_WR_PROHIBIT} state;
struct list_head finished_requests;
};
struct scm_request {
struct scm_blk_dev *bdev;
struct aidaw *next_aidaw;
struct request **request;
struct aob *aob;
struct list_head list;
u8 retries;
blk_status_t error;
};
#define to_aobrq(rq) container_of((void *) rq, struct aob_rq_header, data)
int scm_blk_dev_setup(struct scm_blk_dev *, struct scm_device *);
void scm_blk_dev_cleanup(struct scm_blk_dev *);
void scm_blk_set_available(struct scm_blk_dev *);
void scm_blk_irq(struct scm_device *, void *, blk_status_t);
struct aidaw *scm_aidaw_fetch(struct scm_request *scmrq, unsigned int bytes);
int scm_drv_init(void);
void scm_drv_cleanup(void);
extern debug_info_t *scm_debug;
#define SCM_LOG(imp, txt) do { \
debug_text_event(scm_debug, imp, txt); \
} while (0)
static inline void SCM_LOG_HEX(int level, void *data, int length)
{
debug_event(scm_debug, level, data, length);
}
static inline void SCM_LOG_STATE(int level, struct scm_device *scmdev)
{
struct {
u64 address;
u8 oper_state;
u8 rank;
} __packed data = {
.address = scmdev->address,
.oper_state = scmdev->attrs.oper_state,
.rank = scmdev->attrs.rank,
};
SCM_LOG_HEX(level, &data, sizeof(data));
}
#endif /* SCM_BLK_H */
+90
View File
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Device driver for s390 storage class memory.
*
* Copyright IBM Corp. 2012
* Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com>
*/
#define pr_fmt(fmt) "scm_block: " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <asm/eadm.h>
#include "scm_blk.h"
static void scm_notify(struct scm_device *scmdev, enum scm_event event)
{
struct scm_blk_dev *bdev = dev_get_drvdata(&scmdev->dev);
switch (event) {
case SCM_CHANGE:
pr_info("%lx: The capabilities of the SCM increment changed\n",
(unsigned long) scmdev->address);
SCM_LOG(2, "State changed");
SCM_LOG_STATE(2, scmdev);
break;
case SCM_AVAIL:
SCM_LOG(2, "Increment available");
SCM_LOG_STATE(2, scmdev);
scm_blk_set_available(bdev);
break;
}
}
static int scm_probe(struct scm_device *scmdev)
{
struct scm_blk_dev *bdev;
int ret;
SCM_LOG(2, "probe");
SCM_LOG_STATE(2, scmdev);
if (scmdev->attrs.oper_state != OP_STATE_GOOD)
return -EINVAL;
bdev = kzalloc_obj(*bdev);
if (!bdev)
return -ENOMEM;
dev_set_drvdata(&scmdev->dev, bdev);
ret = scm_blk_dev_setup(bdev, scmdev);
if (ret) {
dev_set_drvdata(&scmdev->dev, NULL);
kfree(bdev);
goto out;
}
out:
return ret;
}
static void scm_remove(struct scm_device *scmdev)
{
struct scm_blk_dev *bdev = dev_get_drvdata(&scmdev->dev);
scm_blk_dev_cleanup(bdev);
dev_set_drvdata(&scmdev->dev, NULL);
kfree(bdev);
}
static struct scm_driver scm_drv = {
.drv = {
.name = "scm_block",
.owner = THIS_MODULE,
},
.notify = scm_notify,
.probe = scm_probe,
.remove = scm_remove,
.handler = scm_blk_irq,
};
int __init scm_drv_init(void)
{
return scm_driver_register(&scm_drv);
}
void scm_drv_cleanup(void)
{
scm_driver_unregister(&scm_drv);
}