summaryrefslogtreecommitdiff
path: root/drivers/s390
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390')
-rw-r--r--drivers/s390/block/dasd.c46
-rw-r--r--drivers/s390/block/dasd_devmap.c24
-rw-r--r--drivers/s390/block/dasd_eckd.c372
-rw-r--r--drivers/s390/block/dasd_eckd.h63
-rw-r--r--drivers/s390/block/dasd_genhd.c26
-rw-r--r--drivers/s390/block/dasd_int.h10
-rw-r--r--drivers/s390/block/dcssblk.c1
-rw-r--r--drivers/s390/block/scm_blk.c223
-rw-r--r--drivers/s390/block/scm_blk.h6
-rw-r--r--drivers/s390/block/scm_blk_cluster.c69
-rw-r--r--drivers/s390/block/xpram.c2
-rw-r--r--drivers/s390/char/Kconfig23
-rw-r--r--drivers/s390/char/Makefile3
-rw-r--r--drivers/s390/char/diag_ftp.c237
-rw-r--r--drivers/s390/char/diag_ftp.h21
-rw-r--r--drivers/s390/char/hmcdrv_cache.c252
-rw-r--r--drivers/s390/char/hmcdrv_cache.h24
-rw-r--r--drivers/s390/char/hmcdrv_dev.c368
-rw-r--r--drivers/s390/char/hmcdrv_dev.h14
-rw-r--r--drivers/s390/char/hmcdrv_ftp.c343
-rw-r--r--drivers/s390/char/hmcdrv_ftp.h63
-rw-r--r--drivers/s390/char/hmcdrv_mod.c64
-rw-r--r--drivers/s390/char/monwriter.c1
-rw-r--r--drivers/s390/char/sclp.c1
-rw-r--r--drivers/s390/char/sclp.h2
-rw-r--r--drivers/s390/char/sclp_async.c3
-rw-r--r--drivers/s390/char/sclp_diag.h89
-rw-r--r--drivers/s390/char/sclp_early.c2
-rw-r--r--drivers/s390/char/sclp_ftp.c275
-rw-r--r--drivers/s390/char/sclp_ftp.h21
-rw-r--r--drivers/s390/char/sclp_rw.c13
-rw-r--r--drivers/s390/char/sclp_vt220.c4
-rw-r--r--drivers/s390/char/tape_3590.c4
-rw-r--r--drivers/s390/char/tape_char.c4
-rw-r--r--drivers/s390/char/vmlogrdr.c1
-rw-r--r--drivers/s390/char/zcore.c18
-rw-r--r--drivers/s390/cio/airq.c2
-rw-r--r--drivers/s390/cio/ccwreq.c2
-rw-r--r--drivers/s390/cio/chp.c4
-rw-r--r--drivers/s390/cio/chsc_sch.c2
-rw-r--r--drivers/s390/cio/cio.c8
-rw-r--r--drivers/s390/cio/device_fsm.c4
-rw-r--r--drivers/s390/cio/eadm_sch.c4
-rw-r--r--drivers/s390/crypto/ap_bus.c33
-rw-r--r--drivers/s390/crypto/ap_bus.h7
-rw-r--r--drivers/s390/crypto/zcrypt_api.c7
-rw-r--r--drivers/s390/kvm/kvm_virtio.c20
-rw-r--r--drivers/s390/kvm/virtio_ccw.c210
-rw-r--r--drivers/s390/net/Kconfig2
-rw-r--r--drivers/s390/net/claw.c2
-rw-r--r--drivers/s390/net/ctcm_sysfs.c8
-rw-r--r--drivers/s390/net/lcs.c11
-rw-r--r--drivers/s390/net/qeth_core.h16
-rw-r--r--drivers/s390/net/qeth_core_main.c24
-rw-r--r--drivers/s390/net/qeth_l2_main.c2
-rw-r--r--drivers/s390/net/qeth_l3.h4
-rw-r--r--drivers/s390/net/qeth_l3_main.c12
-rw-r--r--drivers/s390/scsi/zfcp_aux.c6
-rw-r--r--drivers/s390/scsi/zfcp_ccw.c32
-rw-r--r--drivers/s390/scsi/zfcp_def.h3
-rw-r--r--drivers/s390/scsi/zfcp_erp.c7
-rw-r--r--drivers/s390/scsi/zfcp_ext.h1
-rw-r--r--drivers/s390/scsi/zfcp_fc.c52
-rw-r--r--drivers/s390/scsi/zfcp_fc.h14
-rw-r--r--drivers/s390/scsi/zfcp_fsf.c3
-rw-r--r--drivers/s390/scsi/zfcp_scsi.c26
-rw-r--r--drivers/s390/scsi/zfcp_sysfs.c66
67 files changed, 2977 insertions, 309 deletions
diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c
index 5df05f26b7d9..4abf11965484 100644
--- a/drivers/s390/block/dasd.c
+++ b/drivers/s390/block/dasd.c
@@ -1377,6 +1377,20 @@ int dasd_term_IO(struct dasd_ccw_req *cqr)
"I/O error, retry");
break;
case -EINVAL:
+ /*
+ * device not valid so no I/O could be running
+ * handle CQR as termination successful
+ */
+ cqr->status = DASD_CQR_CLEARED;
+ cqr->stopclk = get_tod_clock();
+ cqr->starttime = 0;
+ /* no retries for invalid devices */
+ cqr->retries = -1;
+ DBF_DEV_EVENT(DBF_ERR, device, "%s",
+ "EINVAL, handle as terminated");
+ /* fake rc to success */
+ rc = 0;
+ break;
case -EBUSY:
DBF_DEV_EVENT(DBF_ERR, device, "%s",
"device busy, retry later");
@@ -1660,6 +1674,14 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
device->discipline->check_for_device_change(device, cqr, irb);
dasd_put_device(device);
}
+
+ /* check for for attention message */
+ if (scsw_dstat(&irb->scsw) & DEV_STAT_ATTENTION) {
+ device = dasd_device_from_cdev_locked(cdev);
+ device->discipline->check_attention(device, irb->esw.esw1.lpum);
+ dasd_put_device(device);
+ }
+
if (!cqr)
return;
@@ -1675,11 +1697,8 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
if (cqr->status == DASD_CQR_CLEAR_PENDING &&
scsw_fctl(&irb->scsw) & SCSW_FCTL_CLEAR_FUNC) {
cqr->status = DASD_CQR_CLEARED;
- if (cqr->callback_data == DASD_SLEEPON_START_TAG)
- cqr->callback_data = DASD_SLEEPON_END_TAG;
dasd_device_clear_timer(device);
wake_up(&dasd_flush_wq);
- wake_up(&generic_waitq);
dasd_schedule_device_bh(device);
return;
}
@@ -2261,8 +2280,8 @@ static inline int _wait_for_wakeup_queue(struct list_head *ccw_queue)
static int _dasd_sleep_on_queue(struct list_head *ccw_queue, int interruptible)
{
struct dasd_device *device;
- int rc;
struct dasd_ccw_req *cqr, *n;
+ int rc;
retry:
list_for_each_entry_safe(cqr, n, ccw_queue, blocklist) {
@@ -2310,23 +2329,18 @@ retry:
/*
* for alias devices simplify error recovery and
* return to upper layer
+ * do not skip ERP requests
*/
- if (cqr->startdev != cqr->basedev &&
+ if (cqr->startdev != cqr->basedev && !cqr->refers &&
(cqr->status == DASD_CQR_TERMINATED ||
cqr->status == DASD_CQR_NEED_ERP))
return -EAGAIN;
- else {
- /* normal recovery for basedev IO */
- if (__dasd_sleep_on_erp(cqr)) {
- if (!cqr->status == DASD_CQR_TERMINATED &&
- !cqr->status == DASD_CQR_NEED_ERP)
- break;
- rc = 1;
- }
- }
+
+ /* normal recovery for basedev IO */
+ if (__dasd_sleep_on_erp(cqr))
+ /* handle erp first */
+ goto retry;
}
- if (rc)
- goto retry;
return 0;
}
diff --git a/drivers/s390/block/dasd_devmap.c b/drivers/s390/block/dasd_devmap.c
index 14ba80bfa571..8286f742436b 100644
--- a/drivers/s390/block/dasd_devmap.c
+++ b/drivers/s390/block/dasd_devmap.c
@@ -1432,6 +1432,29 @@ static ssize_t dasd_reservation_state_store(struct device *dev,
static DEVICE_ATTR(last_known_reservation_state, 0644,
dasd_reservation_state_show, dasd_reservation_state_store);
+static ssize_t dasd_pm_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dasd_device *device;
+ u8 opm, nppm, cablepm, cuirpm, hpfpm;
+
+ device = dasd_device_from_cdev(to_ccwdev(dev));
+ if (IS_ERR(device))
+ return sprintf(buf, "0\n");
+
+ opm = device->path_data.opm;
+ nppm = device->path_data.npm;
+ cablepm = device->path_data.cablepm;
+ cuirpm = device->path_data.cuirpm;
+ hpfpm = device->path_data.hpfpm;
+ dasd_put_device(device);
+
+ return sprintf(buf, "%02x %02x %02x %02x %02x\n", opm, nppm,
+ cablepm, cuirpm, hpfpm);
+}
+
+static DEVICE_ATTR(path_masks, 0444, dasd_pm_show, NULL);
+
static struct attribute * dasd_attrs[] = {
&dev_attr_readonly.attr,
&dev_attr_discipline.attr,
@@ -1450,6 +1473,7 @@ static struct attribute * dasd_attrs[] = {
&dev_attr_reservation_policy.attr,
&dev_attr_last_known_reservation_state.attr,
&dev_attr_safe_offline.attr,
+ &dev_attr_path_masks.attr,
NULL,
};
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c
index 51dea7baf02c..d47f5b99623a 100644
--- a/drivers/s390/block/dasd_eckd.c
+++ b/drivers/s390/block/dasd_eckd.c
@@ -29,6 +29,8 @@
#include <asm/cio.h>
#include <asm/ccwdev.h>
#include <asm/itcw.h>
+#include <asm/schid.h>
+#include <asm/chpid.h>
#include "dasd_int.h"
#include "dasd_eckd.h"
@@ -112,6 +114,12 @@ struct path_verification_work_data {
static struct path_verification_work_data *path_verification_worker;
static DEFINE_MUTEX(dasd_path_verification_mutex);
+struct check_attention_work_data {
+ struct work_struct worker;
+ struct dasd_device *device;
+ __u8 lpum;
+};
+
/* initial attempt at a probe function. this can be simplified once
* the other detection code is gone */
static int
@@ -1126,6 +1134,7 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
"device %s instead of %s\n", lpm,
print_path_uid, print_device_uid);
path_err = -EINVAL;
+ path_data->cablepm |= lpm;
continue;
}
@@ -1141,6 +1150,13 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
break;
}
path_data->opm |= lpm;
+ /*
+ * if the path is used
+ * it should not be in one of the negative lists
+ */
+ path_data->cablepm &= ~lpm;
+ path_data->hpfpm &= ~lpm;
+ path_data->cuirpm &= ~lpm;
if (conf_data != private->conf_data)
kfree(conf_data);
@@ -1230,7 +1246,7 @@ static void do_path_verification_work(struct work_struct *work)
struct dasd_eckd_private path_private;
struct dasd_uid *uid;
__u8 path_rcd_buf[DASD_ECKD_RCD_DATA_SIZE];
- __u8 lpm, opm, npm, ppm, epm;
+ __u8 lpm, opm, npm, ppm, epm, hpfpm, cablepm;
unsigned long flags;
char print_uid[60];
int rc;
@@ -1248,6 +1264,9 @@ static void do_path_verification_work(struct work_struct *work)
npm = 0;
ppm = 0;
epm = 0;
+ hpfpm = 0;
+ cablepm = 0;
+
for (lpm = 0x80; lpm; lpm >>= 1) {
if (!(lpm & data->tbvpm))
continue;
@@ -1289,6 +1308,7 @@ static void do_path_verification_work(struct work_struct *work)
opm &= ~lpm;
npm &= ~lpm;
ppm &= ~lpm;
+ hpfpm |= lpm;
continue;
}
@@ -1350,6 +1370,7 @@ static void do_path_verification_work(struct work_struct *work)
opm &= ~lpm;
npm &= ~lpm;
ppm &= ~lpm;
+ cablepm |= lpm;
continue;
}
}
@@ -1364,12 +1385,21 @@ static void do_path_verification_work(struct work_struct *work)
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
if (!device->path_data.opm && opm) {
device->path_data.opm = opm;
+ device->path_data.cablepm &= ~opm;
+ device->path_data.cuirpm &= ~opm;
+ device->path_data.hpfpm &= ~opm;
dasd_generic_path_operational(device);
- } else
+ } else {
device->path_data.opm |= opm;
+ device->path_data.cablepm &= ~opm;
+ device->path_data.cuirpm &= ~opm;
+ device->path_data.hpfpm &= ~opm;
+ }
device->path_data.npm |= npm;
device->path_data.ppm |= ppm;
device->path_data.tbvpm |= epm;
+ device->path_data.cablepm |= cablepm;
+ device->path_data.hpfpm |= hpfpm;
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
}
@@ -4475,6 +4505,343 @@ out_err:
return -1;
}
+static int dasd_eckd_read_message_buffer(struct dasd_device *device,
+ struct dasd_rssd_messages *messages,
+ __u8 lpum)
+{
+ struct dasd_rssd_messages *message_buf;
+ struct dasd_psf_prssd_data *prssdp;
+ struct dasd_eckd_private *private;
+ struct dasd_ccw_req *cqr;
+ struct ccw1 *ccw;
+ int rc;
+
+ private = (struct dasd_eckd_private *) device->private;
+ cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */,
+ (sizeof(struct dasd_psf_prssd_data) +
+ sizeof(struct dasd_rssd_messages)),
+ device);
+ if (IS_ERR(cqr)) {
+ DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+ "Could not allocate read message buffer request");
+ return PTR_ERR(cqr);
+ }
+
+ cqr->startdev = device;
+ cqr->memdev = device;
+ cqr->block = NULL;
+ cqr->retries = 256;
+ cqr->expires = 10 * HZ;
+
+ /* we need to check for messages on exactly this path */
+ set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags);
+ cqr->lpm = lpum;
+
+ /* 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 = 0x03; /* Message Buffer */
+ /* 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->flags |= CCW_FLAG_SLI;
+ ccw->cda = (__u32)(addr_t) prssdp;
+
+ /* Read Subsystem Data - message buffer */
+ message_buf = (struct dasd_rssd_messages *) (prssdp + 1);
+ memset(message_buf, 0, sizeof(struct dasd_rssd_messages));
+
+ ccw++;
+ ccw->cmd_code = DASD_ECKD_CCW_RSSD;
+ ccw->count = sizeof(struct dasd_rssd_messages);
+ ccw->flags |= CCW_FLAG_SLI;
+ ccw->cda = (__u32)(addr_t) message_buf;
+
+ cqr->buildclk = get_tod_clock();
+ cqr->status = DASD_CQR_FILLED;
+ rc = dasd_sleep_on_immediatly(cqr);
+ if (rc == 0) {
+ prssdp = (struct dasd_psf_prssd_data *) cqr->data;
+ message_buf = (struct dasd_rssd_messages *)
+ (prssdp + 1);
+ memcpy(messages, message_buf,
+ sizeof(struct dasd_rssd_messages));
+ } else
+ DBF_EVENT_DEVID(DBF_WARNING, device->cdev,
+ "Reading messages failed with rc=%d\n"
+ , rc);
+ dasd_sfree_request(cqr, cqr->memdev);
+ return rc;
+}
+
+/*
+ * Perform Subsystem Function - CUIR response
+ */
+static int
+dasd_eckd_psf_cuir_response(struct dasd_device *device, int response,
+ __u32 message_id,
+ struct channel_path_desc *desc,
+ struct subchannel_id sch_id)
+{
+ struct dasd_psf_cuir_response *psf_cuir;
+ struct dasd_ccw_req *cqr;
+ struct ccw1 *ccw;
+ int rc;
+
+ cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ ,
+ sizeof(struct dasd_psf_cuir_response),
+ device);
+
+ if (IS_ERR(cqr)) {
+ DBF_DEV_EVENT(DBF_WARNING, device, "%s",
+ "Could not allocate PSF-CUIR request");
+ return PTR_ERR(cqr);
+ }
+
+ psf_cuir = (struct dasd_psf_cuir_response *)cqr->data;
+ psf_cuir->order = PSF_ORDER_CUIR_RESPONSE;
+ psf_cuir->cc = response;
+ if (desc)
+ psf_cuir->chpid = desc->chpid;
+ psf_cuir->message_id = message_id;
+ psf_cuir->cssid = sch_id.cssid;
+ psf_cuir->ssid = sch_id.ssid;
+
+ ccw = cqr->cpaddr;
+ ccw->cmd_code = DASD_ECKD_CCW_PSF;
+ ccw->cda = (__u32)(addr_t)psf_cuir;
+ ccw->count = sizeof(struct dasd_psf_cuir_response);
+
+ cqr->startdev = device;
+ cqr->memdev = device;
+ cqr->block = NULL;
+ cqr->retries = 256;
+ cqr->expires = 10*HZ;
+ cqr->buildclk = get_tod_clock();
+ cqr->status = DASD_CQR_FILLED;
+
+ rc = dasd_sleep_on(cqr);
+
+ dasd_sfree_request(cqr, cqr->memdev);
+ return rc;
+}
+
+static int dasd_eckd_cuir_change_state(struct dasd_device *device, __u8 lpum)
+{
+ unsigned long flags;
+ __u8 tbcpm;
+
+ spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+ tbcpm = device->path_data.opm & ~lpum;
+ if (tbcpm) {
+ device->path_data.opm = tbcpm;
+ device->path_data.cuirpm |= lpum;
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+ return tbcpm ? 0 : PSF_CUIR_LAST_PATH;
+}
+
+/*
+ * walk through all devices and quiesce them
+ * if it is the last path return error
+ *
+ * if only part of the devices are quiesced and an error
+ * occurs no onlining necessary, the storage server will
+ * notify the already set offline devices again
+ */
+static int dasd_eckd_cuir_quiesce(struct dasd_device *device, __u8 lpum,
+ struct channel_path_desc *desc,
+ struct subchannel_id sch_id)
+{
+ struct alias_pav_group *pavgroup, *tempgroup;
+ struct dasd_eckd_private *private;
+ struct dasd_device *dev, *n;
+ int rc;
+
+ private = (struct dasd_eckd_private *) device->private;
+ rc = 0;
+
+ /* active devices */
+ list_for_each_entry_safe(dev, n,
+ &private->lcu->active_devices,
+ alias_list) {
+ rc = dasd_eckd_cuir_change_state(dev, lpum);
+ if (rc)
+ goto out;
+ }
+
+ /* inactive devices */
+ list_for_each_entry_safe(dev, n,
+ &private->lcu->inactive_devices,
+ alias_list) {
+ rc = dasd_eckd_cuir_change_state(dev, lpum);
+ if (rc)
+ goto out;
+ }
+
+ /* devices in PAV groups */
+ list_for_each_entry_safe(pavgroup, tempgroup,
+ &private->lcu->grouplist, group) {
+ list_for_each_entry_safe(dev, n, &pavgroup->baselist,
+ alias_list) {
+ rc = dasd_eckd_cuir_change_state(dev, lpum);
+ if (rc)
+ goto out;
+ }
+ list_for_each_entry_safe(dev, n, &pavgroup->aliaslist,
+ alias_list) {
+ rc = dasd_eckd_cuir_change_state(dev, lpum);
+ if (rc)
+ goto out;
+ }
+ }
+
+ pr_warn("Service on the storage server caused path %x.%02x to go offline",
+ sch_id.cssid, desc ? desc->chpid : 0);
+ rc = PSF_CUIR_COMPLETED;
+out:
+ return rc;
+}
+
+static int dasd_eckd_cuir_resume(struct dasd_device *device, __u8 lpum,
+ struct channel_path_desc *desc,
+ struct subchannel_id sch_id)
+{
+ struct alias_pav_group *pavgroup, *tempgroup;
+ struct dasd_eckd_private *private;
+ struct dasd_device *dev, *n;
+
+ pr_info("Path %x.%02x is back online after service on the storage server",
+ sch_id.cssid, desc ? desc->chpid : 0);
+ private = (struct dasd_eckd_private *) device->private;
+
+ /*
+ * the path may have been added through a generic path event before
+ * only trigger path verification if the path is not already in use
+ */
+
+ list_for_each_entry_safe(dev, n,
+ &private->lcu->active_devices,
+ alias_list) {
+ if (!(dev->path_data.opm & lpum)) {
+ dev->path_data.tbvpm |= lpum;
+ dasd_schedule_device_bh(dev);
+ }
+ }
+
+ list_for_each_entry_safe(dev, n,
+ &private->lcu->inactive_devices,
+ alias_list) {
+ if (!(dev->path_data.opm & lpum)) {
+ dev->path_data.tbvpm |= lpum;
+ dasd_schedule_device_bh(dev);
+ }
+ }
+
+ /* devices in PAV groups */
+ list_for_each_entry_safe(pavgroup, tempgroup,
+ &private->lcu->grouplist,
+ group) {
+ list_for_each_entry_safe(dev, n,
+ &pavgroup->baselist,
+ alias_list) {
+ if (!(dev->path_data.opm & lpum)) {
+ dev->path_data.tbvpm |= lpum;
+ dasd_schedule_device_bh(dev);
+ }
+ }
+ list_for_each_entry_safe(dev, n,
+ &pavgroup->aliaslist,
+ alias_list) {
+ if (!(dev->path_data.opm & lpum)) {
+ dev->path_data.tbvpm |= lpum;
+ dasd_schedule_device_bh(dev);
+ }
+ }
+ }
+ return PSF_CUIR_COMPLETED;
+}
+
+static void dasd_eckd_handle_cuir(struct dasd_device *device, void *messages,
+ __u8 lpum)
+{
+ struct dasd_cuir_message *cuir = messages;
+ struct channel_path_desc *desc;
+ struct subchannel_id sch_id;
+ int pos, response;
+ ccw_device_get_schid(device->cdev, &sch_id);
+
+ /* get position of path in mask */
+ pos = 8 - ffs(lpum);
+ /* get channel path descriptor from this position */
+ desc = ccw_device_get_chp_desc(device->cdev, pos);
+
+ if (cuir->code == CUIR_QUIESCE) {
+ /* quiesce */
+ response = dasd_eckd_cuir_quiesce(device, lpum, desc, sch_id);
+ } else if (cuir->code == CUIR_RESUME) {
+ /* resume */
+ response = dasd_eckd_cuir_resume(device, lpum, desc, sch_id);
+ } else
+ response = PSF_CUIR_NOT_SUPPORTED;
+
+ dasd_eckd_psf_cuir_response(device, response, cuir->message_id,
+ desc, sch_id);
+
+ /* free descriptor copy */
+ kfree(desc);
+}
+
+static void dasd_eckd_check_attention_work(struct work_struct *work)
+{
+ struct check_attention_work_data *data;
+ struct dasd_rssd_messages *messages;
+ struct dasd_device *device;
+ int rc;
+
+ data = container_of(work, struct check_attention_work_data, worker);
+ device = data->device;
+
+ messages = kzalloc(sizeof(*messages), GFP_KERNEL);
+ if (!messages) {
+ DBF_DEV_EVENT(DBF_WARNING, device, "%s",
+ "Could not allocate attention message buffer");
+ goto out;
+ }
+
+ rc = dasd_eckd_read_message_buffer(device, messages, data->lpum);
+ if (rc)
+ goto out;
+
+ if (messages->length == ATTENTION_LENGTH_CUIR &&
+ messages->format == ATTENTION_FORMAT_CUIR)
+ dasd_eckd_handle_cuir(device, messages, data->lpum);
+
+out:
+ dasd_put_device(device);
+ kfree(messages);
+ kfree(data);
+}
+
+static int dasd_eckd_check_attention(struct dasd_device *device, __u8 lpum)
+{
+ struct check_attention_work_data *data;
+
+ data = kzalloc(sizeof(*data), GFP_ATOMIC);
+ if (!data)
+ return -ENOMEM;
+ INIT_WORK(&data->worker, dasd_eckd_check_attention_work);
+ dasd_get_device(device);
+ data->device = device;
+ data->lpum = lpum;
+ schedule_work(&data->worker);
+ return 0;
+}
+
static struct ccw_driver dasd_eckd_driver = {
.driver = {
.name = "dasd-eckd",
@@ -4539,6 +4906,7 @@ static struct dasd_discipline dasd_eckd_discipline = {
.reload = dasd_eckd_reload_device,
.get_uid = dasd_eckd_get_uid,
.kick_validate = dasd_eckd_kick_validate_server,
+ .check_attention = dasd_eckd_check_attention,
};
static int __init
diff --git a/drivers/s390/block/dasd_eckd.h b/drivers/s390/block/dasd_eckd.h
index 2555e494591f..ddab7df36e25 100644
--- a/drivers/s390/block/dasd_eckd.h
+++ b/drivers/s390/block/dasd_eckd.h
@@ -51,8 +51,35 @@
/*
* Perform Subsystem Function / Sub-Orders
*/
-#define PSF_ORDER_PRSSD 0x18
-#define PSF_ORDER_SSC 0x1D
+#define PSF_ORDER_PRSSD 0x18
+#define PSF_ORDER_CUIR_RESPONSE 0x1A
+#define PSF_ORDER_SSC 0x1D
+
+/*
+ * 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
+
+/*
+ * attention message definitions
+ */
+#define ATTENTION_LENGTH_CUIR 0x0e
+#define ATTENTION_FORMAT_CUIR 0x01
/*
* Size that is reportet for large volumes in the old 16-bit no_cyl field
@@ -342,6 +369,38 @@ 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;
+
+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;
/*
* Perform Subsystem Function - Prepare for Read Subsystem Data
diff --git a/drivers/s390/block/dasd_genhd.c b/drivers/s390/block/dasd_genhd.c
index f224d59c4b6b..90f39f79f5d7 100644
--- a/drivers/s390/block/dasd_genhd.c
+++ b/drivers/s390/block/dasd_genhd.c
@@ -99,15 +99,37 @@ void dasd_gendisk_free(struct dasd_block *block)
int dasd_scan_partitions(struct dasd_block *block)
{
struct block_device *bdev;
+ int retry, rc;
+ retry = 5;
bdev = bdget_disk(block->gdp, 0);
- if (!bdev || blkdev_get(bdev, FMODE_READ, NULL) < 0)
+ if (!bdev) {
+ DBF_DEV_EVENT(DBF_ERR, block->base, "%s",
+ "scan partitions error, bdget returned NULL");
return -ENODEV;
+ }
+
+ rc = blkdev_get(bdev, FMODE_READ, NULL);
+ if (rc < 0) {
+ DBF_DEV_EVENT(DBF_ERR, block->base,
+ "scan partitions error, blkdev_get returned %d",
+ rc);
+ return -ENODEV;
+ }
/*
* See fs/partition/check.c:register_disk,rescan_partitions
* Can't call rescan_partitions directly. Use ioctl.
*/
- ioctl_by_bdev(bdev, BLKRRPART, 0);
+ rc = ioctl_by_bdev(bdev, BLKRRPART, 0);
+ while (rc == -EBUSY && retry > 0) {
+ schedule();
+ rc = ioctl_by_bdev(bdev, BLKRRPART, 0);
+ retry--;
+ DBF_DEV_EVENT(DBF_ERR, block->base,
+ "scan partitions error, retry %d rc %d",
+ retry, rc);
+ }
+
/*
* Since the matching blkdev_put call to the blkdev_get in
* this function is not called before dasd_destroy_partitions
diff --git a/drivers/s390/block/dasd_int.h b/drivers/s390/block/dasd_int.h
index c20170166909..8b5d4100abf7 100644
--- a/drivers/s390/block/dasd_int.h
+++ b/drivers/s390/block/dasd_int.h
@@ -357,6 +357,7 @@ struct dasd_discipline {
int (*get_uid) (struct dasd_device *, struct dasd_uid *);
void (*kick_validate) (struct dasd_device *);
+ int (*check_attention)(struct dasd_device *, __u8);
};
extern struct dasd_discipline *dasd_diag_discipline_pointer;
@@ -382,6 +383,10 @@ struct dasd_path {
__u8 tbvpm;
__u8 ppm;
__u8 npm;
+ /* paths that are not used because of a special condition */
+ __u8 cablepm; /* miss-cabled */
+ __u8 hpfpm; /* the HPF requirements of the other paths are not met */
+ __u8 cuirpm; /* CUIR varied offline */
};
struct dasd_profile_info {
@@ -501,7 +506,10 @@ struct dasd_block {
struct dasd_profile profile;
};
-
+struct dasd_attention_data {
+ struct dasd_device *device;
+ __u8 lpum;
+};
/* reasons why device (ccw_device_start) was stopped */
#define DASD_STOPPED_NOT_ACC 1 /* not accessible */
diff --git a/drivers/s390/block/dcssblk.c b/drivers/s390/block/dcssblk.c
index 0f471750327e..b550c8c8d010 100644
--- a/drivers/s390/block/dcssblk.c
+++ b/drivers/s390/block/dcssblk.c
@@ -1003,7 +1003,6 @@ static const struct dev_pm_ops dcssblk_pm_ops = {
static struct platform_driver dcssblk_pdrv = {
.driver = {
.name = "dcssblk",
- .owner = THIS_MODULE,
.pm = &dcssblk_pm_ops,
},
};
diff --git a/drivers/s390/block/scm_blk.c b/drivers/s390/block/scm_blk.c
index 76bed1743db1..75d9896deccb 100644
--- a/drivers/s390/block/scm_blk.c
+++ b/drivers/s390/block/scm_blk.c
@@ -10,6 +10,7 @@
#include <linux/interrupt.h>
#include <linux/spinlock.h>
+#include <linux/mempool.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
@@ -20,13 +21,18 @@
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*");
@@ -36,8 +42,8 @@ static void __scm_free_rq(struct scm_request *scmrq)
struct aob_rq_header *aobrq = to_aobrq(scmrq);
free_page((unsigned long) scmrq->aob);
- free_page((unsigned long) scmrq->aidaw);
__scm_free_rq_cluster(scmrq);
+ kfree(scmrq->request);
kfree(aobrq);
}
@@ -53,6 +59,8 @@ static void scm_free_rqs(void)
__scm_free_rq(scmrq);
}
spin_unlock_irq(&list_lock);
+
+ mempool_destroy(aidaw_pool);
}
static int __scm_alloc_rq(void)
@@ -65,17 +73,17 @@ static int __scm_alloc_rq(void)
return -ENOMEM;
scmrq = (void *) aobrq->data;
- scmrq->aidaw = (void *) get_zeroed_page(GFP_DMA);
scmrq->aob = (void *) get_zeroed_page(GFP_DMA);
- if (!scmrq->aob || !scmrq->aidaw) {
- __scm_free_rq(scmrq);
- return -ENOMEM;
- }
+ if (!scmrq->aob)
+ goto free;
- if (__scm_alloc_rq_cluster(scmrq)) {
- __scm_free_rq(scmrq);
- return -ENOMEM;
- }
+ scmrq->request = kcalloc(nr_requests_per_io, sizeof(scmrq->request[0]),
+ GFP_KERNEL);
+ if (!scmrq->request)
+ goto free;
+
+ if (__scm_alloc_rq_cluster(scmrq))
+ goto free;
INIT_LIST_HEAD(&scmrq->list);
spin_lock_irq(&list_lock);
@@ -83,12 +91,19 @@ static int __scm_alloc_rq(void)
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();
@@ -112,6 +127,18 @@ out:
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 = msb->data_addr;
+
+ if ((msb->flags & MSB_FLAG_IDA) && aidaw &&
+ IS_ALIGNED(aidaw, PAGE_SIZE))
+ mempool_free(virt_to_page(aidaw), aidaw_pool);
+ }
spin_lock_irqsave(&list_lock, flags);
list_add(&scmrq->list, &inactive_requests);
@@ -123,48 +150,90 @@ 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 void scm_request_prepare(struct scm_request *scmrq)
+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;
- struct aidaw *aidaw = scmrq->aidaw;
- struct msb *msb = &scmrq->aob->msb[0];
+ 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 = 1;
- msb->scm_addr = scmdev->address +
- ((u64) blk_rq_pos(scmrq->request) << 9);
- msb->oc = (rq_data_dir(scmrq->request) == READ) ?
- MSB_OC_READ : MSB_OC_WRITE;
+ 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 = (u64) aidaw;
- rq_for_each_segment(bv, scmrq->request, iter) {
+ rq_for_each_segment(bv, req, iter) {
WARN_ON(bv.bv_offset);
msb->blk_count += bv.bv_len >> 12;
aidaw->data_addr = (u64) 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 request *req)
+ 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));
- memset(scmrq->aidaw, 0, PAGE_SIZE);
aobrq->scmdev = bdev->scmdev;
aob->request.cmd_code = ARQB_CMD_MOVE;
aob->request.data = (u64) aobrq;
- scmrq->request = req;
scmrq->bdev = bdev;
scmrq->retries = 4;
scmrq->error = 0;
+ /* 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];
scm_request_cluster_init(scmrq);
}
@@ -180,9 +249,12 @@ static void scm_ensure_queue_restart(struct scm_blk_dev *bdev)
void scm_request_requeue(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
+ int i;
scm_release_cluster(scmrq);
- blk_requeue_request(bdev->rq, scmrq->request);
+ for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++)
+ blk_requeue_request(bdev->rq, scmrq->request[i]);
+
atomic_dec(&bdev->queued_reqs);
scm_request_done(scmrq);
scm_ensure_queue_restart(bdev);
@@ -191,20 +263,41 @@ void scm_request_requeue(struct scm_request *scmrq)
void scm_request_finish(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
+ int i;
scm_release_cluster(scmrq);
- blk_end_request_all(scmrq->request, scmrq->error);
+ for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++)
+ blk_end_request_all(scmrq->request[i], scmrq->error);
+
atomic_dec(&bdev->queued_reqs);
scm_request_done(scmrq);
}
+static int scm_request_start(struct scm_request *scmrq)
+{
+ struct scm_blk_dev *bdev = scmrq->bdev;
+ int ret;
+
+ atomic_inc(&bdev->queued_reqs);
+ if (!scmrq->aob->request.msb_count) {
+ scm_request_requeue(scmrq);
+ return -EINVAL;
+ }
+
+ ret = eadm_start_aob(scmrq->aob);
+ if (ret) {
+ SCM_LOG(5, "no subchannel");
+ scm_request_requeue(scmrq);
+ }
+ return ret;
+}
+
static void scm_blk_request(struct request_queue *rq)
{
struct scm_device *scmdev = rq->queuedata;
struct scm_blk_dev *bdev = dev_get_drvdata(&scmdev->dev);
- struct scm_request *scmrq;
+ struct scm_request *scmrq = NULL;
struct request *req;
- int ret;
while ((req = blk_peek_request(rq))) {
if (req->cmd_type != REQ_TYPE_FS) {
@@ -214,39 +307,64 @@ static void scm_blk_request(struct request_queue *rq)
continue;
}
- if (!scm_permit_request(bdev, req)) {
- scm_ensure_queue_restart(bdev);
- return;
- }
- scmrq = scm_request_fetch();
+ if (!scm_permit_request(bdev, req))
+ goto out;
+
if (!scmrq) {
- SCM_LOG(5, "no request");
- scm_ensure_queue_restart(bdev);
- return;
+ scmrq = scm_request_fetch();
+ if (!scmrq) {
+ SCM_LOG(5, "no request");
+ goto out;
+ }
+ scm_request_init(bdev, scmrq);
}
- scm_request_init(bdev, scmrq, req);
+ scm_request_set(scmrq, req);
+
if (!scm_reserve_cluster(scmrq)) {
SCM_LOG(5, "cluster busy");
+ scm_request_set(scmrq, NULL);
+ if (scmrq->aob->request.msb_count)
+ goto out;
+
scm_request_done(scmrq);
return;
}
+
if (scm_need_cluster_request(scmrq)) {
- atomic_inc(&bdev->queued_reqs);
- blk_start_request(req);
- scm_initiate_cluster_request(scmrq);
- return;
+ if (scmrq->aob->request.msb_count) {
+ /* Start cluster requests separately. */
+ scm_request_set(scmrq, NULL);
+ if (scm_request_start(scmrq))
+ return;
+ } else {
+ atomic_inc(&bdev->queued_reqs);
+ blk_start_request(req);
+ scm_initiate_cluster_request(scmrq);
+ }
+ scmrq = NULL;
+ continue;
+ }
+
+ if (scm_request_prepare(scmrq)) {
+ SCM_LOG(5, "aidaw alloc failed");
+ scm_request_set(scmrq, NULL);
+ goto out;
}
- scm_request_prepare(scmrq);
- atomic_inc(&bdev->queued_reqs);
blk_start_request(req);
- ret = eadm_start_aob(scmrq->aob);
- if (ret) {
- SCM_LOG(5, "no subchannel");
- scm_request_requeue(scmrq);
+ if (scmrq->aob->request.msb_count < nr_requests_per_io)
+ continue;
+
+ if (scm_request_start(scmrq))
return;
- }
+
+ scmrq = NULL;
}
+out:
+ if (scmrq)
+ scm_request_start(scmrq);
+ else
+ scm_ensure_queue_restart(bdev);
}
static void __scmrq_log_error(struct scm_request *scmrq)
@@ -386,6 +504,7 @@ int scm_blk_dev_setup(struct scm_blk_dev *bdev, struct scm_device *scmdev)
blk_queue_max_hw_sectors(rq, nr_max_blk << 3); /* 8 * 512 = blk_size */
blk_queue_max_segments(rq, nr_max_blk);
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, rq);
+ queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, rq);
scm_blk_dev_cluster_setup(bdev);
bdev->gendisk = alloc_disk(SCM_NR_PARTS);
@@ -442,11 +561,19 @@ void scm_blk_set_available(struct scm_blk_dev *bdev)
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 scm_cluster_size_valid();
+}
+
static int __init scm_blk_init(void)
{
int ret = -EINVAL;
- if (!scm_cluster_size_valid())
+ if (!scm_blk_params_valid())
goto out;
ret = register_blkdev(0, "scm");
diff --git a/drivers/s390/block/scm_blk.h b/drivers/s390/block/scm_blk.h
index e59331e6c2e5..09218cdc5129 100644
--- a/drivers/s390/block/scm_blk.h
+++ b/drivers/s390/block/scm_blk.h
@@ -30,8 +30,8 @@ struct scm_blk_dev {
struct scm_request {
struct scm_blk_dev *bdev;
- struct request *request;
- struct aidaw *aidaw;
+ struct aidaw *next_aidaw;
+ struct request **request;
struct aob *aob;
struct list_head list;
u8 retries;
@@ -55,6 +55,8 @@ void scm_blk_irq(struct scm_device *, void *, int);
void scm_request_finish(struct scm_request *);
void scm_request_requeue(struct scm_request *);
+struct aidaw *scm_aidaw_fetch(struct scm_request *scmrq, unsigned int bytes);
+
int scm_drv_init(void);
void scm_drv_cleanup(void);
diff --git a/drivers/s390/block/scm_blk_cluster.c b/drivers/s390/block/scm_blk_cluster.c
index 9aae909d47a5..09db45296eed 100644
--- a/drivers/s390/block/scm_blk_cluster.c
+++ b/drivers/s390/block/scm_blk_cluster.c
@@ -57,39 +57,52 @@ void scm_request_cluster_init(struct scm_request *scmrq)
scmrq->cluster.state = CLUSTER_NONE;
}
-static bool clusters_intersect(struct scm_request *A, struct scm_request *B)
+static bool clusters_intersect(struct request *A, struct request *B)
{
unsigned long firstA, lastA, firstB, lastB;
- firstA = ((u64) blk_rq_pos(A->request) << 9) / CLUSTER_SIZE;
- lastA = (((u64) blk_rq_pos(A->request) << 9) +
- blk_rq_bytes(A->request) - 1) / CLUSTER_SIZE;
+ firstA = ((u64) blk_rq_pos(A) << 9) / CLUSTER_SIZE;
+ lastA = (((u64) blk_rq_pos(A) << 9) +
+ blk_rq_bytes(A) - 1) / CLUSTER_SIZE;
- firstB = ((u64) blk_rq_pos(B->request) << 9) / CLUSTER_SIZE;
- lastB = (((u64) blk_rq_pos(B->request) << 9) +
- blk_rq_bytes(B->request) - 1) / CLUSTER_SIZE;
+ firstB = ((u64) blk_rq_pos(B) << 9) / CLUSTER_SIZE;
+ lastB = (((u64) blk_rq_pos(B) << 9) +
+ blk_rq_bytes(B) - 1) / CLUSTER_SIZE;
return (firstB <= lastA && firstA <= lastB);
}
bool scm_reserve_cluster(struct scm_request *scmrq)
{
+ struct request *req = scmrq->request[scmrq->aob->request.msb_count];
struct scm_blk_dev *bdev = scmrq->bdev;
struct scm_request *iter;
+ int pos, add = 1;
if (write_cluster_size == 0)
return true;
spin_lock(&bdev->lock);
list_for_each_entry(iter, &bdev->cluster_list, cluster.list) {
- if (clusters_intersect(scmrq, iter) &&
- (rq_data_dir(scmrq->request) == WRITE ||
- rq_data_dir(iter->request) == WRITE)) {
- spin_unlock(&bdev->lock);
- return false;
+ if (iter == scmrq) {
+ /*
+ * We don't have to use clusters_intersect here, since
+ * cluster requests are always started separately.
+ */
+ add = 0;
+ continue;
+ }
+ for (pos = 0; pos <= iter->aob->request.msb_count; pos++) {
+ if (clusters_intersect(req, iter->request[pos]) &&
+ (rq_data_dir(req) == WRITE ||
+ rq_data_dir(iter->request[pos]) == WRITE)) {
+ spin_unlock(&bdev->lock);
+ return false;
+ }
}
}
- list_add(&scmrq->cluster.list, &bdev->cluster_list);
+ if (add)
+ list_add(&scmrq->cluster.list, &bdev->cluster_list);
spin_unlock(&bdev->lock);
return true;
@@ -114,14 +127,14 @@ void scm_blk_dev_cluster_setup(struct scm_blk_dev *bdev)
blk_queue_io_opt(bdev->rq, CLUSTER_SIZE);
}
-static void scm_prepare_cluster_request(struct scm_request *scmrq)
+static int scm_prepare_cluster_request(struct scm_request *scmrq)
{
struct scm_blk_dev *bdev = scmrq->bdev;
struct scm_device *scmdev = bdev->gendisk->private_data;
- struct request *req = scmrq->request;
- struct aidaw *aidaw = scmrq->aidaw;
+ struct request *req = scmrq->request[0];
struct msb *msb = &scmrq->aob->msb[0];
struct req_iterator iter;
+ struct aidaw *aidaw;
struct bio_vec bv;
int i = 0;
u64 addr;
@@ -131,11 +144,9 @@ static void scm_prepare_cluster_request(struct scm_request *scmrq)
scmrq->cluster.state = CLUSTER_READ;
/* fall through */
case CLUSTER_READ:
- scmrq->aob->request.msb_count = 1;
msb->bs = MSB_BS_4K;
msb->oc = MSB_OC_READ;
msb->flags = MSB_FLAG_IDA;
- msb->data_addr = (u64) aidaw;
msb->blk_count = write_cluster_size;
addr = scmdev->address + ((u64) blk_rq_pos(req) << 9);
@@ -146,6 +157,12 @@ static void scm_prepare_cluster_request(struct scm_request *scmrq)
CLUSTER_SIZE))
msb->blk_count = 2 * write_cluster_size;
+ aidaw = scm_aidaw_fetch(scmrq, msb->blk_count * PAGE_SIZE);
+ if (!aidaw)
+ return -ENOMEM;
+
+ scmrq->aob->request.msb_count = 1;
+ msb->data_addr = (u64) aidaw;
for (i = 0; i < msb->blk_count; i++) {
aidaw->data_addr = (u64) scmrq->cluster.buf[i];
aidaw++;
@@ -153,6 +170,7 @@ static void scm_prepare_cluster_request(struct scm_request *scmrq)
break;
case CLUSTER_WRITE:
+ aidaw = (void *) msb->data_addr;
msb->oc = MSB_OC_WRITE;
for (addr = msb->scm_addr;
@@ -173,22 +191,29 @@ static void scm_prepare_cluster_request(struct scm_request *scmrq)
}
break;
}
+ return 0;
}
bool scm_need_cluster_request(struct scm_request *scmrq)
{
- if (rq_data_dir(scmrq->request) == READ)
+ int pos = scmrq->aob->request.msb_count;
+
+ if (rq_data_dir(scmrq->request[pos]) == READ)
return false;
- return blk_rq_bytes(scmrq->request) < CLUSTER_SIZE;
+ return blk_rq_bytes(scmrq->request[pos]) < CLUSTER_SIZE;
}
/* Called with queue lock held. */
void scm_initiate_cluster_request(struct scm_request *scmrq)
{
- scm_prepare_cluster_request(scmrq);
+ if (scm_prepare_cluster_request(scmrq))
+ goto requeue;
if (eadm_start_aob(scmrq->aob))
- scm_request_requeue(scmrq);
+ goto requeue;
+ return;
+requeue:
+ scm_request_requeue(scmrq);
}
bool scm_test_cluster_request(struct scm_request *scmrq)
diff --git a/drivers/s390/block/xpram.c b/drivers/s390/block/xpram.c
index 6969d39f1e2e..7d4e9397ac31 100644
--- a/drivers/s390/block/xpram.c
+++ b/drivers/s390/block/xpram.c
@@ -346,6 +346,7 @@ static int __init xpram_setup_blkdev(void)
goto out;
}
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, xpram_queues[i]);
+ queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, xpram_queues[i]);
blk_queue_make_request(xpram_queues[i], xpram_make_request);
blk_queue_logical_block_size(xpram_queues[i], 4096);
}
@@ -416,7 +417,6 @@ static const struct dev_pm_ops xpram_pm_ops = {
static struct platform_driver xpram_pdrv = {
.driver = {
.name = XPRAM_NAME,
- .owner = THIS_MODULE,
.pm = &xpram_pm_ops,
},
};
diff --git a/drivers/s390/char/Kconfig b/drivers/s390/char/Kconfig
index 71bf959732fe..a5c6f7e157aa 100644
--- a/drivers/s390/char/Kconfig
+++ b/drivers/s390/char/Kconfig
@@ -102,6 +102,29 @@ config SCLP_ASYNC
want for inform other people about your kernel panics,
need this feature and intend to run your kernel in LPAR.
+config SCLP_ASYNC_ID
+ string "Component ID for Call Home"
+ depends on SCLP_ASYNC
+ default "000000000"
+ help
+ The Component ID for Call Home is used to identify the correct
+ problem reporting queue the call home records should be sent to.
+
+ If your are unsure, please use the default value "000000000".
+
+config HMC_DRV
+ def_tristate m
+ prompt "Support for file transfers from HMC drive CD/DVD-ROM"
+ depends on S390 && 64BIT
+ select CRC16
+ help
+ This option enables support for file transfers from a Hardware
+ Management Console (HMC) drive CD/DVD-ROM. It is available as a
+ module, called 'hmcdrv', and also as kernel built-in. There is one
+ optional parameter for this module: cachesize=N, which modifies the
+ transfer cache size from it's default value 0.5MB to N bytes. If N
+ is zero, then no caching is performed.
+
config S390_TAPE
def_tristate m
prompt "S/390 tape device support"
diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile
index 78b6ace7edcb..6fa9364d1c07 100644
--- a/drivers/s390/char/Makefile
+++ b/drivers/s390/char/Makefile
@@ -33,3 +33,6 @@ obj-$(CONFIG_S390_VMUR) += vmur.o
zcore_mod-objs := sclp_sdias.o zcore.o
obj-$(CONFIG_CRASH_DUMP) += zcore_mod.o
+
+hmcdrv-objs := hmcdrv_mod.o hmcdrv_dev.o hmcdrv_ftp.o hmcdrv_cache.o diag_ftp.o sclp_ftp.o
+obj-$(CONFIG_HMC_DRV) += hmcdrv.o
diff --git a/drivers/s390/char/diag_ftp.c b/drivers/s390/char/diag_ftp.c
new file mode 100644
index 000000000000..93889632fdf9
--- /dev/null
+++ b/drivers/s390/char/diag_ftp.c
@@ -0,0 +1,237 @@
+/*
+ * DIAGNOSE X'2C4' instruction based HMC FTP services, useable on z/VM
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ *
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/irq.h>
+#include <linux/wait.h>
+#include <linux/string.h>
+#include <asm/ctl_reg.h>
+
+#include "hmcdrv_ftp.h"
+#include "diag_ftp.h"
+
+/* DIAGNOSE X'2C4' return codes in Ry */
+#define DIAG_FTP_RET_OK 0 /* HMC FTP started successfully */
+#define DIAG_FTP_RET_EBUSY 4 /* HMC FTP service currently busy */
+#define DIAG_FTP_RET_EIO 8 /* HMC FTP service I/O error */
+/* and an artificial extension */
+#define DIAG_FTP_RET_EPERM 2 /* HMC FTP service privilege error */
+
+/* FTP service status codes (after INTR at guest real location 133) */
+#define DIAG_FTP_STAT_OK 0U /* request completed successfully */
+#define DIAG_FTP_STAT_PGCC 4U /* program check condition */
+#define DIAG_FTP_STAT_PGIOE 8U /* paging I/O error */
+#define DIAG_FTP_STAT_TIMEOUT 12U /* timeout */
+#define DIAG_FTP_STAT_EBASE 16U /* base of error codes from SCLP */
+#define DIAG_FTP_STAT_LDFAIL (DIAG_FTP_STAT_EBASE + 1U) /* failed */
+#define DIAG_FTP_STAT_LDNPERM (DIAG_FTP_STAT_EBASE + 2U) /* not allowed */
+#define DIAG_FTP_STAT_LDRUNS (DIAG_FTP_STAT_EBASE + 3U) /* runs */
+#define DIAG_FTP_STAT_LDNRUNS (DIAG_FTP_STAT_EBASE + 4U) /* not runs */
+
+/**
+ * struct diag_ftp_ldfpl - load file FTP parameter list (LDFPL)
+ * @bufaddr: real buffer address (at 4k boundary)
+ * @buflen: length of buffer
+ * @offset: dir/file offset
+ * @intparm: interruption parameter (unused)
+ * @transferred: bytes transferred
+ * @fsize: file size, filled on GET
+ * @failaddr: failing address
+ * @spare: padding
+ * @fident: file name - ASCII
+ */
+struct diag_ftp_ldfpl {
+ u64 bufaddr;
+ u64 buflen;
+ u64 offset;
+ u64 intparm;
+ u64 transferred;
+ u64 fsize;
+ u64 failaddr;
+ u64 spare;
+ u8 fident[HMCDRV_FTP_FIDENT_MAX];
+} __packed;
+
+static DECLARE_COMPLETION(diag_ftp_rx_complete);
+static int diag_ftp_subcode;
+
+/**
+ * diag_ftp_handler() - FTP services IRQ handler
+ * @extirq: external interrupt (sub-) code
+ * @param32: 32-bit interruption parameter from &struct diag_ftp_ldfpl
+ * @param64: unused (for 64-bit interrupt parameters)
+ */
+static void diag_ftp_handler(struct ext_code extirq,
+ unsigned int param32,
+ unsigned long param64)
+{
+ if ((extirq.subcode >> 8) != 8)
+ return; /* not a FTP services sub-code */
+
+ inc_irq_stat(IRQEXT_FTP);
+ diag_ftp_subcode = extirq.subcode & 0xffU;
+ complete(&diag_ftp_rx_complete);
+}
+
+/**
+ * diag_ftp_2c4() - DIAGNOSE X'2C4' service call
+ * @fpl: pointer to prepared LDFPL
+ * @cmd: FTP command to be executed
+ *
+ * Performs a DIAGNOSE X'2C4' call with (input/output) FTP parameter list
+ * @fpl and FTP function code @cmd. In case of an error the function does
+ * nothing and returns an (negative) error code.
+ *
+ * Notes:
+ * 1. This function only initiates a transfer, so the caller must wait
+ * for completion (asynchronous execution).
+ * 2. The FTP parameter list @fpl must be aligned to a double-word boundary.
+ * 3. fpl->bufaddr must be a real address, 4k aligned
+ */
+static int diag_ftp_2c4(struct diag_ftp_ldfpl *fpl,
+ enum hmcdrv_ftp_cmdid cmd)
+{
+ int rc;
+
+ asm volatile(
+ " diag %[addr],%[cmd],0x2c4\n"
+ "0: j 2f\n"
+ "1: la %[rc],%[err]\n"
+ "2:\n"
+ EX_TABLE(0b, 1b)
+ : [rc] "=d" (rc), "+m" (*fpl)
+ : [cmd] "0" (cmd), [addr] "d" (virt_to_phys(fpl)),
+ [err] "i" (DIAG_FTP_RET_EPERM)
+ : "cc");
+
+ switch (rc) {
+ case DIAG_FTP_RET_OK:
+ return 0;
+ case DIAG_FTP_RET_EBUSY:
+ return -EBUSY;
+ case DIAG_FTP_RET_EPERM:
+ return -EPERM;
+ case DIAG_FTP_RET_EIO:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * diag_ftp_cmd() - executes a DIAG X'2C4' FTP command, targeting a HMC
+ * @ftp: pointer to FTP command specification
+ * @fsize: return of file size (or NULL if undesirable)
+ *
+ * Attention: Notice that this function is not reentrant - so the caller
+ * must ensure locking.
+ *
+ * Return: number of bytes read/written or a (negative) error code
+ */
+ssize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize)
+{
+ struct diag_ftp_ldfpl *ldfpl;
+ ssize_t len;
+#ifdef DEBUG
+ unsigned long start_jiffies;
+
+ pr_debug("starting DIAG X'2C4' on '%s', requesting %zd bytes\n",
+ ftp->fname, ftp->len);
+ start_jiffies = jiffies;
+#endif
+ init_completion(&diag_ftp_rx_complete);
+
+ ldfpl = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!ldfpl) {
+ len = -ENOMEM;
+ goto out;
+ }
+
+ len = strlcpy(ldfpl->fident, ftp->fname, sizeof(ldfpl->fident));
+ if (len >= HMCDRV_FTP_FIDENT_MAX) {
+ len = -EINVAL;
+ goto out_free;
+ }
+
+ ldfpl->transferred = 0;
+ ldfpl->fsize = 0;
+ ldfpl->offset = ftp->ofs;
+ ldfpl->buflen = ftp->len;
+ ldfpl->bufaddr = virt_to_phys(ftp->buf);
+
+ len = diag_ftp_2c4(ldfpl, ftp->id);
+ if (len)
+ goto out_free;
+
+ /*
+ * There is no way to cancel the running diag X'2C4', the code
+ * needs to wait unconditionally until the transfer is complete.
+ */
+ wait_for_completion(&diag_ftp_rx_complete);
+
+#ifdef DEBUG
+ pr_debug("completed DIAG X'2C4' after %lu ms\n",
+ (jiffies - start_jiffies) * 1000 / HZ);
+ pr_debug("status of DIAG X'2C4' is %u, with %lld/%lld bytes\n",
+ diag_ftp_subcode, ldfpl->transferred, ldfpl->fsize);
+#endif
+
+ switch (diag_ftp_subcode) {
+ case DIAG_FTP_STAT_OK: /* success */
+ len = ldfpl->transferred;
+ if (fsize)
+ *fsize = ldfpl->fsize;
+ break;
+ case DIAG_FTP_STAT_LDNPERM:
+ len = -EPERM;
+ break;
+ case DIAG_FTP_STAT_LDRUNS:
+ len = -EBUSY;
+ break;
+ case DIAG_FTP_STAT_LDFAIL:
+ len = -ENOENT; /* no such file or media */
+ break;
+ default:
+ len = -EIO;
+ break;
+ }
+
+out_free:
+ free_page((unsigned long) ldfpl);
+out:
+ return len;
+}
+
+/**
+ * diag_ftp_startup() - startup of FTP services, when running on z/VM
+ *
+ * Return: 0 on success, else an (negative) error code
+ */
+int diag_ftp_startup(void)
+{
+ int rc;
+
+ rc = register_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
+ if (rc)
+ return rc;
+
+ ctl_set_bit(0, 63 - 22);
+ return 0;
+}
+
+/**
+ * diag_ftp_shutdown() - shutdown of FTP services, when running on z/VM
+ */
+void diag_ftp_shutdown(void)
+{
+ ctl_clear_bit(0, 63 - 22);
+ unregister_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
+}
diff --git a/drivers/s390/char/diag_ftp.h b/drivers/s390/char/diag_ftp.h
new file mode 100644
index 000000000000..3abd2614053a
--- /dev/null
+++ b/drivers/s390/char/diag_ftp.h
@@ -0,0 +1,21 @@
+/*
+ * DIAGNOSE X'2C4' instruction based SE/HMC FTP Services, useable on z/VM
+ *
+ * Notice that all functions exported here are not reentrant.
+ * So usage should be exclusive, ensured by the caller (e.g. using a
+ * mutex).
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __DIAG_FTP_H__
+#define __DIAG_FTP_H__
+
+#include "hmcdrv_ftp.h"
+
+int diag_ftp_startup(void);
+void diag_ftp_shutdown(void);
+ssize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize);
+
+#endif /* __DIAG_FTP_H__ */
diff --git a/drivers/s390/char/hmcdrv_cache.c b/drivers/s390/char/hmcdrv_cache.c
new file mode 100644
index 000000000000..4cda5ada143a
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_cache.c
@@ -0,0 +1,252 @@
+/*
+ * SE/HMC Drive (Read) Cache Functions
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ *
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/jiffies.h>
+
+#include "hmcdrv_ftp.h"
+#include "hmcdrv_cache.h"
+
+#define HMCDRV_CACHE_TIMEOUT 30 /* aging timeout in seconds */
+
+/**
+ * struct hmcdrv_cache_entry - file cache (only used on read/dir)
+ * @id: FTP command ID
+ * @content: kernel-space buffer, 4k aligned
+ * @len: size of @content cache (0 if caching disabled)
+ * @ofs: start of content within file (-1 if no cached content)
+ * @fname: file name
+ * @fsize: file size
+ * @timeout: cache timeout in jiffies
+ *
+ * Notice that the first three members (id, fname, fsize) are cached on all
+ * read/dir requests. But content is cached only under some preconditions.
+ * Uncached content is signalled by a negative value of @ofs.
+ */
+struct hmcdrv_cache_entry {
+ enum hmcdrv_ftp_cmdid id;
+ char fname[HMCDRV_FTP_FIDENT_MAX];
+ size_t fsize;
+ loff_t ofs;
+ unsigned long timeout;
+ void *content;
+ size_t len;
+};
+
+static int hmcdrv_cache_order; /* cache allocated page order */
+
+static struct hmcdrv_cache_entry hmcdrv_cache_file = {
+ .fsize = SIZE_MAX,
+ .ofs = -1,
+ .len = 0,
+ .fname = {'\0'}
+};
+
+/**
+ * hmcdrv_cache_get() - looks for file data/content in read cache
+ * @ftp: pointer to FTP command specification
+ *
+ * Return: number of bytes read from cache or a negative number if nothing
+ * in content cache (for the file/cmd specified in @ftp)
+ */
+static ssize_t hmcdrv_cache_get(const struct hmcdrv_ftp_cmdspec *ftp)
+{
+ loff_t pos; /* position in cache (signed) */
+ ssize_t len;
+
+ if ((ftp->id != hmcdrv_cache_file.id) ||
+ strcmp(hmcdrv_cache_file.fname, ftp->fname))
+ return -1;
+
+ if (ftp->ofs >= hmcdrv_cache_file.fsize) /* EOF ? */
+ return 0;
+
+ if ((hmcdrv_cache_file.ofs < 0) || /* has content? */
+ time_after(jiffies, hmcdrv_cache_file.timeout))
+ return -1;
+
+ /* there seems to be cached content - calculate the maximum number
+ * of bytes that can be returned (regarding file size and offset)
+ */
+ len = hmcdrv_cache_file.fsize - ftp->ofs;
+
+ if (len > ftp->len)
+ len = ftp->len;
+
+ /* check if the requested chunk falls into our cache (which starts
+ * at offset 'hmcdrv_cache_file.ofs' in the file of interest)
+ */
+ pos = ftp->ofs - hmcdrv_cache_file.ofs;
+
+ if ((pos >= 0) &&
+ ((pos + len) <= hmcdrv_cache_file.len)) {
+
+ memcpy(ftp->buf,
+ hmcdrv_cache_file.content + pos,
+ len);
+ pr_debug("using cached content of '%s', returning %zd/%zd bytes\n",
+ hmcdrv_cache_file.fname, len,
+ hmcdrv_cache_file.fsize);
+
+ return len;
+ }
+
+ return -1;
+}
+
+/**
+ * hmcdrv_cache_do() - do a HMC drive CD/DVD transfer with cache update
+ * @ftp: pointer to FTP command specification
+ * @func: FTP transfer function to be used
+ *
+ * Return: number of bytes read/written or a (negative) error code
+ */
+static ssize_t hmcdrv_cache_do(const struct hmcdrv_ftp_cmdspec *ftp,
+ hmcdrv_cache_ftpfunc func)
+{
+ ssize_t len;
+
+ /* only cache content if the read/dir cache really exists
+ * (hmcdrv_cache_file.len > 0), is large enough to handle the
+ * request (hmcdrv_cache_file.len >= ftp->len) and there is a need
+ * to do so (ftp->len > 0)
+ */
+ if ((ftp->len > 0) && (hmcdrv_cache_file.len >= ftp->len)) {
+
+ /* because the cache is not located at ftp->buf, we have to
+ * assemble a new HMC drive FTP cmd specification (pointing
+ * to our cache, and using the increased size)
+ */
+ struct hmcdrv_ftp_cmdspec cftp = *ftp; /* make a copy */
+ cftp.buf = hmcdrv_cache_file.content; /* and update */
+ cftp.len = hmcdrv_cache_file.len; /* buffer data */
+
+ len = func(&cftp, &hmcdrv_cache_file.fsize); /* now do */
+
+ if (len > 0) {
+ pr_debug("caching %zd bytes content for '%s'\n",
+ len, ftp->fname);
+
+ if (len > ftp->len)
+ len = ftp->len;
+
+ hmcdrv_cache_file.ofs = ftp->ofs;
+ hmcdrv_cache_file.timeout = jiffies +
+ HMCDRV_CACHE_TIMEOUT * HZ;
+ memcpy(ftp->buf, hmcdrv_cache_file.content, len);
+ }
+ } else {
+ len = func(ftp, &hmcdrv_cache_file.fsize);
+ hmcdrv_cache_file.ofs = -1; /* invalidate content */
+ }
+
+ if (len > 0) {
+ /* cache some file info (FTP command, file name and file
+ * size) unconditionally
+ */
+ strlcpy(hmcdrv_cache_file.fname, ftp->fname,
+ HMCDRV_FTP_FIDENT_MAX);
+ hmcdrv_cache_file.id = ftp->id;
+ pr_debug("caching cmd %d, file size %zu for '%s'\n",
+ ftp->id, hmcdrv_cache_file.fsize, ftp->fname);
+ }
+
+ return len;
+}
+
+/**
+ * hmcdrv_cache_cmd() - perform a cached HMC drive CD/DVD transfer
+ * @ftp: pointer to FTP command specification
+ * @func: FTP transfer function to be used
+ *
+ * Attention: Notice that this function is not reentrant - so the caller
+ * must ensure exclusive execution.
+ *
+ * Return: number of bytes read/written or a (negative) error code
+ */
+ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp,
+ hmcdrv_cache_ftpfunc func)
+{
+ ssize_t len;
+
+ if ((ftp->id == HMCDRV_FTP_DIR) || /* read cache */
+ (ftp->id == HMCDRV_FTP_NLIST) ||
+ (ftp->id == HMCDRV_FTP_GET)) {
+
+ len = hmcdrv_cache_get(ftp);
+
+ if (len >= 0) /* got it from cache ? */
+ return len; /* yes */
+
+ len = hmcdrv_cache_do(ftp, func);
+
+ if (len >= 0)
+ return len;
+
+ } else {
+ len = func(ftp, NULL); /* simply do original command */
+ }
+
+ /* invalidate the (read) cache in case there was a write operation
+ * or an error on read/dir
+ */
+ hmcdrv_cache_file.id = HMCDRV_FTP_NOOP;
+ hmcdrv_cache_file.fsize = LLONG_MAX;
+ hmcdrv_cache_file.ofs = -1;
+
+ return len;
+}
+
+/**
+ * hmcdrv_cache_startup() - startup of HMC drive cache
+ * @cachesize: cache size
+ *
+ * Return: 0 on success, else a (negative) error code
+ */
+int hmcdrv_cache_startup(size_t cachesize)
+{
+ if (cachesize > 0) { /* perform caching ? */
+ hmcdrv_cache_order = get_order(cachesize);
+ hmcdrv_cache_file.content =
+ (void *) __get_free_pages(GFP_KERNEL | GFP_DMA,
+ hmcdrv_cache_order);
+
+ if (!hmcdrv_cache_file.content) {
+ pr_err("Allocating the requested cache size of %zu bytes failed\n",
+ cachesize);
+ return -ENOMEM;
+ }
+
+ pr_debug("content cache enabled, size is %zu bytes\n",
+ cachesize);
+ }
+
+ hmcdrv_cache_file.len = cachesize;
+ return 0;
+}
+
+/**
+ * hmcdrv_cache_shutdown() - shutdown of HMC drive cache
+ */
+void hmcdrv_cache_shutdown(void)
+{
+ if (hmcdrv_cache_file.content) {
+ free_pages((unsigned long) hmcdrv_cache_file.content,
+ hmcdrv_cache_order);
+ hmcdrv_cache_file.content = NULL;
+ }
+
+ hmcdrv_cache_file.id = HMCDRV_FTP_NOOP;
+ hmcdrv_cache_file.fsize = LLONG_MAX;
+ hmcdrv_cache_file.ofs = -1;
+ hmcdrv_cache_file.len = 0; /* no cache */
+}
diff --git a/drivers/s390/char/hmcdrv_cache.h b/drivers/s390/char/hmcdrv_cache.h
new file mode 100644
index 000000000000..a14b57526781
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_cache.h
@@ -0,0 +1,24 @@
+/*
+ * SE/HMC Drive (Read) Cache Functions
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __HMCDRV_CACHE_H__
+#define __HMCDRV_CACHE_H__
+
+#include <linux/mmzone.h>
+#include "hmcdrv_ftp.h"
+
+#define HMCDRV_CACHE_SIZE_DFLT (MAX_ORDER_NR_PAGES * PAGE_SIZE / 2UL)
+
+typedef ssize_t (*hmcdrv_cache_ftpfunc)(const struct hmcdrv_ftp_cmdspec *ftp,
+ size_t *fsize);
+
+ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp,
+ hmcdrv_cache_ftpfunc func);
+int hmcdrv_cache_startup(size_t cachesize);
+void hmcdrv_cache_shutdown(void);
+
+#endif /* __HMCDRV_CACHE_H__ */
diff --git a/drivers/s390/char/hmcdrv_dev.c b/drivers/s390/char/hmcdrv_dev.c
new file mode 100644
index 000000000000..43cee7fcd01c
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_dev.c
@@ -0,0 +1,368 @@
+/*
+ * HMC Drive CD/DVD Device
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ *
+ * This file provides a Linux "misc" character device for access to an
+ * assigned HMC drive CD/DVD-ROM. It works as follows: First create the
+ * device by calling hmcdrv_dev_init(). After open() a lseek(fd, 0,
+ * SEEK_END) indicates that a new FTP command follows (not needed on the
+ * first command after open). Then write() the FTP command ASCII string
+ * to it, e.g. "dir /" or "nls <directory>" or "get <filename>". At the
+ * end read() the response.
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/miscdevice.h>
+#include <linux/device.h>
+#include <linux/capability.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+
+#include "hmcdrv_dev.h"
+#include "hmcdrv_ftp.h"
+
+/* If the following macro is defined, then the HMC device creates it's own
+ * separated device class (and dynamically assigns a major number). If not
+ * defined then the HMC device is assigned to the "misc" class devices.
+ *
+#define HMCDRV_DEV_CLASS "hmcftp"
+ */
+
+#define HMCDRV_DEV_NAME "hmcdrv"
+#define HMCDRV_DEV_BUSY_DELAY 500 /* delay between -EBUSY trials in ms */
+#define HMCDRV_DEV_BUSY_RETRIES 3 /* number of retries on -EBUSY */
+
+struct hmcdrv_dev_node {
+
+#ifdef HMCDRV_DEV_CLASS
+ struct cdev dev; /* character device structure */
+ umode_t mode; /* mode of device node (unused, zero) */
+#else
+ struct miscdevice dev; /* "misc" device structure */
+#endif
+
+};
+
+static int hmcdrv_dev_open(struct inode *inode, struct file *fp);
+static int hmcdrv_dev_release(struct inode *inode, struct file *fp);
+static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence);
+static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf,
+ size_t len, loff_t *pos);
+static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf,
+ size_t len, loff_t *pos);
+static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset,
+ char __user *buf, size_t len);
+
+/*
+ * device operations
+ */
+static const struct file_operations hmcdrv_dev_fops = {
+ .open = hmcdrv_dev_open,
+ .llseek = hmcdrv_dev_seek,
+ .release = hmcdrv_dev_release,
+ .read = hmcdrv_dev_read,
+ .write = hmcdrv_dev_write,
+};
+
+static struct hmcdrv_dev_node hmcdrv_dev; /* HMC device struct (static) */
+
+#ifdef HMCDRV_DEV_CLASS
+
+static struct class *hmcdrv_dev_class; /* device class pointer */
+static dev_t hmcdrv_dev_no; /* device number (major/minor) */
+
+/**
+ * hmcdrv_dev_name() - provides a naming hint for a device node in /dev
+ * @dev: device for which the naming/mode hint is
+ * @mode: file mode for device node created in /dev
+ *
+ * See: devtmpfs.c, function devtmpfs_create_node()
+ *
+ * Return: recommended device file name in /dev
+ */
+static char *hmcdrv_dev_name(struct device *dev, umode_t *mode)
+{
+ char *nodename = NULL;
+ const char *devname = dev_name(dev); /* kernel device name */
+
+ if (devname)
+ nodename = kasprintf(GFP_KERNEL, "%s", devname);
+
+ /* on device destroy (rmmod) the mode pointer may be NULL
+ */
+ if (mode)
+ *mode = hmcdrv_dev.mode;
+
+ return nodename;
+}
+
+#endif /* HMCDRV_DEV_CLASS */
+
+/*
+ * open()
+ */
+static int hmcdrv_dev_open(struct inode *inode, struct file *fp)
+{
+ int rc;
+
+ /* check for non-blocking access, which is really unsupported
+ */
+ if (fp->f_flags & O_NONBLOCK)
+ return -EINVAL;
+
+ /* Because it makes no sense to open this device read-only (then a
+ * FTP command cannot be emitted), we respond with an error.
+ */
+ if ((fp->f_flags & O_ACCMODE) == O_RDONLY)
+ return -EINVAL;
+
+ /* prevent unloading this module as long as anyone holds the
+ * device file open - so increment the reference count here
+ */
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+
+ fp->private_data = NULL; /* no command yet */
+ rc = hmcdrv_ftp_startup();
+ if (rc)
+ module_put(THIS_MODULE);
+
+ pr_debug("open file '/dev/%pD' with return code %d\n", fp, rc);
+ return rc;
+}
+
+/*
+ * release()
+ */
+static int hmcdrv_dev_release(struct inode *inode, struct file *fp)
+{
+ pr_debug("closing file '/dev/%pD'\n", fp);
+ kfree(fp->private_data);
+ fp->private_data = NULL;
+ hmcdrv_ftp_shutdown();
+ module_put(THIS_MODULE);
+ return 0;
+}
+
+/*
+ * lseek()
+ */
+static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence)
+{
+ switch (whence) {
+ case SEEK_CUR: /* relative to current file position */
+ pos += fp->f_pos; /* new position stored in 'pos' */
+ break;
+
+ case SEEK_SET: /* absolute (relative to beginning of file) */
+ break; /* SEEK_SET */
+
+ /* We use SEEK_END as a special indicator for a SEEK_SET
+ * (set absolute position), combined with a FTP command
+ * clear.
+ */
+ case SEEK_END:
+ if (fp->private_data) {
+ kfree(fp->private_data);
+ fp->private_data = NULL;
+ }
+
+ break; /* SEEK_END */
+
+ default: /* SEEK_DATA, SEEK_HOLE: unsupported */
+ return -EINVAL;
+ }
+
+ if (pos < 0)
+ return -EINVAL;
+
+ if (fp->f_pos != pos)
+ ++fp->f_version;
+
+ fp->f_pos = pos;
+ return pos;
+}
+
+/*
+ * transfer (helper function)
+ */
+static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset,
+ char __user *buf, size_t len)
+{
+ ssize_t retlen;
+ unsigned trials = HMCDRV_DEV_BUSY_RETRIES;
+
+ do {
+ retlen = hmcdrv_ftp_cmd(cmd, offset, buf, len);
+
+ if (retlen != -EBUSY)
+ break;
+
+ msleep(HMCDRV_DEV_BUSY_DELAY);
+
+ } while (--trials > 0);
+
+ return retlen;
+}
+
+/*
+ * read()
+ */
+static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf,
+ size_t len, loff_t *pos)
+{
+ ssize_t retlen;
+
+ if (((fp->f_flags & O_ACCMODE) == O_WRONLY) ||
+ (fp->private_data == NULL)) { /* no FTP cmd defined ? */
+ return -EBADF;
+ }
+
+ retlen = hmcdrv_dev_transfer((char *) fp->private_data,
+ *pos, ubuf, len);
+
+ pr_debug("read from file '/dev/%pD' at %lld returns %zd/%zu\n",
+ fp, (long long) *pos, retlen, len);
+
+ if (retlen > 0)
+ *pos += retlen;
+
+ return retlen;
+}
+
+/*
+ * write()
+ */
+static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf,
+ size_t len, loff_t *pos)
+{
+ ssize_t retlen;
+
+ pr_debug("writing file '/dev/%pD' at pos. %lld with length %zd\n",
+ fp, (long long) *pos, len);
+
+ if (!fp->private_data) { /* first expect a cmd write */
+ fp->private_data = kmalloc(len + 1, GFP_KERNEL);
+
+ if (!fp->private_data)
+ return -ENOMEM;
+
+ if (!copy_from_user(fp->private_data, ubuf, len)) {
+ ((char *)fp->private_data)[len] = '\0';
+ return len;
+ }
+
+ kfree(fp->private_data);
+ fp->private_data = NULL;
+ return -EFAULT;
+ }
+
+ retlen = hmcdrv_dev_transfer((char *) fp->private_data,
+ *pos, (char __user *) ubuf, len);
+ if (retlen > 0)
+ *pos += retlen;
+
+ pr_debug("write to file '/dev/%pD' returned %zd\n", fp, retlen);
+
+ return retlen;
+}
+
+/**
+ * hmcdrv_dev_init() - creates a HMC drive CD/DVD device
+ *
+ * This function creates a HMC drive CD/DVD kernel device and an associated
+ * device under /dev, using a dynamically allocated major number.
+ *
+ * Return: 0 on success, else an error code.
+ */
+int hmcdrv_dev_init(void)
+{
+ int rc;
+
+#ifdef HMCDRV_DEV_CLASS
+ struct device *dev;
+
+ rc = alloc_chrdev_region(&hmcdrv_dev_no, 0, 1, HMCDRV_DEV_NAME);
+
+ if (rc)
+ goto out_err;
+
+ cdev_init(&hmcdrv_dev.dev, &hmcdrv_dev_fops);
+ hmcdrv_dev.dev.owner = THIS_MODULE;
+ rc = cdev_add(&hmcdrv_dev.dev, hmcdrv_dev_no, 1);
+
+ if (rc)
+ goto out_unreg;
+
+ /* At this point the character device exists in the kernel (see
+ * /proc/devices), but not under /dev nor /sys/devices/virtual. So
+ * we have to create an associated class (see /sys/class).
+ */
+ hmcdrv_dev_class = class_create(THIS_MODULE, HMCDRV_DEV_CLASS);
+
+ if (IS_ERR(hmcdrv_dev_class)) {
+ rc = PTR_ERR(hmcdrv_dev_class);
+ goto out_devdel;
+ }
+
+ /* Finally a device node in /dev has to be established (as 'mkdev'
+ * does from the command line). Notice that assignment of a device
+ * node name/mode function is optional (only for mode != 0600).
+ */
+ hmcdrv_dev.mode = 0; /* "unset" */
+ hmcdrv_dev_class->devnode = hmcdrv_dev_name;
+
+ dev = device_create(hmcdrv_dev_class, NULL, hmcdrv_dev_no, NULL,
+ "%s", HMCDRV_DEV_NAME);
+ if (!IS_ERR(dev))
+ return 0;
+
+ rc = PTR_ERR(dev);
+ class_destroy(hmcdrv_dev_class);
+ hmcdrv_dev_class = NULL;
+
+out_devdel:
+ cdev_del(&hmcdrv_dev.dev);
+
+out_unreg:
+ unregister_chrdev_region(hmcdrv_dev_no, 1);
+
+out_err:
+
+#else /* !HMCDRV_DEV_CLASS */
+ hmcdrv_dev.dev.minor = MISC_DYNAMIC_MINOR;
+ hmcdrv_dev.dev.name = HMCDRV_DEV_NAME;
+ hmcdrv_dev.dev.fops = &hmcdrv_dev_fops;
+ hmcdrv_dev.dev.mode = 0; /* finally produces 0600 */
+ rc = misc_register(&hmcdrv_dev.dev);
+#endif /* HMCDRV_DEV_CLASS */
+
+ return rc;
+}
+
+/**
+ * hmcdrv_dev_exit() - destroys a HMC drive CD/DVD device
+ */
+void hmcdrv_dev_exit(void)
+{
+#ifdef HMCDRV_DEV_CLASS
+ if (!IS_ERR_OR_NULL(hmcdrv_dev_class)) {
+ device_destroy(hmcdrv_dev_class, hmcdrv_dev_no);
+ class_destroy(hmcdrv_dev_class);
+ }
+
+ cdev_del(&hmcdrv_dev.dev);
+ unregister_chrdev_region(hmcdrv_dev_no, 1);
+#else /* !HMCDRV_DEV_CLASS */
+ misc_deregister(&hmcdrv_dev.dev);
+#endif /* HMCDRV_DEV_CLASS */
+}
diff --git a/drivers/s390/char/hmcdrv_dev.h b/drivers/s390/char/hmcdrv_dev.h
new file mode 100644
index 000000000000..cb17f07e02de
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_dev.h
@@ -0,0 +1,14 @@
+/*
+ * SE/HMC Drive FTP Device
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __HMCDRV_DEV_H__
+#define __HMCDRV_DEV_H__
+
+int hmcdrv_dev_init(void);
+void hmcdrv_dev_exit(void);
+
+#endif /* __HMCDRV_DEV_H__ */
diff --git a/drivers/s390/char/hmcdrv_ftp.c b/drivers/s390/char/hmcdrv_ftp.c
new file mode 100644
index 000000000000..4bd63322fc29
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_ftp.c
@@ -0,0 +1,343 @@
+/*
+ * HMC Drive FTP Services
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/export.h>
+
+#include <linux/ctype.h>
+#include <linux/crc16.h>
+
+#include "hmcdrv_ftp.h"
+#include "hmcdrv_cache.h"
+#include "sclp_ftp.h"
+#include "diag_ftp.h"
+
+/**
+ * struct hmcdrv_ftp_ops - HMC drive FTP operations
+ * @startup: startup function
+ * @shutdown: shutdown function
+ * @cmd: FTP transfer function
+ */
+struct hmcdrv_ftp_ops {
+ int (*startup)(void);
+ void (*shutdown)(void);
+ ssize_t (*transfer)(const struct hmcdrv_ftp_cmdspec *ftp,
+ size_t *fsize);
+};
+
+static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len);
+static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp);
+
+static struct hmcdrv_ftp_ops *hmcdrv_ftp_funcs; /* current operations */
+static DEFINE_MUTEX(hmcdrv_ftp_mutex); /* mutex for hmcdrv_ftp_funcs */
+static unsigned hmcdrv_ftp_refcnt; /* start/shutdown reference counter */
+
+/**
+ * hmcdrv_ftp_cmd_getid() - determine FTP command ID from a command string
+ * @cmd: FTP command string (NOT zero-terminated)
+ * @len: length of FTP command string in @cmd
+ */
+static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len)
+{
+ /* HMC FTP command descriptor */
+ struct hmcdrv_ftp_cmd_desc {
+ const char *str; /* command string */
+ enum hmcdrv_ftp_cmdid cmd; /* associated command as enum */
+ };
+
+ /* Description of all HMC drive FTP commands
+ *
+ * Notes:
+ * 1. Array size should be a prime number.
+ * 2. Do not change the order of commands in table (because the
+ * index is determined by CRC % ARRAY_SIZE).
+ * 3. Original command 'nlist' was renamed, else the CRC would
+ * collide with 'append' (see point 2).
+ */
+ static const struct hmcdrv_ftp_cmd_desc ftpcmds[7] = {
+
+ {.str = "get", /* [0] get (CRC = 0x68eb) */
+ .cmd = HMCDRV_FTP_GET},
+ {.str = "dir", /* [1] dir (CRC = 0x6a9e) */
+ .cmd = HMCDRV_FTP_DIR},
+ {.str = "delete", /* [2] delete (CRC = 0x53ae) */
+ .cmd = HMCDRV_FTP_DELETE},
+ {.str = "nls", /* [3] nls (CRC = 0xf87c) */
+ .cmd = HMCDRV_FTP_NLIST},
+ {.str = "put", /* [4] put (CRC = 0xac56) */
+ .cmd = HMCDRV_FTP_PUT},
+ {.str = "append", /* [5] append (CRC = 0xf56e) */
+ .cmd = HMCDRV_FTP_APPEND},
+ {.str = NULL} /* [6] unused */
+ };
+
+ const struct hmcdrv_ftp_cmd_desc *pdesc;
+
+ u16 crc = 0xffffU;
+
+ if (len == 0)
+ return HMCDRV_FTP_NOOP; /* error indiactor */
+
+ crc = crc16(crc, cmd, len);
+ pdesc = ftpcmds + (crc % ARRAY_SIZE(ftpcmds));
+ pr_debug("FTP command '%s' has CRC 0x%04x, at table pos. %lu\n",
+ cmd, crc, (crc % ARRAY_SIZE(ftpcmds)));
+
+ if (!pdesc->str || strncmp(pdesc->str, cmd, len))
+ return HMCDRV_FTP_NOOP;
+
+ pr_debug("FTP command '%s' found, with ID %d\n",
+ pdesc->str, pdesc->cmd);
+
+ return pdesc->cmd;
+}
+
+/**
+ * hmcdrv_ftp_parse() - HMC drive FTP command parser
+ * @cmd: FTP command string "<cmd> <filename>"
+ * @ftp: Pointer to FTP command specification buffer (output)
+ *
+ * Return: 0 on success, else a (negative) error code
+ */
+static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp)
+{
+ char *start;
+ int argc = 0;
+
+ ftp->id = HMCDRV_FTP_NOOP;
+ ftp->fname = NULL;
+
+ while (*cmd != '\0') {
+
+ while (isspace(*cmd))
+ ++cmd;
+
+ if (*cmd == '\0')
+ break;
+
+ start = cmd;
+
+ switch (argc) {
+ case 0: /* 1st argument (FTP command) */
+ while ((*cmd != '\0') && !isspace(*cmd))
+ ++cmd;
+ ftp->id = hmcdrv_ftp_cmd_getid(start, cmd - start);
+ break;
+ case 1: /* 2nd / last argument (rest of line) */
+ while ((*cmd != '\0') && !iscntrl(*cmd))
+ ++cmd;
+ ftp->fname = start;
+ /* fall through */
+ default:
+ *cmd = '\0';
+ break;
+ } /* switch */
+
+ ++argc;
+ } /* while */
+
+ if (!ftp->fname || (ftp->id == HMCDRV_FTP_NOOP))
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * hmcdrv_ftp_do() - perform a HMC drive FTP, with data from kernel-space
+ * @ftp: pointer to FTP command specification
+ *
+ * Return: number of bytes read/written or a negative error code
+ */
+ssize_t hmcdrv_ftp_do(const struct hmcdrv_ftp_cmdspec *ftp)
+{
+ ssize_t len;
+
+ mutex_lock(&hmcdrv_ftp_mutex);
+
+ if (hmcdrv_ftp_funcs && hmcdrv_ftp_refcnt) {
+ pr_debug("starting transfer, cmd %d for '%s' at %lld with %zd bytes\n",
+ ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len);
+ len = hmcdrv_cache_cmd(ftp, hmcdrv_ftp_funcs->transfer);
+ } else {
+ len = -ENXIO;
+ }
+
+ mutex_unlock(&hmcdrv_ftp_mutex);
+ return len;
+}
+EXPORT_SYMBOL(hmcdrv_ftp_do);
+
+/**
+ * hmcdrv_ftp_probe() - probe for the HMC drive FTP service
+ *
+ * Return: 0 if service is available, else an (negative) error code
+ */
+int hmcdrv_ftp_probe(void)
+{
+ int rc;
+
+ struct hmcdrv_ftp_cmdspec ftp = {
+ .id = HMCDRV_FTP_NOOP,
+ .ofs = 0,
+ .fname = "",
+ .len = PAGE_SIZE
+ };
+
+ ftp.buf = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
+
+ if (!ftp.buf)
+ return -ENOMEM;
+
+ rc = hmcdrv_ftp_startup();
+
+ if (rc)
+ return rc;
+
+ rc = hmcdrv_ftp_do(&ftp);
+ free_page((unsigned long) ftp.buf);
+ hmcdrv_ftp_shutdown();
+
+ switch (rc) {
+ case -ENOENT: /* no such file/media or currently busy, */
+ case -EBUSY: /* but service seems to be available */
+ rc = 0;
+ break;
+ default: /* leave 'rc' as it is for [0, -EPERM, -E...] */
+ if (rc > 0)
+ rc = 0; /* clear length (success) */
+ break;
+ } /* switch */
+
+ return rc;
+}
+EXPORT_SYMBOL(hmcdrv_ftp_probe);
+
+/**
+ * hmcdrv_ftp_cmd() - Perform a HMC drive FTP, with data from user-space
+ *
+ * @cmd: FTP command string "<cmd> <filename>"
+ * @offset: file position to read/write
+ * @buf: user-space buffer for read/written directory/file
+ * @len: size of @buf (read/dir) or number of bytes to write
+ *
+ * This function must not be called before hmcdrv_ftp_startup() was called.
+ *
+ * Return: number of bytes read/written or a negative error code
+ */
+ssize_t hmcdrv_ftp_cmd(char __kernel *cmd, loff_t offset,
+ char __user *buf, size_t len)
+{
+ int order;
+
+ struct hmcdrv_ftp_cmdspec ftp = {.len = len, .ofs = offset};
+ ssize_t retlen = hmcdrv_ftp_parse(cmd, &ftp);
+
+ if (retlen)
+ return retlen;
+
+ order = get_order(ftp.len);
+ ftp.buf = (void *) __get_free_pages(GFP_KERNEL | GFP_DMA, order);
+
+ if (!ftp.buf)
+ return -ENOMEM;
+
+ switch (ftp.id) {
+ case HMCDRV_FTP_DIR:
+ case HMCDRV_FTP_NLIST:
+ case HMCDRV_FTP_GET:
+ retlen = hmcdrv_ftp_do(&ftp);
+
+ if ((retlen >= 0) &&
+ copy_to_user(buf, ftp.buf, retlen))
+ retlen = -EFAULT;
+ break;
+
+ case HMCDRV_FTP_PUT:
+ case HMCDRV_FTP_APPEND:
+ if (!copy_from_user(ftp.buf, buf, ftp.len))
+ retlen = hmcdrv_ftp_do(&ftp);
+ else
+ retlen = -EFAULT;
+ break;
+
+ case HMCDRV_FTP_DELETE:
+ retlen = hmcdrv_ftp_do(&ftp);
+ break;
+
+ default:
+ retlen = -EOPNOTSUPP;
+ break;
+ }
+
+ free_pages((unsigned long) ftp.buf, order);
+ return retlen;
+}
+
+/**
+ * hmcdrv_ftp_startup() - startup of HMC drive FTP functionality for a
+ * dedicated (owner) instance
+ *
+ * Return: 0 on success, else an (negative) error code
+ */
+int hmcdrv_ftp_startup(void)
+{
+ static struct hmcdrv_ftp_ops hmcdrv_ftp_zvm = {
+ .startup = diag_ftp_startup,
+ .shutdown = diag_ftp_shutdown,
+ .transfer = diag_ftp_cmd
+ };
+
+ static struct hmcdrv_ftp_ops hmcdrv_ftp_lpar = {
+ .startup = sclp_ftp_startup,
+ .shutdown = sclp_ftp_shutdown,
+ .transfer = sclp_ftp_cmd
+ };
+
+ int rc = 0;
+
+ mutex_lock(&hmcdrv_ftp_mutex); /* block transfers while start-up */
+
+ if (hmcdrv_ftp_refcnt == 0) {
+ if (MACHINE_IS_VM)
+ hmcdrv_ftp_funcs = &hmcdrv_ftp_zvm;
+ else if (MACHINE_IS_LPAR || MACHINE_IS_KVM)
+ hmcdrv_ftp_funcs = &hmcdrv_ftp_lpar;
+ else
+ rc = -EOPNOTSUPP;
+
+ if (hmcdrv_ftp_funcs)
+ rc = hmcdrv_ftp_funcs->startup();
+ }
+
+ if (!rc)
+ ++hmcdrv_ftp_refcnt;
+
+ mutex_unlock(&hmcdrv_ftp_mutex);
+ return rc;
+}
+EXPORT_SYMBOL(hmcdrv_ftp_startup);
+
+/**
+ * hmcdrv_ftp_shutdown() - shutdown of HMC drive FTP functionality for a
+ * dedicated (owner) instance
+ */
+void hmcdrv_ftp_shutdown(void)
+{
+ mutex_lock(&hmcdrv_ftp_mutex);
+ --hmcdrv_ftp_refcnt;
+
+ if ((hmcdrv_ftp_refcnt == 0) && hmcdrv_ftp_funcs)
+ hmcdrv_ftp_funcs->shutdown();
+
+ mutex_unlock(&hmcdrv_ftp_mutex);
+}
+EXPORT_SYMBOL(hmcdrv_ftp_shutdown);
diff --git a/drivers/s390/char/hmcdrv_ftp.h b/drivers/s390/char/hmcdrv_ftp.h
new file mode 100644
index 000000000000..f3643a7b3676
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_ftp.h
@@ -0,0 +1,63 @@
+/*
+ * SE/HMC Drive FTP Services
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __HMCDRV_FTP_H__
+#define __HMCDRV_FTP_H__
+
+#include <linux/types.h> /* size_t, loff_t */
+
+/*
+ * HMC drive FTP Service max. length of path (w/ EOS)
+ */
+#define HMCDRV_FTP_FIDENT_MAX 192
+
+/**
+ * enum hmcdrv_ftp_cmdid - HMC drive FTP commands
+ * @HMCDRV_FTP_NOOP: do nothing (only for probing)
+ * @HMCDRV_FTP_GET: read a file
+ * @HMCDRV_FTP_PUT: (over-) write a file
+ * @HMCDRV_FTP_APPEND: append to a file
+ * @HMCDRV_FTP_DIR: list directory long (ls -l)
+ * @HMCDRV_FTP_NLIST: list files, no directories (name list)
+ * @HMCDRV_FTP_DELETE: delete a file
+ * @HMCDRV_FTP_CANCEL: cancel operation (SCLP/LPAR only)
+ */
+enum hmcdrv_ftp_cmdid {
+ HMCDRV_FTP_NOOP = 0,
+ HMCDRV_FTP_GET = 1,
+ HMCDRV_FTP_PUT = 2,
+ HMCDRV_FTP_APPEND = 3,
+ HMCDRV_FTP_DIR = 4,
+ HMCDRV_FTP_NLIST = 5,
+ HMCDRV_FTP_DELETE = 6,
+ HMCDRV_FTP_CANCEL = 7
+};
+
+/**
+ * struct hmcdrv_ftp_cmdspec - FTP command specification
+ * @id: FTP command ID
+ * @ofs: offset in file
+ * @fname: filename (ASCII), null-terminated
+ * @buf: kernel-space transfer data buffer, 4k aligned
+ * @len: (max) number of bytes to transfer from/to @buf
+ */
+struct hmcdrv_ftp_cmdspec {
+ enum hmcdrv_ftp_cmdid id;
+ loff_t ofs;
+ const char *fname;
+ void __kernel *buf;
+ size_t len;
+};
+
+int hmcdrv_ftp_startup(void);
+void hmcdrv_ftp_shutdown(void);
+int hmcdrv_ftp_probe(void);
+ssize_t hmcdrv_ftp_do(const struct hmcdrv_ftp_cmdspec *ftp);
+ssize_t hmcdrv_ftp_cmd(char __kernel *cmd, loff_t offset,
+ char __user *buf, size_t len);
+
+#endif /* __HMCDRV_FTP_H__ */
diff --git a/drivers/s390/char/hmcdrv_mod.c b/drivers/s390/char/hmcdrv_mod.c
new file mode 100644
index 000000000000..505c6a78ee1a
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_mod.c
@@ -0,0 +1,64 @@
+/*
+ * HMC Drive DVD Module
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/stat.h>
+
+#include "hmcdrv_ftp.h"
+#include "hmcdrv_dev.h"
+#include "hmcdrv_cache.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Copyright 2013 IBM Corporation");
+MODULE_DESCRIPTION("HMC drive DVD access");
+
+/*
+ * module parameter 'cachesize'
+ */
+static size_t hmcdrv_mod_cachesize = HMCDRV_CACHE_SIZE_DFLT;
+module_param_named(cachesize, hmcdrv_mod_cachesize, ulong, S_IRUGO);
+
+/**
+ * hmcdrv_mod_init() - module init function
+ */
+static int __init hmcdrv_mod_init(void)
+{
+ int rc = hmcdrv_ftp_probe(); /* perform w/o cache */
+
+ if (rc)
+ return rc;
+
+ rc = hmcdrv_cache_startup(hmcdrv_mod_cachesize);
+
+ if (rc)
+ return rc;
+
+ rc = hmcdrv_dev_init();
+
+ if (rc)
+ hmcdrv_cache_shutdown();
+
+ return rc;
+}
+
+/**
+ * hmcdrv_mod_exit() - module exit function
+ */
+static void __exit hmcdrv_mod_exit(void)
+{
+ hmcdrv_dev_exit();
+ hmcdrv_cache_shutdown();
+}
+
+module_init(hmcdrv_mod_init);
+module_exit(hmcdrv_mod_exit);
diff --git a/drivers/s390/char/monwriter.c b/drivers/s390/char/monwriter.c
index 668b32b0dc1d..9b5d1138b2e2 100644
--- a/drivers/s390/char/monwriter.c
+++ b/drivers/s390/char/monwriter.c
@@ -334,7 +334,6 @@ static const struct dev_pm_ops monwriter_pm_ops = {
static struct platform_driver monwriter_pdrv = {
.driver = {
.name = "monwriter",
- .owner = THIS_MODULE,
.pm = &monwriter_pm_ops,
},
};
diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c
index c316051d9bda..41ba56d2e752 100644
--- a/drivers/s390/char/sclp.c
+++ b/drivers/s390/char/sclp.c
@@ -1167,7 +1167,6 @@ static const struct attribute_group *sclp_drv_attr_groups[] = {
static struct platform_driver sclp_pdrv = {
.driver = {
.name = "sclp",
- .owner = THIS_MODULE,
.pm = &sclp_pm_ops,
.groups = sclp_drv_attr_groups,
},
diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h
index a68b5ec7d042..a88069f8c677 100644
--- a/drivers/s390/char/sclp.h
+++ b/drivers/s390/char/sclp.h
@@ -19,6 +19,7 @@
#define EVTYP_OPCMD 0x01
#define EVTYP_MSG 0x02
+#define EVTYP_DIAG_TEST 0x07
#define EVTYP_STATECHANGE 0x08
#define EVTYP_PMSGCMD 0x09
#define EVTYP_CNTLPROGOPCMD 0x20
@@ -32,6 +33,7 @@
#define EVTYP_OPCMD_MASK 0x80000000
#define EVTYP_MSG_MASK 0x40000000
+#define EVTYP_DIAG_TEST_MASK 0x02000000
#define EVTYP_STATECHANGE_MASK 0x01000000
#define EVTYP_PMSGCMD_MASK 0x00800000
#define EVTYP_CTLPROGOPCMD_MASK 0x00000001
diff --git a/drivers/s390/char/sclp_async.c b/drivers/s390/char/sclp_async.c
index 5f9f929e891c..19c25427f27f 100644
--- a/drivers/s390/char/sclp_async.c
+++ b/drivers/s390/char/sclp_async.c
@@ -137,7 +137,8 @@ static int sclp_async_send_wait(char *message)
* Retain Queue
* e.g. 5639CC140 500 Red Hat RHEL5 Linux for zSeries (RHEL AS)
*/
- strncpy(sccb->evbuf.comp_id, "000000000", sizeof(sccb->evbuf.comp_id));
+ strncpy(sccb->evbuf.comp_id, CONFIG_SCLP_ASYNC_ID,
+ sizeof(sccb->evbuf.comp_id));
sccb->evbuf.header.length = sizeof(sccb->evbuf);
sccb->header.length = sizeof(sccb->evbuf) + sizeof(sccb->header);
sccb->header.function_code = SCLP_NORMAL_WRITE;
diff --git a/drivers/s390/char/sclp_diag.h b/drivers/s390/char/sclp_diag.h
new file mode 100644
index 000000000000..59c4afa5e670
--- /dev/null
+++ b/drivers/s390/char/sclp_diag.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef _SCLP_DIAG_H
+#define _SCLP_DIAG_H
+
+#include <linux/types.h>
+
+/* return codes for Diagnostic Test FTP Service, as indicated in member
+ * sclp_diag_ftp::ldflg
+ */
+#define SCLP_DIAG_FTP_OK 0x80U /* success */
+#define SCLP_DIAG_FTP_LDFAIL 0x01U /* load failed */
+#define SCLP_DIAG_FTP_LDNPERM 0x02U /* not allowed */
+#define SCLP_DIAG_FTP_LDRUNS 0x03U /* LD runs */
+#define SCLP_DIAG_FTP_LDNRUNS 0x04U /* LD does not run */
+
+#define SCLP_DIAG_FTP_XPCX 0x80 /* PCX communication code */
+#define SCLP_DIAG_FTP_ROUTE 4 /* routing code for new FTP service */
+
+/*
+ * length of Diagnostic Test FTP Service event buffer
+ */
+#define SCLP_DIAG_FTP_EVBUF_LEN \
+ (offsetof(struct sclp_diag_evbuf, mdd) + \
+ sizeof(struct sclp_diag_ftp))
+
+/**
+ * struct sclp_diag_ftp - Diagnostic Test FTP Service model-dependent data
+ * @pcx: code for PCX communication (should be 0x80)
+ * @ldflg: load flag (see defines above)
+ * @cmd: FTP command
+ * @pgsize: page size (0 = 4kB, 1 = large page size)
+ * @srcflg: source flag
+ * @spare: reserved (zeroes)
+ * @offset: file offset
+ * @fsize: file size
+ * @length: buffer size resp. bytes transferred
+ * @failaddr: failing address
+ * @bufaddr: buffer address, virtual
+ * @asce: region or segment table designation
+ * @fident: file name (ASCII, zero-terminated)
+ */
+struct sclp_diag_ftp {
+ u8 pcx;
+ u8 ldflg;
+ u8 cmd;
+ u8 pgsize;
+ u8 srcflg;
+ u8 spare;
+ u64 offset;
+ u64 fsize;
+ u64 length;
+ u64 failaddr;
+ u64 bufaddr;
+ u64 asce;
+
+ u8 fident[256];
+} __packed;
+
+/**
+ * struct sclp_diag_evbuf - Diagnostic Test (ET7) Event Buffer
+ * @hdr: event buffer header
+ * @route: diagnostic route
+ * @mdd: model-dependent data (@route dependent)
+ */
+struct sclp_diag_evbuf {
+ struct evbuf_header hdr;
+ u16 route;
+
+ union {
+ struct sclp_diag_ftp ftp;
+ } mdd;
+} __packed;
+
+/**
+ * struct sclp_diag_sccb - Diagnostic Test (ET7) SCCB
+ * @hdr: SCCB header
+ * @evbuf: event buffer
+ */
+struct sclp_diag_sccb {
+
+ struct sccb_header hdr;
+ struct sclp_diag_evbuf evbuf;
+} __packed;
+
+#endif /* _SCLP_DIAG_H */
diff --git a/drivers/s390/char/sclp_early.c b/drivers/s390/char/sclp_early.c
index 1918d9dff45d..5bd6cb145a87 100644
--- a/drivers/s390/char/sclp_early.c
+++ b/drivers/s390/char/sclp_early.c
@@ -281,7 +281,7 @@ out:
static unsigned int __init sclp_con_check_linemode(struct init_sccb *sccb)
{
- if (!(sccb->sclp_send_mask & (EVTYP_OPCMD_MASK | EVTYP_PMSGCMD_MASK)))
+ if (!(sccb->sclp_send_mask & EVTYP_OPCMD_MASK))
return 0;
if (!(sccb->sclp_receive_mask & (EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK)))
return 0;
diff --git a/drivers/s390/char/sclp_ftp.c b/drivers/s390/char/sclp_ftp.c
new file mode 100644
index 000000000000..6561cc5b2d5d
--- /dev/null
+++ b/drivers/s390/char/sclp_ftp.c
@@ -0,0 +1,275 @@
+/*
+ * SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ *
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/string.h>
+#include <linux/jiffies.h>
+#include <asm/sysinfo.h>
+#include <asm/ebcdic.h>
+
+#include "sclp.h"
+#include "sclp_diag.h"
+#include "sclp_ftp.h"
+
+static DECLARE_COMPLETION(sclp_ftp_rx_complete);
+static u8 sclp_ftp_ldflg;
+static u64 sclp_ftp_fsize;
+static u64 sclp_ftp_length;
+
+/**
+ * sclp_ftp_txcb() - Diagnostic Test FTP services SCLP command callback
+ */
+static void sclp_ftp_txcb(struct sclp_req *req, void *data)
+{
+ struct completion *completion = data;
+
+#ifdef DEBUG
+ pr_debug("SCLP (ET7) TX-IRQ, SCCB @ 0x%p: %*phN\n",
+ req->sccb, 24, req->sccb);
+#endif
+ complete(completion);
+}
+
+/**
+ * sclp_ftp_rxcb() - Diagnostic Test FTP services receiver event callback
+ */
+static void sclp_ftp_rxcb(struct evbuf_header *evbuf)
+{
+ struct sclp_diag_evbuf *diag = (struct sclp_diag_evbuf *) evbuf;
+
+ /*
+ * Check for Diagnostic Test FTP Service
+ */
+ if (evbuf->type != EVTYP_DIAG_TEST ||
+ diag->route != SCLP_DIAG_FTP_ROUTE ||
+ diag->mdd.ftp.pcx != SCLP_DIAG_FTP_XPCX ||
+ evbuf->length < SCLP_DIAG_FTP_EVBUF_LEN)
+ return;
+
+#ifdef DEBUG
+ pr_debug("SCLP (ET7) RX-IRQ, Event @ 0x%p: %*phN\n",
+ evbuf, 24, evbuf);
+#endif
+
+ /*
+ * Because the event buffer is located in a page which is owned
+ * by the SCLP core, all data of interest must be copied. The
+ * error indication is in 'sclp_ftp_ldflg'
+ */
+ sclp_ftp_ldflg = diag->mdd.ftp.ldflg;
+ sclp_ftp_fsize = diag->mdd.ftp.fsize;
+ sclp_ftp_length = diag->mdd.ftp.length;
+
+ complete(&sclp_ftp_rx_complete);
+}
+
+/**
+ * sclp_ftp_et7() - start a Diagnostic Test FTP Service SCLP request
+ * @ftp: pointer to FTP descriptor
+ *
+ * Return: 0 on success, else a (negative) error code
+ */
+static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp)
+{
+ struct completion completion;
+ struct sclp_diag_sccb *sccb;
+ struct sclp_req *req;
+ size_t len;
+ int rc;
+
+ req = kzalloc(sizeof(*req), GFP_KERNEL);
+ sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!req || !sccb) {
+ rc = -ENOMEM;
+ goto out_free;
+ }
+
+ sccb->hdr.length = SCLP_DIAG_FTP_EVBUF_LEN +
+ sizeof(struct sccb_header);
+ sccb->evbuf.hdr.type = EVTYP_DIAG_TEST;
+ sccb->evbuf.hdr.length = SCLP_DIAG_FTP_EVBUF_LEN;
+ sccb->evbuf.hdr.flags = 0; /* clear processed-buffer */
+ sccb->evbuf.route = SCLP_DIAG_FTP_ROUTE;
+ sccb->evbuf.mdd.ftp.pcx = SCLP_DIAG_FTP_XPCX;
+ sccb->evbuf.mdd.ftp.srcflg = 0;
+ sccb->evbuf.mdd.ftp.pgsize = 0;
+ sccb->evbuf.mdd.ftp.asce = _ASCE_REAL_SPACE;
+ sccb->evbuf.mdd.ftp.ldflg = SCLP_DIAG_FTP_LDFAIL;
+ sccb->evbuf.mdd.ftp.fsize = 0;
+ sccb->evbuf.mdd.ftp.cmd = ftp->id;
+ sccb->evbuf.mdd.ftp.offset = ftp->ofs;
+ sccb->evbuf.mdd.ftp.length = ftp->len;
+ sccb->evbuf.mdd.ftp.bufaddr = virt_to_phys(ftp->buf);
+
+ len = strlcpy(sccb->evbuf.mdd.ftp.fident, ftp->fname,
+ HMCDRV_FTP_FIDENT_MAX);
+ if (len >= HMCDRV_FTP_FIDENT_MAX) {
+ rc = -EINVAL;
+ goto out_free;
+ }
+
+ req->command = SCLP_CMDW_WRITE_EVENT_DATA;
+ req->sccb = sccb;
+ req->status = SCLP_REQ_FILLED;
+ req->callback = sclp_ftp_txcb;
+ req->callback_data = &completion;
+
+ init_completion(&completion);
+
+ rc = sclp_add_request(req);
+ if (rc)
+ goto out_free;
+
+ /* Wait for end of ftp sclp command. */
+ wait_for_completion(&completion);
+
+#ifdef DEBUG
+ pr_debug("status of SCLP (ET7) request is 0x%04x (0x%02x)\n",
+ sccb->hdr.response_code, sccb->evbuf.hdr.flags);
+#endif
+
+ /*
+ * Check if sclp accepted the request. The data transfer runs
+ * asynchronously and the completion is indicated with an
+ * sclp ET7 event.
+ */
+ if (req->status != SCLP_REQ_DONE ||
+ (sccb->evbuf.hdr.flags & 0x80) == 0 || /* processed-buffer */
+ (sccb->hdr.response_code & 0xffU) != 0x20U) {
+ rc = -EIO;
+ }
+
+out_free:
+ free_page((unsigned long) sccb);
+ kfree(req);
+ return rc;
+}
+
+/**
+ * sclp_ftp_cmd() - executes a HMC related SCLP Diagnose (ET7) FTP command
+ * @ftp: pointer to FTP command specification
+ * @fsize: return of file size (or NULL if undesirable)
+ *
+ * Attention: Notice that this function is not reentrant - so the caller
+ * must ensure locking.
+ *
+ * Return: number of bytes read/written or a (negative) error code
+ */
+ssize_t sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize)
+{
+ ssize_t len;
+#ifdef DEBUG
+ unsigned long start_jiffies;
+
+ pr_debug("starting SCLP (ET7), cmd %d for '%s' at %lld with %zd bytes\n",
+ ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len);
+ start_jiffies = jiffies;
+#endif
+
+ init_completion(&sclp_ftp_rx_complete);
+
+ /* Start ftp sclp command. */
+ len = sclp_ftp_et7(ftp);
+ if (len)
+ goto out_unlock;
+
+ /*
+ * There is no way to cancel the sclp ET7 request, the code
+ * needs to wait unconditionally until the transfer is complete.
+ */
+ wait_for_completion(&sclp_ftp_rx_complete);
+
+#ifdef DEBUG
+ pr_debug("completed SCLP (ET7) request after %lu ms (all)\n",
+ (jiffies - start_jiffies) * 1000 / HZ);
+ pr_debug("return code of SCLP (ET7) FTP Service is 0x%02x, with %lld/%lld bytes\n",
+ sclp_ftp_ldflg, sclp_ftp_length, sclp_ftp_fsize);
+#endif
+
+ switch (sclp_ftp_ldflg) {
+ case SCLP_DIAG_FTP_OK:
+ len = sclp_ftp_length;
+ if (fsize)
+ *fsize = sclp_ftp_fsize;
+ break;
+ case SCLP_DIAG_FTP_LDNPERM:
+ len = -EPERM;
+ break;
+ case SCLP_DIAG_FTP_LDRUNS:
+ len = -EBUSY;
+ break;
+ case SCLP_DIAG_FTP_LDFAIL:
+ len = -ENOENT;
+ break;
+ default:
+ len = -EIO;
+ break;
+ }
+
+out_unlock:
+ return len;
+}
+
+/*
+ * ET7 event listener
+ */
+static struct sclp_register sclp_ftp_event = {
+ .send_mask = EVTYP_DIAG_TEST_MASK, /* want tx events */
+ .receive_mask = EVTYP_DIAG_TEST_MASK, /* want rx events */
+ .receiver_fn = sclp_ftp_rxcb, /* async callback (rx) */
+ .state_change_fn = NULL,
+ .pm_event_fn = NULL,
+};
+
+/**
+ * sclp_ftp_startup() - startup of FTP services, when running on LPAR
+ */
+int sclp_ftp_startup(void)
+{
+#ifdef DEBUG
+ unsigned long info;
+#endif
+ int rc;
+
+ rc = sclp_register(&sclp_ftp_event);
+ if (rc)
+ return rc;
+
+#ifdef DEBUG
+ info = get_zeroed_page(GFP_KERNEL);
+
+ if (info != 0) {
+ struct sysinfo_2_2_2 *info222 = (struct sysinfo_2_2_2 *)info;
+
+ if (!stsi(info222, 2, 2, 2)) { /* get SYSIB 2.2.2 */
+ info222->name[sizeof(info222->name) - 1] = '\0';
+ EBCASC_500(info222->name, sizeof(info222->name) - 1);
+ pr_debug("SCLP (ET7) FTP Service working on LPAR %u (%s)\n",
+ info222->lpar_number, info222->name);
+ }
+
+ free_page(info);
+ }
+#endif /* DEBUG */
+ return 0;
+}
+
+/**
+ * sclp_ftp_shutdown() - shutdown of FTP services, when running on LPAR
+ */
+void sclp_ftp_shutdown(void)
+{
+ sclp_unregister(&sclp_ftp_event);
+}
diff --git a/drivers/s390/char/sclp_ftp.h b/drivers/s390/char/sclp_ftp.h
new file mode 100644
index 000000000000..98ba3183e7d9
--- /dev/null
+++ b/drivers/s390/char/sclp_ftp.h
@@ -0,0 +1,21 @@
+/*
+ * SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR
+ *
+ * Notice that all functions exported here are not reentrant.
+ * So usage should be exclusive, ensured by the caller (e.g. using a
+ * mutex).
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __SCLP_FTP_H__
+#define __SCLP_FTP_H__
+
+#include "hmcdrv_ftp.h"
+
+int sclp_ftp_startup(void);
+void sclp_ftp_shutdown(void);
+ssize_t sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize);
+
+#endif /* __SCLP_FTP_H__ */
diff --git a/drivers/s390/char/sclp_rw.c b/drivers/s390/char/sclp_rw.c
index 3b13d58fe87b..35a84af875ee 100644
--- a/drivers/s390/char/sclp_rw.c
+++ b/drivers/s390/char/sclp_rw.c
@@ -33,7 +33,7 @@ static void sclp_rw_pm_event(struct sclp_register *reg,
/* Event type structure for write message and write priority message */
static struct sclp_register sclp_rw_event = {
- .send_mask = EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK,
+ .send_mask = EVTYP_MSG_MASK,
.pm_event_fn = sclp_rw_pm_event,
};
@@ -456,14 +456,9 @@ sclp_emit_buffer(struct sclp_buffer *buffer,
return -EIO;
sccb = buffer->sccb;
- if (sclp_rw_event.sclp_receive_mask & EVTYP_MSG_MASK)
- /* Use normal write message */
- sccb->msg_buf.header.type = EVTYP_MSG;
- else if (sclp_rw_event.sclp_receive_mask & EVTYP_PMSGCMD_MASK)
- /* Use write priority message */
- sccb->msg_buf.header.type = EVTYP_PMSGCMD;
- else
- return -EOPNOTSUPP;
+ /* Use normal write message */
+ sccb->msg_buf.header.type = EVTYP_MSG;
+
buffer->request.command = SCLP_CMDW_WRITE_EVENT_DATA;
buffer->request.status = SCLP_REQ_FILLED;
buffer->request.callback = sclp_writedata_callback;
diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c
index b9a9f721716d..ae67386c03d3 100644
--- a/drivers/s390/char/sclp_vt220.c
+++ b/drivers/s390/char/sclp_vt220.c
@@ -206,10 +206,6 @@ sclp_vt220_callback(struct sclp_req *request, void *data)
static int
__sclp_vt220_emit(struct sclp_vt220_request *request)
{
- if (!(sclp_vt220_register.sclp_receive_mask & EVTYP_VT220MSG_MASK)) {
- request->sclp_req.status = SCLP_REQ_FAILED;
- return -EIO;
- }
request->sclp_req.command = SCLP_CMDW_WRITE_EVENT_DATA;
request->sclp_req.status = SCLP_REQ_FILLED;
request->sclp_req.callback = sclp_vt220_callback;
diff --git a/drivers/s390/char/tape_3590.c b/drivers/s390/char/tape_3590.c
index 327cb19ad0b0..d3d1936057b4 100644
--- a/drivers/s390/char/tape_3590.c
+++ b/drivers/s390/char/tape_3590.c
@@ -1090,7 +1090,7 @@ tape_3590_print_io_sim_msg_f1(struct tape_device *device, struct irb *irb)
"channel path 0x%x on CU",
sense->fmt.f71.md[1]);
else
- snprintf(service, BUFSIZE, "Repair will disable cannel"
+ snprintf(service, BUFSIZE, "Repair will disable channel"
" paths (0x%x-0x%x) on CU",
sense->fmt.f71.md[1], sense->fmt.f71.md[2]);
break;
@@ -1481,7 +1481,7 @@ tape_3590_irq(struct tape_device *device, struct tape_request *request,
}
if (irb->scsw.cmd.dstat & DEV_STAT_CHN_END) {
- DBF_EVENT(2, "cannel end\n");
+ DBF_EVENT(2, "channel end\n");
return TAPE_IO_PENDING;
}
diff --git a/drivers/s390/char/tape_char.c b/drivers/s390/char/tape_char.c
index 6dc60725de92..77f9b9c2f701 100644
--- a/drivers/s390/char/tape_char.c
+++ b/drivers/s390/char/tape_char.c
@@ -402,7 +402,9 @@ __tapechar_ioctl(struct tape_device *device,
memset(&get, 0, sizeof(get));
get.mt_type = MT_ISUNKNOWN;
get.mt_resid = 0 /* device->devstat.rescnt */;
- get.mt_dsreg = device->tape_state;
+ get.mt_dsreg =
+ ((device->char_data.block_size << MT_ST_BLKSIZE_SHIFT)
+ & MT_ST_BLKSIZE_MASK);
/* FIXME: mt_gstat, mt_erreg, mt_fileno */
get.mt_gstat = 0;
get.mt_erreg = 0;
diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c
index a8848db7b09d..9bb48d70957c 100644
--- a/drivers/s390/char/vmlogrdr.c
+++ b/drivers/s390/char/vmlogrdr.c
@@ -338,7 +338,6 @@ static int vmlogrdr_open (struct inode *inode, struct file *filp)
/* set the file options */
filp->private_data = logptr;
- filp->f_op = &vmlogrdr_fops;
/* start recording for this service*/
if (logptr->autorecording) {
diff --git a/drivers/s390/char/zcore.c b/drivers/s390/char/zcore.c
index 1884653e4472..efcf48481c5f 100644
--- a/drivers/s390/char/zcore.c
+++ b/drivers/s390/char/zcore.c
@@ -28,6 +28,7 @@
#include <asm/processor.h>
#include <asm/irqflags.h>
#include <asm/checksum.h>
+#include <asm/switch_to.h>
#include "sclp.h"
#define TRACE(x...) debug_sprintf_event(zcore_dbf, 1, x)
@@ -149,18 +150,21 @@ static int memcpy_hsa_kernel(void *dest, unsigned long src, size_t count)
static int __init init_cpu_info(enum arch_id arch)
{
- struct save_area *sa;
+ struct save_area_ext *sa_ext;
/* get info for boot cpu from lowcore, stored in the HSA */
- sa = dump_save_area_create(0);
- if (!sa)
+ sa_ext = dump_save_area_create(0);
+ if (!sa_ext)
return -ENOMEM;
- if (memcpy_hsa_kernel(sa, sys_info.sa_base, sys_info.sa_size) < 0) {
+ if (memcpy_hsa_kernel(&sa_ext->sa, sys_info.sa_base,
+ sys_info.sa_size) < 0) {
TRACE("could not copy from HSA\n");
- kfree(sa);
+ kfree(sa_ext);
return -EIO;
}
+ if (MACHINE_HAS_VX)
+ save_vx_regs_safe(sa_ext->vx_regs);
return 0;
}
@@ -258,7 +262,7 @@ static int zcore_add_lc(char __user *buf, unsigned long start, size_t count)
unsigned long sa_start, sa_end; /* save area range */
unsigned long prefix;
unsigned long sa_off, len, buf_off;
- struct save_area *save_area = dump_save_areas.areas[i];
+ struct save_area *save_area = &dump_save_areas.areas[i]->sa;
prefix = save_area->pref_reg;
sa_start = prefix + sys_info.sa_base;
@@ -612,7 +616,7 @@ static void __init zcore_header_init(int arch, struct zcore_header *hdr,
hdr->tod = get_tod_clock();
get_cpu_id(&hdr->cpu_id);
for (i = 0; i < dump_save_areas.count; i++) {
- prefix = dump_save_areas.areas[i]->pref_reg;
+ prefix = dump_save_areas.areas[i]->sa.pref_reg;
hdr->real_cpu_cnt++;
if (!prefix)
continue;
diff --git a/drivers/s390/cio/airq.c b/drivers/s390/cio/airq.c
index 00bfbee0af9e..56eb4ee4deba 100644
--- a/drivers/s390/cio/airq.c
+++ b/drivers/s390/cio/airq.c
@@ -87,7 +87,7 @@ static irqreturn_t do_airq_interrupt(int irq, void *dummy)
struct airq_struct *airq;
struct hlist_head *head;
- __this_cpu_write(s390_idle.nohz_delay, 1);
+ set_cpu_flag(CIF_NOHZ_DELAY);
tpi_info = (struct tpi_info *) &get_irq_regs()->int_code;
head = &airq_lists[tpi_info->isc];
rcu_read_lock();
diff --git a/drivers/s390/cio/ccwreq.c b/drivers/s390/cio/ccwreq.c
index 07676c22d514..79f59915f71b 100644
--- a/drivers/s390/cio/ccwreq.c
+++ b/drivers/s390/cio/ccwreq.c
@@ -252,7 +252,7 @@ static void ccwreq_log_status(struct ccw_device *cdev, enum io_status status)
*/
void ccw_request_handler(struct ccw_device *cdev)
{
- struct irb *irb = &__get_cpu_var(cio_irb);
+ struct irb *irb = this_cpu_ptr(&cio_irb);
struct ccw_request *req = &cdev->private->req;
enum io_status status;
int rc = -EOPNOTSUPP;
diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c
index d497aa05a72f..c692dfebd0ba 100644
--- a/drivers/s390/cio/chp.c
+++ b/drivers/s390/cio/chp.c
@@ -257,11 +257,11 @@ static ssize_t chp_status_write(struct device *dev,
if (!num_args)
return count;
- if (!strnicmp(cmd, "on", 2) || !strcmp(cmd, "1")) {
+ if (!strncasecmp(cmd, "on", 2) || !strcmp(cmd, "1")) {
mutex_lock(&cp->lock);
error = s390_vary_chpid(cp->chpid, 1);
mutex_unlock(&cp->lock);
- } else if (!strnicmp(cmd, "off", 3) || !strcmp(cmd, "0")) {
+ } else if (!strncasecmp(cmd, "off", 3) || !strcmp(cmd, "0")) {
mutex_lock(&cp->lock);
error = s390_vary_chpid(cp->chpid, 0);
mutex_unlock(&cp->lock);
diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c
index 3d22d2a4ce14..213159dec89e 100644
--- a/drivers/s390/cio/chsc_sch.c
+++ b/drivers/s390/cio/chsc_sch.c
@@ -58,7 +58,7 @@ static void chsc_subchannel_irq(struct subchannel *sch)
{
struct chsc_private *private = dev_get_drvdata(&sch->dev);
struct chsc_request *request = private->request;
- struct irb *irb = &__get_cpu_var(cio_irb);
+ struct irb *irb = this_cpu_ptr(&cio_irb);
CHSC_LOG(4, "irb");
CHSC_LOG_HEX(4, irb, sizeof(*irb));
diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c
index 2905d8b0ec95..10eb738fc81a 100644
--- a/drivers/s390/cio/cio.c
+++ b/drivers/s390/cio/cio.c
@@ -561,9 +561,9 @@ static irqreturn_t do_cio_interrupt(int irq, void *dummy)
struct subchannel *sch;
struct irb *irb;
- __this_cpu_write(s390_idle.nohz_delay, 1);
+ set_cpu_flag(CIF_NOHZ_DELAY);
tpi_info = (struct tpi_info *) &get_irq_regs()->int_code;
- irb = &__get_cpu_var(cio_irb);
+ irb = this_cpu_ptr(&cio_irb);
sch = (struct subchannel *)(unsigned long) tpi_info->intparm;
if (!sch) {
/* Clear pending interrupt condition. */
@@ -613,7 +613,7 @@ void cio_tsch(struct subchannel *sch)
struct irb *irb;
int irq_context;
- irb = &__get_cpu_var(cio_irb);
+ irb = this_cpu_ptr(&cio_irb);
/* Store interrupt response block to lowcore. */
if (tsch(sch->schid, irb) != 0)
/* Not status pending or not operational. */
@@ -751,7 +751,7 @@ __clear_io_subchannel_easy(struct subchannel_id schid)
struct tpi_info ti;
if (tpi(&ti)) {
- tsch(ti.schid, &__get_cpu_var(cio_irb));
+ tsch(ti.schid, this_cpu_ptr(&cio_irb));
if (schid_equal(&ti.schid, &schid))
return 0;
}
diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c
index 0bc902b3cd84..83da53c8e54c 100644
--- a/drivers/s390/cio/device_fsm.c
+++ b/drivers/s390/cio/device_fsm.c
@@ -739,7 +739,7 @@ ccw_device_irq(struct ccw_device *cdev, enum dev_event dev_event)
struct irb *irb;
int is_cmd;
- irb = &__get_cpu_var(cio_irb);
+ irb = this_cpu_ptr(&cio_irb);
is_cmd = !scsw_is_tm(&irb->scsw);
/* Check for unsolicited interrupt. */
if (!scsw_is_solicited(&irb->scsw)) {
@@ -805,7 +805,7 @@ ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event)
{
struct irb *irb;
- irb = &__get_cpu_var(cio_irb);
+ irb = this_cpu_ptr(&cio_irb);
/* Check for unsolicited interrupt. */
if (scsw_stctl(&irb->scsw) ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
diff --git a/drivers/s390/cio/eadm_sch.c b/drivers/s390/cio/eadm_sch.c
index c4f7bf3e24c2..bee8c11cd086 100644
--- a/drivers/s390/cio/eadm_sch.c
+++ b/drivers/s390/cio/eadm_sch.c
@@ -31,7 +31,7 @@
MODULE_DESCRIPTION("driver for s390 eadm subchannels");
MODULE_LICENSE("GPL");
-#define EADM_TIMEOUT (5 * HZ)
+#define EADM_TIMEOUT (7 * HZ)
static DEFINE_SPINLOCK(list_lock);
static LIST_HEAD(eadm_list);
@@ -134,7 +134,7 @@ static void eadm_subchannel_irq(struct subchannel *sch)
{
struct eadm_private *private = get_eadm_private(sch);
struct eadm_scsw *scsw = &sch->schib.scsw.eadm;
- struct irb *irb = &__get_cpu_var(cio_irb);
+ struct irb *irb = this_cpu_ptr(&cio_irb);
int error = 0;
EADM_LOG(6, "irq");
diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c
index 4038437ff033..91e97ec01418 100644
--- a/drivers/s390/crypto/ap_bus.c
+++ b/drivers/s390/crypto/ap_bus.c
@@ -44,6 +44,7 @@
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <asm/facility.h>
+#include <linux/crypto.h>
#include "ap_bus.h"
@@ -71,7 +72,7 @@ MODULE_AUTHOR("IBM Corporation");
MODULE_DESCRIPTION("Adjunct Processor Bus driver, " \
"Copyright IBM Corp. 2006, 2012");
MODULE_LICENSE("GPL");
-MODULE_ALIAS("z90crypt");
+MODULE_ALIAS_CRYPTO("z90crypt");
/*
* Module parameter
@@ -664,6 +665,17 @@ static ssize_t ap_hwtype_show(struct device *dev,
}
static DEVICE_ATTR(hwtype, 0444, ap_hwtype_show, NULL);
+
+static ssize_t ap_raw_hwtype_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", ap_dev->raw_hwtype);
+}
+
+static DEVICE_ATTR(raw_hwtype, 0444, ap_raw_hwtype_show, NULL);
+
static ssize_t ap_depth_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -734,6 +746,7 @@ static DEVICE_ATTR(ap_functions, 0444, ap_functions_show, NULL);
static struct attribute *ap_dev_attrs[] = {
&dev_attr_hwtype.attr,
+ &dev_attr_raw_hwtype.attr,
&dev_attr_depth.attr,
&dev_attr_request_count.attr,
&dev_attr_requestq_count.attr,
@@ -1188,6 +1201,10 @@ static int ap_select_domain(void)
ap_qid_t qid;
int rc, i, j;
+ /* IF APXA isn't installed, only 16 domains could be defined */
+ if (!ap_configuration->ap_extended && (ap_domain_index > 15))
+ return -EINVAL;
+
/*
* We want to use a single domain. Either the one specified with
* the "domain=" parameter or the domain with the maximum number
@@ -1413,9 +1430,13 @@ static void ap_scan_bus(struct work_struct *unused)
continue;
}
break;
+ case 11:
+ ap_dev->device_type = 10;
+ break;
default:
ap_dev->device_type = device_type;
}
+ ap_dev->raw_hwtype = device_type;
rc = ap_query_functions(qid, &device_functions);
if (!rc)
@@ -1900,9 +1921,15 @@ static void ap_reset_all(void)
{
int i, j;
- for (i = 0; i < AP_DOMAINS; i++)
- for (j = 0; j < AP_DEVICES; j++)
+ for (i = 0; i < AP_DOMAINS; i++) {
+ if (!ap_test_config_domain(i))
+ continue;
+ for (j = 0; j < AP_DEVICES; j++) {
+ if (!ap_test_config_card_id(j))
+ continue;
ap_reset_queue(AP_MKQID(j, i));
+ }
+ }
}
static struct reset_call ap_reset_call = {
diff --git a/drivers/s390/crypto/ap_bus.h b/drivers/s390/crypto/ap_bus.h
index 6405ae24a7a6..055a0f956d17 100644
--- a/drivers/s390/crypto/ap_bus.h
+++ b/drivers/s390/crypto/ap_bus.h
@@ -31,7 +31,7 @@
#include <linux/types.h>
#define AP_DEVICES 64 /* Number of AP devices. */
-#define AP_DOMAINS 16 /* Number of AP domains. */
+#define AP_DOMAINS 256 /* Number of AP domains. */
#define AP_MAX_RESET 90 /* Maximum number of resets. */
#define AP_RESET_TIMEOUT (HZ*0.7) /* Time in ticks for reset timeouts. */
#define AP_CONFIG_TIME 30 /* Time in seconds between AP bus rescans. */
@@ -45,9 +45,9 @@ extern int ap_domain_index;
*/
typedef unsigned int ap_qid_t;
-#define AP_MKQID(_device,_queue) (((_device) & 63) << 8 | ((_queue) & 15))
+#define AP_MKQID(_device, _queue) (((_device) & 63) << 8 | ((_queue) & 255))
#define AP_QID_DEVICE(_qid) (((_qid) >> 8) & 63)
-#define AP_QID_QUEUE(_qid) ((_qid) & 15)
+#define AP_QID_QUEUE(_qid) ((_qid) & 255)
/**
* structy ap_queue_status - Holds the AP queue status.
@@ -161,6 +161,7 @@ struct ap_device {
ap_qid_t qid; /* AP queue id. */
int queue_depth; /* AP queue depth.*/
int device_type; /* AP device type. */
+ int raw_hwtype; /* AP raw hardware type. */
unsigned int functions; /* AP device function bitfield. */
int unregistered; /* marks AP device as unregistered */
struct timer_list timeout; /* Timer for request timeouts. */
diff --git a/drivers/s390/crypto/zcrypt_api.c b/drivers/s390/crypto/zcrypt_api.c
index 0e18c5dcd91f..08f1830cbfc4 100644
--- a/drivers/s390/crypto/zcrypt_api.c
+++ b/drivers/s390/crypto/zcrypt_api.c
@@ -343,10 +343,11 @@ struct zcrypt_ops *__ops_lookup(unsigned char *name, int variant)
break;
}
}
+ if (!found || !try_module_get(zops->owner))
+ zops = NULL;
+
spin_unlock_bh(&zcrypt_ops_list_lock);
- if (!found)
- return NULL;
return zops;
}
@@ -359,8 +360,6 @@ struct zcrypt_ops *zcrypt_msgtype_request(unsigned char *name, int variant)
request_module("%s", name);
zops = __ops_lookup(name, variant);
}
- if ((!zops) || (!try_module_get(zops->owner)))
- return NULL;
return zops;
}
EXPORT_SYMBOL(zcrypt_msgtype_request);
diff --git a/drivers/s390/kvm/kvm_virtio.c b/drivers/s390/kvm/kvm_virtio.c
index a1349653c6d9..dd65c8b4c7fe 100644
--- a/drivers/s390/kvm/kvm_virtio.c
+++ b/drivers/s390/kvm/kvm_virtio.c
@@ -80,7 +80,7 @@ static unsigned desc_size(const struct kvm_device_desc *desc)
}
/* This gets the device's feature bits. */
-static u32 kvm_get_features(struct virtio_device *vdev)
+static u64 kvm_get_features(struct virtio_device *vdev)
{
unsigned int i;
u32 features = 0;
@@ -93,7 +93,7 @@ static u32 kvm_get_features(struct virtio_device *vdev)
return features;
}
-static void kvm_finalize_features(struct virtio_device *vdev)
+static int kvm_finalize_features(struct virtio_device *vdev)
{
unsigned int i, bits;
struct kvm_device_desc *desc = to_kvmdev(vdev)->desc;
@@ -103,12 +103,17 @@ static void kvm_finalize_features(struct virtio_device *vdev)
/* Give virtio_ring a chance to accept features. */
vring_transport_features(vdev);
+ /* Make sure we don't have any features > 32 bits! */
+ BUG_ON((u32)vdev->features != vdev->features);
+
memset(out_features, 0, desc->feature_len);
bits = min_t(unsigned, desc->feature_len, sizeof(vdev->features)) * 8;
for (i = 0; i < bits; i++) {
- if (test_bit(i, vdev->features))
+ if (__virtio_test_bit(vdev, i))
out_features[i / 8] |= (1 << (i % 8));
}
+
+ return 0;
}
/*
@@ -406,15 +411,8 @@ static void kvm_extint_handler(struct ext_code ext_code,
switch (param) {
case VIRTIO_PARAM_CONFIG_CHANGED:
- {
- struct virtio_driver *drv;
- drv = container_of(vq->vdev->dev.driver,
- struct virtio_driver, driver);
- if (drv->config_changed)
- drv->config_changed(vq->vdev);
-
+ virtio_config_changed(vq->vdev);
break;
- }
case VIRTIO_PARAM_DEV_ADD:
schedule_work(&hotplug_work);
break;
diff --git a/drivers/s390/kvm/virtio_ccw.c b/drivers/s390/kvm/virtio_ccw.c
index d2c0b442bce5..71d7802aa8b4 100644
--- a/drivers/s390/kvm/virtio_ccw.c
+++ b/drivers/s390/kvm/virtio_ccw.c
@@ -55,6 +55,7 @@ struct virtio_ccw_device {
struct ccw_device *cdev;
__u32 curr_io;
int err;
+ unsigned int revision; /* Transport revision */
wait_queue_head_t wait_q;
spinlock_t lock;
struct list_head virtqueues;
@@ -67,13 +68,22 @@ struct virtio_ccw_device {
void *airq_info;
};
-struct vq_info_block {
+struct vq_info_block_legacy {
__u64 queue;
__u32 align;
__u16 index;
__u16 num;
} __packed;
+struct vq_info_block {
+ __u64 desc;
+ __u32 res0;
+ __u16 index;
+ __u16 num;
+ __u64 avail;
+ __u64 used;
+} __packed;
+
struct virtio_feature_desc {
__u32 features;
__u8 index;
@@ -86,11 +96,23 @@ struct virtio_thinint_area {
u8 isc;
} __packed;
+struct virtio_rev_info {
+ __u16 revision;
+ __u16 length;
+ __u8 data[];
+};
+
+/* the highest virtio-ccw revision we support */
+#define VIRTIO_CCW_REV_MAX 1
+
struct virtio_ccw_vq_info {
struct virtqueue *vq;
int num;
void *queue;
- struct vq_info_block *info_block;
+ union {
+ struct vq_info_block s;
+ struct vq_info_block_legacy l;
+ } *info_block;
int bit_nr;
struct list_head node;
long cookie;
@@ -122,6 +144,7 @@ static struct airq_info *airq_areas[MAX_AIRQ_AREAS];
#define CCW_CMD_WRITE_STATUS 0x31
#define CCW_CMD_READ_VQ_CONF 0x32
#define CCW_CMD_SET_IND_ADAPTER 0x73
+#define CCW_CMD_SET_VIRTIO_REV 0x83
#define VIRTIO_CCW_DOING_SET_VQ 0x00010000
#define VIRTIO_CCW_DOING_RESET 0x00040000
@@ -134,6 +157,7 @@ static struct airq_info *airq_areas[MAX_AIRQ_AREAS];
#define VIRTIO_CCW_DOING_READ_VQ_CONF 0x02000000
#define VIRTIO_CCW_DOING_SET_CONF_IND 0x04000000
#define VIRTIO_CCW_DOING_SET_IND_ADAPTER 0x08000000
+#define VIRTIO_CCW_DOING_SET_VIRTIO_REV 0x10000000
#define VIRTIO_CCW_INTPARM_MASK 0xffff0000
static struct virtio_ccw_device *to_vc_device(struct virtio_device *vdev)
@@ -399,13 +423,22 @@ static void virtio_ccw_del_vq(struct virtqueue *vq, struct ccw1 *ccw)
spin_unlock_irqrestore(&vcdev->lock, flags);
/* Release from host. */
- info->info_block->queue = 0;
- info->info_block->align = 0;
- info->info_block->index = index;
- info->info_block->num = 0;
+ if (vcdev->revision == 0) {
+ info->info_block->l.queue = 0;
+ info->info_block->l.align = 0;
+ info->info_block->l.index = index;
+ info->info_block->l.num = 0;
+ ccw->count = sizeof(info->info_block->l);
+ } else {
+ info->info_block->s.desc = 0;
+ info->info_block->s.index = index;
+ info->info_block->s.num = 0;
+ info->info_block->s.avail = 0;
+ info->info_block->s.used = 0;
+ ccw->count = sizeof(info->info_block->s);
+ }
ccw->cmd_code = CCW_CMD_SET_VQ;
ccw->flags = 0;
- ccw->count = sizeof(*info->info_block);
ccw->cda = (__u32)(unsigned long)(info->info_block);
ret = ccw_io_helper(vcdev, ccw,
VIRTIO_CCW_DOING_SET_VQ | index);
@@ -488,13 +521,22 @@ static struct virtqueue *virtio_ccw_setup_vq(struct virtio_device *vdev,
}
/* Register it with the host. */
- info->info_block->queue = (__u64)info->queue;
- info->info_block->align = KVM_VIRTIO_CCW_RING_ALIGN;
- info->info_block->index = i;
- info->info_block->num = info->num;
+ if (vcdev->revision == 0) {
+ info->info_block->l.queue = (__u64)info->queue;
+ info->info_block->l.align = KVM_VIRTIO_CCW_RING_ALIGN;
+ info->info_block->l.index = i;
+ info->info_block->l.num = info->num;
+ ccw->count = sizeof(info->info_block->l);
+ } else {
+ info->info_block->s.desc = (__u64)info->queue;
+ info->info_block->s.index = i;
+ info->info_block->s.num = info->num;
+ info->info_block->s.avail = (__u64)virtqueue_get_avail(vq);
+ info->info_block->s.used = (__u64)virtqueue_get_used(vq);
+ ccw->count = sizeof(info->info_block->s);
+ }
ccw->cmd_code = CCW_CMD_SET_VQ;
ccw->flags = 0;
- ccw->count = sizeof(*info->info_block);
ccw->cda = (__u32)(unsigned long)(info->info_block);
err = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_SET_VQ | i);
if (err) {
@@ -660,11 +702,12 @@ static void virtio_ccw_reset(struct virtio_device *vdev)
kfree(ccw);
}
-static u32 virtio_ccw_get_features(struct virtio_device *vdev)
+static u64 virtio_ccw_get_features(struct virtio_device *vdev)
{
struct virtio_ccw_device *vcdev = to_vc_device(vdev);
struct virtio_feature_desc *features;
- int ret, rc;
+ int ret;
+ u64 rc;
struct ccw1 *ccw;
ccw = kzalloc(sizeof(*ccw), GFP_DMA | GFP_KERNEL);
@@ -677,7 +720,6 @@ static u32 virtio_ccw_get_features(struct virtio_device *vdev)
goto out_free;
}
/* Read the feature bits from the host. */
- /* TODO: Features > 32 bits */
features->index = 0;
ccw->cmd_code = CCW_CMD_READ_FEAT;
ccw->flags = 0;
@@ -691,46 +733,79 @@ static u32 virtio_ccw_get_features(struct virtio_device *vdev)
rc = le32_to_cpu(features->features);
+ if (vcdev->revision == 0)
+ goto out_free;
+
+ /* Read second half of the feature bits from the host. */
+ features->index = 1;
+ ccw->cmd_code = CCW_CMD_READ_FEAT;
+ ccw->flags = 0;
+ ccw->count = sizeof(*features);
+ ccw->cda = (__u32)(unsigned long)features;
+ ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_READ_FEAT);
+ if (ret == 0)
+ rc |= (u64)le32_to_cpu(features->features) << 32;
+
out_free:
kfree(features);
kfree(ccw);
return rc;
}
-static void virtio_ccw_finalize_features(struct virtio_device *vdev)
+static int virtio_ccw_finalize_features(struct virtio_device *vdev)
{
struct virtio_ccw_device *vcdev = to_vc_device(vdev);
struct virtio_feature_desc *features;
- int i;
struct ccw1 *ccw;
+ int ret;
+
+ if (vcdev->revision >= 1 &&
+ !__virtio_test_bit(vdev, VIRTIO_F_VERSION_1)) {
+ dev_err(&vdev->dev, "virtio: device uses revision 1 "
+ "but does not have VIRTIO_F_VERSION_1\n");
+ return -EINVAL;
+ }
ccw = kzalloc(sizeof(*ccw), GFP_DMA | GFP_KERNEL);
if (!ccw)
- return;
+ return -ENOMEM;
features = kzalloc(sizeof(*features), GFP_DMA | GFP_KERNEL);
- if (!features)
+ if (!features) {
+ ret = -ENOMEM;
goto out_free;
-
+ }
/* Give virtio_ring a chance to accept features. */
vring_transport_features(vdev);
- for (i = 0; i < sizeof(*vdev->features) / sizeof(features->features);
- i++) {
- int highbits = i % 2 ? 32 : 0;
- features->index = i;
- features->features = cpu_to_le32(vdev->features[i / 2]
- >> highbits);
- /* Write the feature bits to the host. */
- ccw->cmd_code = CCW_CMD_WRITE_FEAT;
- ccw->flags = 0;
- ccw->count = sizeof(*features);
- ccw->cda = (__u32)(unsigned long)features;
- ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_FEAT);
- }
+ features->index = 0;
+ features->features = cpu_to_le32((u32)vdev->features);
+ /* Write the first half of the feature bits to the host. */
+ ccw->cmd_code = CCW_CMD_WRITE_FEAT;
+ ccw->flags = 0;
+ ccw->count = sizeof(*features);
+ ccw->cda = (__u32)(unsigned long)features;
+ ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_FEAT);
+ if (ret)
+ goto out_free;
+
+ if (vcdev->revision == 0)
+ goto out_free;
+
+ features->index = 1;
+ features->features = cpu_to_le32(vdev->features >> 32);
+ /* Write the second half of the feature bits to the host. */
+ ccw->cmd_code = CCW_CMD_WRITE_FEAT;
+ ccw->flags = 0;
+ ccw->count = sizeof(*features);
+ ccw->cda = (__u32)(unsigned long)features;
+ ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_FEAT);
+
out_free:
kfree(features);
kfree(ccw);
+
+ return ret;
}
static void virtio_ccw_get_config(struct virtio_device *vdev,
@@ -806,7 +881,9 @@ static u8 virtio_ccw_get_status(struct virtio_device *vdev)
static void virtio_ccw_set_status(struct virtio_device *vdev, u8 status)
{
struct virtio_ccw_device *vcdev = to_vc_device(vdev);
+ u8 old_status = *vcdev->status;
struct ccw1 *ccw;
+ int ret;
ccw = kzalloc(sizeof(*ccw), GFP_DMA | GFP_KERNEL);
if (!ccw)
@@ -818,7 +895,10 @@ static void virtio_ccw_set_status(struct virtio_device *vdev, u8 status)
ccw->flags = 0;
ccw->count = sizeof(status);
ccw->cda = (__u32)(unsigned long)vcdev->status;
- ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_STATUS);
+ ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_STATUS);
+ /* Write failed? We assume status is unchanged. */
+ if (ret)
+ *vcdev->status = old_status;
kfree(ccw);
}
@@ -888,7 +968,6 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev,
struct virtio_ccw_device *vcdev = dev_get_drvdata(&cdev->dev);
int i;
struct virtqueue *vq;
- struct virtio_driver *drv;
if (!vcdev)
return;
@@ -920,6 +999,7 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev,
case VIRTIO_CCW_DOING_RESET:
case VIRTIO_CCW_DOING_READ_VQ_CONF:
case VIRTIO_CCW_DOING_SET_IND_ADAPTER:
+ case VIRTIO_CCW_DOING_SET_VIRTIO_REV:
vcdev->curr_io &= ~activity;
wake_up(&vcdev->wait_q);
break;
@@ -940,11 +1020,7 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev,
vring_interrupt(0, vq);
}
if (test_bit(0, &vcdev->indicators2)) {
- drv = container_of(vcdev->vdev.dev.driver,
- struct virtio_driver, driver);
-
- if (drv && drv->config_changed)
- drv->config_changed(&vcdev->vdev);
+ virtio_config_changed(&vcdev->vdev);
clear_bit(0, &vcdev->indicators2);
}
}
@@ -1039,6 +1115,51 @@ static int virtio_ccw_offline(struct ccw_device *cdev)
return 0;
}
+static int virtio_ccw_set_transport_rev(struct virtio_ccw_device *vcdev)
+{
+ struct virtio_rev_info *rev;
+ struct ccw1 *ccw;
+ int ret;
+
+ ccw = kzalloc(sizeof(*ccw), GFP_DMA | GFP_KERNEL);
+ if (!ccw)
+ return -ENOMEM;
+ rev = kzalloc(sizeof(*rev), GFP_DMA | GFP_KERNEL);
+ if (!rev) {
+ kfree(ccw);
+ return -ENOMEM;
+ }
+
+ /* Set transport revision */
+ ccw->cmd_code = CCW_CMD_SET_VIRTIO_REV;
+ ccw->flags = 0;
+ ccw->count = sizeof(*rev);
+ ccw->cda = (__u32)(unsigned long)rev;
+
+ vcdev->revision = VIRTIO_CCW_REV_MAX;
+ do {
+ rev->revision = vcdev->revision;
+ /* none of our supported revisions carry payload */
+ rev->length = 0;
+ ret = ccw_io_helper(vcdev, ccw,
+ VIRTIO_CCW_DOING_SET_VIRTIO_REV);
+ if (ret == -EOPNOTSUPP) {
+ if (vcdev->revision == 0)
+ /*
+ * The host device does not support setting
+ * the revision: let's operate it in legacy
+ * mode.
+ */
+ ret = 0;
+ else
+ vcdev->revision--;
+ }
+ } while (ret == -EOPNOTSUPP);
+
+ kfree(ccw);
+ kfree(rev);
+ return ret;
+}
static int virtio_ccw_online(struct ccw_device *cdev)
{
@@ -1079,6 +1200,15 @@ static int virtio_ccw_online(struct ccw_device *cdev)
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
vcdev->vdev.id.vendor = cdev->id.cu_type;
vcdev->vdev.id.device = cdev->id.cu_model;
+
+ if (virtio_device_is_legacy_only(vcdev->vdev.id)) {
+ vcdev->revision = 0;
+ } else {
+ ret = virtio_ccw_set_transport_rev(vcdev);
+ if (ret)
+ goto out_free;
+ }
+
ret = register_virtio_device(&vcdev->vdev);
if (ret) {
dev_warn(&cdev->dev, "Failed to register virtio device: %d\n",
diff --git a/drivers/s390/net/Kconfig b/drivers/s390/net/Kconfig
index 8b3f55991805..f1b5111bbaba 100644
--- a/drivers/s390/net/Kconfig
+++ b/drivers/s390/net/Kconfig
@@ -71,7 +71,7 @@ config CLAW
config QETH
def_tristate y
prompt "Gigabit Ethernet device support"
- depends on CCW && NETDEVICES && IP_MULTICAST && QDIO
+ depends on CCW && NETDEVICES && IP_MULTICAST && QDIO && ETHERNET
help
This driver supports the IBM System z OSA Express adapters
in QDIO mode (all media types), HiperSockets interfaces and z/VM
diff --git a/drivers/s390/net/claw.c b/drivers/s390/net/claw.c
index fbc6701bef30..213e54ee8a66 100644
--- a/drivers/s390/net/claw.c
+++ b/drivers/s390/net/claw.c
@@ -481,7 +481,6 @@ claw_open(struct net_device *dev)
spin_unlock_irqrestore(
get_ccwdev_lock(privptr->channel[i].cdev), saveflags);
schedule();
- set_current_state(TASK_RUNNING);
remove_wait_queue(&privptr->channel[i].wait, &wait);
if(rc != 0)
ccw_check_return_code(privptr->channel[i].cdev, rc);
@@ -828,7 +827,6 @@ claw_release(struct net_device *dev)
spin_unlock_irqrestore(
get_ccwdev_lock(privptr->channel[i].cdev), saveflags);
schedule();
- set_current_state(TASK_RUNNING);
remove_wait_queue(&privptr->channel[i].wait, &wait);
if (rc != 0) {
ccw_check_return_code(privptr->channel[i].cdev, rc);
diff --git a/drivers/s390/net/ctcm_sysfs.c b/drivers/s390/net/ctcm_sysfs.c
index 6bcfbbb20f04..47773c4d235a 100644
--- a/drivers/s390/net/ctcm_sysfs.c
+++ b/drivers/s390/net/ctcm_sysfs.c
@@ -44,8 +44,8 @@ static ssize_t ctcm_buffer_write(struct device *dev,
return -ENODEV;
}
- rc = sscanf(buf, "%u", &bs1);
- if (rc != 1)
+ rc = kstrtouint(buf, 0, &bs1);
+ if (rc)
goto einval;
if (bs1 > CTCM_BUFSIZE_LIMIT)
goto einval;
@@ -151,8 +151,8 @@ static ssize_t ctcm_proto_store(struct device *dev,
if (!priv)
return -ENODEV;
- rc = sscanf(buf, "%d", &value);
- if ((rc != 1) ||
+ rc = kstrtoint(buf, 0, &value);
+ if (rc ||
!((value == CTCM_PROTO_S390) ||
(value == CTCM_PROTO_LINUX) ||
(value == CTCM_PROTO_MPC) ||
diff --git a/drivers/s390/net/lcs.c b/drivers/s390/net/lcs.c
index 0a7d87c372b8..92190aa20b9f 100644
--- a/drivers/s390/net/lcs.c
+++ b/drivers/s390/net/lcs.c
@@ -1943,15 +1943,16 @@ static ssize_t
lcs_portno_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct lcs_card *card;
- int value, rc;
+ int rc;
+ s16 value;
card = dev_get_drvdata(dev);
if (!card)
return 0;
- rc = sscanf(buf, "%d", &value);
- if (rc != 1)
+ rc = kstrtos16(buf, 0, &value);
+ if (rc)
return -EINVAL;
/* TODO: sanity checks */
card->portno = value;
@@ -2007,8 +2008,8 @@ lcs_timeout_store (struct device *dev, struct device_attribute *attr, const char
if (!card)
return 0;
- rc = sscanf(buf, "%u", &value);
- if (rc != 1)
+ rc = kstrtouint(buf, 0, &value);
+ if (rc)
return -EINVAL;
/* TODO: sanity checks */
card->lancmd_timeout = value;
diff --git a/drivers/s390/net/qeth_core.h b/drivers/s390/net/qeth_core.h
index e7646ce3d659..7a8bb9f78e76 100644
--- a/drivers/s390/net/qeth_core.h
+++ b/drivers/s390/net/qeth_core.h
@@ -380,11 +380,6 @@ enum qeth_header_ids {
#define QETH_HDR_EXT_CSUM_TRANSP_REQ 0x20
#define QETH_HDR_EXT_UDP 0x40 /*bit off for TCP*/
-static inline int qeth_is_last_sbale(struct qdio_buffer_element *sbale)
-{
- return (sbale->eflags & SBAL_EFLAGS_LAST_ENTRY);
-}
-
enum qeth_qdio_buffer_states {
/*
* inbound: read out by driver; owned by hardware in order to be filled
@@ -843,13 +838,6 @@ struct qeth_trap_id {
/*some helper functions*/
#define QETH_CARD_IFNAME(card) (((card)->dev)? (card)->dev->name : "")
-static inline struct qeth_card *CARD_FROM_CDEV(struct ccw_device *cdev)
-{
- struct qeth_card *card = dev_get_drvdata(&((struct ccwgroup_device *)
- dev_get_drvdata(&cdev->dev))->dev);
- return card;
-}
-
static inline int qeth_get_micros(void)
{
return (int) (get_tod_clock() >> 12);
@@ -894,7 +882,6 @@ const char *qeth_get_cardname_short(struct qeth_card *);
int qeth_realloc_buffer_pool(struct qeth_card *, int);
int qeth_core_load_discipline(struct qeth_card *, enum qeth_discipline_id);
void qeth_core_free_discipline(struct qeth_card *);
-void qeth_buffer_reclaim_work(struct work_struct *);
/* exports for qeth discipline device drivers */
extern struct qeth_card_list_struct qeth_core_card_list;
@@ -913,7 +900,6 @@ int qeth_core_hardsetup_card(struct qeth_card *);
void qeth_print_status_message(struct qeth_card *);
int qeth_init_qdio_queues(struct qeth_card *);
int qeth_send_startlan(struct qeth_card *);
-int qeth_send_stoplan(struct qeth_card *);
int qeth_send_ipa_cmd(struct qeth_card *, struct qeth_cmd_buffer *,
int (*reply_cb)
(struct qeth_card *, struct qeth_reply *, unsigned long),
@@ -954,8 +940,6 @@ int qeth_snmp_command(struct qeth_card *, char __user *);
int qeth_query_oat_command(struct qeth_card *, char __user *);
int qeth_query_switch_attributes(struct qeth_card *card,
struct qeth_switch_info *sw_info);
-int qeth_query_card_info(struct qeth_card *card,
- struct carrier_info *carrier_info);
int qeth_send_control_data(struct qeth_card *, int, struct qeth_cmd_buffer *,
int (*reply_cb)(struct qeth_card *, struct qeth_reply*, unsigned long),
void *reply_param);
diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c
index fd22c811cbe1..f407e3763432 100644
--- a/drivers/s390/net/qeth_core_main.c
+++ b/drivers/s390/net/qeth_core_main.c
@@ -718,6 +718,13 @@ static int qeth_check_idx_response(struct qeth_card *card,
return 0;
}
+static struct qeth_card *CARD_FROM_CDEV(struct ccw_device *cdev)
+{
+ struct qeth_card *card = dev_get_drvdata(&((struct ccwgroup_device *)
+ dev_get_drvdata(&cdev->dev))->dev);
+ return card;
+}
+
static void qeth_setup_ccw(struct qeth_channel *channel, unsigned char *iob,
__u32 len)
{
@@ -1431,6 +1438,7 @@ static void qeth_start_kernel_thread(struct work_struct *work)
}
}
+static void qeth_buffer_reclaim_work(struct work_struct *);
static int qeth_setup_card(struct qeth_card *card)
{
@@ -3232,7 +3240,7 @@ int qeth_check_qdio_errors(struct qeth_card *card, struct qdio_buffer *buf,
}
EXPORT_SYMBOL_GPL(qeth_check_qdio_errors);
-void qeth_buffer_reclaim_work(struct work_struct *work)
+static void qeth_buffer_reclaim_work(struct work_struct *work)
{
struct qeth_card *card = container_of(work, struct qeth_card,
buffer_reclaim_work.work);
@@ -4126,7 +4134,7 @@ static int qeth_setadp_promisc_mode_cb(struct qeth_card *card,
qeth_default_setadapterparms_cb(card, reply, (unsigned long)cmd);
if (cmd->hdr.return_code) {
- QETH_CARD_TEXT_(card, 4, "prmrc%2.2x", cmd->hdr.return_code);
+ QETH_CARD_TEXT_(card, 4, "prmrc%x", cmd->hdr.return_code);
setparms->data.mode = SET_PROMISC_MODE_OFF;
}
card->info.promisc_mode = setparms->data.mode;
@@ -4493,13 +4501,13 @@ static int qeth_snmp_command_cb(struct qeth_card *card,
snmp = &cmd->data.setadapterparms.data.snmp;
if (cmd->hdr.return_code) {
- QETH_CARD_TEXT_(card, 4, "scer1%i", cmd->hdr.return_code);
+ QETH_CARD_TEXT_(card, 4, "scer1%x", cmd->hdr.return_code);
return 0;
}
if (cmd->data.setadapterparms.hdr.return_code) {
cmd->hdr.return_code =
cmd->data.setadapterparms.hdr.return_code;
- QETH_CARD_TEXT_(card, 4, "scer2%i", cmd->hdr.return_code);
+ QETH_CARD_TEXT_(card, 4, "scer2%x", cmd->hdr.return_code);
return 0;
}
data_len = *((__u16 *)QETH_IPA_PDU_LEN_PDU1(data));
@@ -4717,7 +4725,7 @@ static int qeth_query_card_info_cb(struct qeth_card *card,
return 0;
}
-int qeth_query_card_info(struct qeth_card *card,
+static int qeth_query_card_info(struct qeth_card *card,
struct carrier_info *carrier_info)
{
struct qeth_cmd_buffer *iob;
@@ -4730,7 +4738,6 @@ int qeth_query_card_info(struct qeth_card *card,
return qeth_send_ipa_cmd(card, iob, qeth_query_card_info_cb,
(void *)carrier_info);
}
-EXPORT_SYMBOL_GPL(qeth_query_card_info);
static inline int qeth_get_qdio_q_format(struct qeth_card *card)
{
@@ -5113,6 +5120,11 @@ static inline int qeth_create_skb_frag(struct qeth_qdio_buffer *qethbuffer,
return 0;
}
+static inline int qeth_is_last_sbale(struct qdio_buffer_element *sbale)
+{
+ return (sbale->eflags & SBAL_EFLAGS_LAST_ENTRY);
+}
+
struct sk_buff *qeth_core_get_next_skb(struct qeth_card *card,
struct qeth_qdio_buffer *qethbuffer,
struct qdio_buffer_element **__element, int *__offset,
diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c
index c2679bfe7f66..d02cd1a67943 100644
--- a/drivers/s390/net/qeth_l2_main.c
+++ b/drivers/s390/net/qeth_l2_main.c
@@ -1512,7 +1512,7 @@ static void qeth_bridge_state_change(struct qeth_card *card,
QETH_CARD_TEXT(card, 2, "brstchng");
if (qports->entry_length != sizeof(struct qeth_sbp_port_entry)) {
- QETH_CARD_TEXT_(card, 2, "BPsz%.8d", qports->entry_length);
+ QETH_CARD_TEXT_(card, 2, "BPsz%04x", qports->entry_length);
return;
}
extrasize = sizeof(struct qeth_sbp_port_entry) * qports->num_entries;
diff --git a/drivers/s390/net/qeth_l3.h b/drivers/s390/net/qeth_l3.h
index 29c1c00e3a0f..551a4b4c03fd 100644
--- a/drivers/s390/net/qeth_l3.h
+++ b/drivers/s390/net/qeth_l3.h
@@ -42,10 +42,6 @@ struct qeth_ipato_entry {
};
-void qeth_l3_ipaddr4_to_string(const __u8 *, char *);
-int qeth_l3_string_to_ipaddr4(const char *, __u8 *);
-void qeth_l3_ipaddr6_to_string(const __u8 *, char *);
-int qeth_l3_string_to_ipaddr6(const char *, __u8 *);
void qeth_l3_ipaddr_to_string(enum qeth_prot_versions, const __u8 *, char *);
int qeth_l3_string_to_ipaddr(const char *, enum qeth_prot_versions, __u8 *);
int qeth_l3_create_device_attributes(struct device *);
diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c
index f8427a2c4840..625227ad16ee 100644
--- a/drivers/s390/net/qeth_l3_main.c
+++ b/drivers/s390/net/qeth_l3_main.c
@@ -55,12 +55,12 @@ static int qeth_l3_isxdigit(char *buf)
return 1;
}
-void qeth_l3_ipaddr4_to_string(const __u8 *addr, char *buf)
+static void qeth_l3_ipaddr4_to_string(const __u8 *addr, char *buf)
{
sprintf(buf, "%i.%i.%i.%i", addr[0], addr[1], addr[2], addr[3]);
}
-int qeth_l3_string_to_ipaddr4(const char *buf, __u8 *addr)
+static int qeth_l3_string_to_ipaddr4(const char *buf, __u8 *addr)
{
int count = 0, rc = 0;
unsigned int in[4];
@@ -78,12 +78,12 @@ int qeth_l3_string_to_ipaddr4(const char *buf, __u8 *addr)
return 0;
}
-void qeth_l3_ipaddr6_to_string(const __u8 *addr, char *buf)
+static void qeth_l3_ipaddr6_to_string(const __u8 *addr, char *buf)
{
sprintf(buf, "%pI6", addr);
}
-int qeth_l3_string_to_ipaddr6(const char *buf, __u8 *addr)
+static int qeth_l3_string_to_ipaddr6(const char *buf, __u8 *addr)
{
const char *end, *end_tmp, *start;
__u16 *in;
@@ -2502,7 +2502,7 @@ static int qeth_l3_arp_query(struct qeth_card *card, char __user *udata)
rc = -EFAULT;
goto free_and_out;
}
- QETH_CARD_TEXT_(card, 4, "qacts");
+ QETH_CARD_TEXT(card, 4, "qacts");
}
free_and_out:
kfree(qinfo.udata);
@@ -3306,7 +3306,7 @@ static int qeth_l3_setup_netdev(struct qeth_card *card)
card->dev->features |= NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_CTAG_FILTER;
- card->dev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
+ netif_keep_dst(card->dev);
card->dev->gso_max_size = 15 * PAGE_SIZE;
SET_NETDEV_DEV(card->dev, &card->gdev->dev);
diff --git a/drivers/s390/scsi/zfcp_aux.c b/drivers/s390/scsi/zfcp_aux.c
index 8004b071a9f2..01a73395a017 100644
--- a/drivers/s390/scsi/zfcp_aux.c
+++ b/drivers/s390/scsi/zfcp_aux.c
@@ -353,9 +353,11 @@ struct zfcp_adapter *zfcp_adapter_enqueue(struct ccw_device *ccw_device)
adapter->ccw_device = ccw_device;
INIT_WORK(&adapter->stat_work, _zfcp_status_read_scheduler);
- INIT_WORK(&adapter->scan_work, zfcp_fc_scan_ports);
+ INIT_DELAYED_WORK(&adapter->scan_work, zfcp_fc_scan_ports);
INIT_WORK(&adapter->ns_up_work, zfcp_fc_sym_name_update);
+ adapter->next_port_scan = jiffies;
+
if (zfcp_qdio_setup(adapter))
goto failed;
@@ -420,7 +422,7 @@ void zfcp_adapter_unregister(struct zfcp_adapter *adapter)
{
struct ccw_device *cdev = adapter->ccw_device;
- cancel_work_sync(&adapter->scan_work);
+ cancel_delayed_work_sync(&adapter->scan_work);
cancel_work_sync(&adapter->stat_work);
cancel_work_sync(&adapter->ns_up_work);
zfcp_destroy_adapter_work_queue(adapter);
diff --git a/drivers/s390/scsi/zfcp_ccw.c b/drivers/s390/scsi/zfcp_ccw.c
index f9879d400d0e..54c7b48fdb46 100644
--- a/drivers/s390/scsi/zfcp_ccw.c
+++ b/drivers/s390/scsi/zfcp_ccw.c
@@ -56,8 +56,22 @@ static int zfcp_ccw_activate(struct ccw_device *cdev, int clear, char *tag)
zfcp_erp_set_adapter_status(adapter, ZFCP_STATUS_COMMON_RUNNING);
zfcp_erp_adapter_reopen(adapter, ZFCP_STATUS_COMMON_ERP_FAILED,
tag);
+
+ /*
+ * We want to scan ports here, with some random backoff and without
+ * rate limit. Recovery has already scheduled a port scan for us,
+ * but with both random delay and rate limit. Nevertheless we get
+ * what we want here by flushing the scheduled work after sleeping
+ * an equivalent random time.
+ * Let the port scan random delay elapse first. If recovery finishes
+ * up to that point in time, that would be perfect for both recovery
+ * and port scan. If not, i.e. recovery takes ages, there was no
+ * point in waiting a random delay on top of the time consumed by
+ * recovery.
+ */
+ msleep(zfcp_fc_port_scan_backoff());
zfcp_erp_wait(adapter);
- flush_work(&adapter->scan_work); /* ok to call even if nothing queued */
+ flush_delayed_work(&adapter->scan_work);
zfcp_ccw_adapter_put(adapter);
@@ -162,11 +176,19 @@ static int zfcp_ccw_set_online(struct ccw_device *cdev)
adapter->req_no = 0;
zfcp_ccw_activate(cdev, 0, "ccsonl1");
- /* scan for remote ports
- either at the end of any successful adapter recovery
- or only after the adapter recovery for setting a device online */
+
+ /*
+ * We want to scan ports here, always, with some random delay and
+ * without rate limit - basically what zfcp_ccw_activate() has
+ * achieved for us. Not quite! That port scan depended on
+ * !no_auto_port_rescan. So let's cover the no_auto_port_rescan
+ * case here to make sure a port scan is done unconditionally.
+ * Since zfcp_ccw_activate() has waited the desired random time,
+ * we can immediately schedule and flush a port scan for the
+ * remaining cases.
+ */
zfcp_fc_inverse_conditional_port_scan(adapter);
- flush_work(&adapter->scan_work); /* ok to call even if nothing queued */
+ flush_delayed_work(&adapter->scan_work);
zfcp_ccw_adapter_put(adapter);
return 0;
}
diff --git a/drivers/s390/scsi/zfcp_def.h b/drivers/s390/scsi/zfcp_def.h
index d91173f326c5..b8e853e53546 100644
--- a/drivers/s390/scsi/zfcp_def.h
+++ b/drivers/s390/scsi/zfcp_def.h
@@ -186,12 +186,13 @@ struct zfcp_adapter {
struct fc_host_statistics *fc_stats;
struct fsf_qtcb_bottom_port *stats_reset_data;
unsigned long stats_reset;
- struct work_struct scan_work;
+ struct delayed_work scan_work;
struct work_struct ns_up_work;
struct service_level service_level;
struct workqueue_struct *work_queue;
struct device_dma_parameters dma_parms;
struct zfcp_fc_events events;
+ unsigned long next_port_scan;
};
struct zfcp_port {
diff --git a/drivers/s390/scsi/zfcp_erp.c b/drivers/s390/scsi/zfcp_erp.c
index c82fe65c4128..2c5d4567d1da 100644
--- a/drivers/s390/scsi/zfcp_erp.c
+++ b/drivers/s390/scsi/zfcp_erp.c
@@ -821,11 +821,6 @@ static int zfcp_erp_port_forced_strategy_close(struct zfcp_erp_action *act)
return ZFCP_ERP_CONTINUES;
}
-static void zfcp_erp_port_strategy_clearstati(struct zfcp_port *port)
-{
- atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED, &port->status);
-}
-
static int zfcp_erp_port_forced_strategy(struct zfcp_erp_action *erp_action)
{
struct zfcp_port *port = erp_action->port;
@@ -833,7 +828,6 @@ static int zfcp_erp_port_forced_strategy(struct zfcp_erp_action *erp_action)
switch (erp_action->step) {
case ZFCP_ERP_STEP_UNINITIALIZED:
- zfcp_erp_port_strategy_clearstati(port);
if ((status & ZFCP_STATUS_PORT_PHYS_OPEN) &&
(status & ZFCP_STATUS_COMMON_OPEN))
return zfcp_erp_port_forced_strategy_close(erp_action);
@@ -933,7 +927,6 @@ static int zfcp_erp_port_strategy(struct zfcp_erp_action *erp_action)
switch (erp_action->step) {
case ZFCP_ERP_STEP_UNINITIALIZED:
- zfcp_erp_port_strategy_clearstati(port);
if (p_status & ZFCP_STATUS_COMMON_OPEN)
return zfcp_erp_port_strategy_close(erp_action);
break;
diff --git a/drivers/s390/scsi/zfcp_ext.h b/drivers/s390/scsi/zfcp_ext.h
index a9c570a09b85..5b500652572b 100644
--- a/drivers/s390/scsi/zfcp_ext.h
+++ b/drivers/s390/scsi/zfcp_ext.h
@@ -85,6 +85,7 @@ extern void zfcp_fc_gs_destroy(struct zfcp_adapter *);
extern int zfcp_fc_exec_bsg_job(struct fc_bsg_job *);
extern int zfcp_fc_timeout_bsg_job(struct fc_bsg_job *);
extern void zfcp_fc_sym_name_update(struct work_struct *);
+extern unsigned int zfcp_fc_port_scan_backoff(void);
extern void zfcp_fc_conditional_port_scan(struct zfcp_adapter *);
extern void zfcp_fc_inverse_conditional_port_scan(struct zfcp_adapter *);
diff --git a/drivers/s390/scsi/zfcp_fc.c b/drivers/s390/scsi/zfcp_fc.c
index ca28e1c66115..25d49f32ca63 100644
--- a/drivers/s390/scsi/zfcp_fc.c
+++ b/drivers/s390/scsi/zfcp_fc.c
@@ -12,6 +12,7 @@
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/utsname.h>
+#include <linux/random.h>
#include <scsi/fc/fc_els.h>
#include <scsi/libfc.h>
#include "zfcp_ext.h"
@@ -31,12 +32,54 @@ module_param_named(no_auto_port_rescan, no_auto_port_rescan, bool, 0600);
MODULE_PARM_DESC(no_auto_port_rescan,
"no automatic port_rescan (default off)");
+static unsigned int port_scan_backoff = 500;
+module_param(port_scan_backoff, uint, 0600);
+MODULE_PARM_DESC(port_scan_backoff,
+ "upper limit of port scan random backoff in msecs (default 500)");
+
+static unsigned int port_scan_ratelimit = 60000;
+module_param(port_scan_ratelimit, uint, 0600);
+MODULE_PARM_DESC(port_scan_ratelimit,
+ "minimum interval between port scans in msecs (default 60000)");
+
+unsigned int zfcp_fc_port_scan_backoff(void)
+{
+ if (!port_scan_backoff)
+ return 0;
+ return get_random_int() % port_scan_backoff;
+}
+
+static void zfcp_fc_port_scan_time(struct zfcp_adapter *adapter)
+{
+ unsigned long interval = msecs_to_jiffies(port_scan_ratelimit);
+ unsigned long backoff = msecs_to_jiffies(zfcp_fc_port_scan_backoff());
+
+ adapter->next_port_scan = jiffies + interval + backoff;
+}
+
+static void zfcp_fc_port_scan(struct zfcp_adapter *adapter)
+{
+ unsigned long now = jiffies;
+ unsigned long next = adapter->next_port_scan;
+ unsigned long delay = 0, max;
+
+ /* delay only needed within waiting period */
+ if (time_before(now, next)) {
+ delay = next - now;
+ /* paranoia: never ever delay scans longer than specified */
+ max = msecs_to_jiffies(port_scan_ratelimit + port_scan_backoff);
+ delay = min(delay, max);
+ }
+
+ queue_delayed_work(adapter->work_queue, &adapter->scan_work, delay);
+}
+
void zfcp_fc_conditional_port_scan(struct zfcp_adapter *adapter)
{
if (no_auto_port_rescan)
return;
- queue_work(adapter->work_queue, &adapter->scan_work);
+ zfcp_fc_port_scan(adapter);
}
void zfcp_fc_inverse_conditional_port_scan(struct zfcp_adapter *adapter)
@@ -44,7 +87,7 @@ void zfcp_fc_inverse_conditional_port_scan(struct zfcp_adapter *adapter)
if (!no_auto_port_rescan)
return;
- queue_work(adapter->work_queue, &adapter->scan_work);
+ zfcp_fc_port_scan(adapter);
}
/**
@@ -680,12 +723,15 @@ static int zfcp_fc_eval_gpn_ft(struct zfcp_fc_req *fc_req,
*/
void zfcp_fc_scan_ports(struct work_struct *work)
{
- struct zfcp_adapter *adapter = container_of(work, struct zfcp_adapter,
+ struct delayed_work *dw = to_delayed_work(work);
+ struct zfcp_adapter *adapter = container_of(dw, struct zfcp_adapter,
scan_work);
int ret, i;
struct zfcp_fc_req *fc_req;
int chain, max_entries, buf_num, max_bytes;
+ zfcp_fc_port_scan_time(adapter);
+
chain = adapter->adapter_features & FSF_FEATURE_ELS_CT_CHAINED_SBALS;
buf_num = chain ? ZFCP_FC_GPN_FT_NUM_BUFS : 1;
max_entries = chain ? ZFCP_FC_GPN_FT_MAX_ENT : ZFCP_FC_GPN_FT_ENT_PAGE;
diff --git a/drivers/s390/scsi/zfcp_fc.h b/drivers/s390/scsi/zfcp_fc.h
index b1d2024ed513..df2b541c8287 100644
--- a/drivers/s390/scsi/zfcp_fc.h
+++ b/drivers/s390/scsi/zfcp_fc.h
@@ -212,8 +212,6 @@ static inline
void zfcp_fc_scsi_to_fcp(struct fcp_cmnd *fcp, struct scsi_cmnd *scsi,
u8 tm_flags)
{
- char tag[2];
-
int_to_scsilun(scsi->device->lun, (struct scsi_lun *) &fcp->fc_lun);
if (unlikely(tm_flags)) {
@@ -221,17 +219,7 @@ void zfcp_fc_scsi_to_fcp(struct fcp_cmnd *fcp, struct scsi_cmnd *scsi,
return;
}
- if (scsi_populate_tag_msg(scsi, tag)) {
- switch (tag[0]) {
- case MSG_ORDERED_TAG:
- fcp->fc_pri_ta |= FCP_PTA_ORDERED;
- break;
- case MSG_SIMPLE_TAG:
- fcp->fc_pri_ta |= FCP_PTA_SIMPLE;
- break;
- };
- } else
- fcp->fc_pri_ta = FCP_PTA_SIMPLE;
+ fcp->fc_pri_ta = FCP_PTA_SIMPLE;
if (scsi->sc_data_direction == DMA_FROM_DEVICE)
fcp->fc_flags |= FCP_CFL_RDDATA;
diff --git a/drivers/s390/scsi/zfcp_fsf.c b/drivers/s390/scsi/zfcp_fsf.c
index 0fe8d5d95119..21ec5e2f584c 100644
--- a/drivers/s390/scsi/zfcp_fsf.c
+++ b/drivers/s390/scsi/zfcp_fsf.c
@@ -1396,8 +1396,7 @@ static void zfcp_fsf_open_port_handler(struct zfcp_fsf_req *req)
port->handle = header->port_handle;
atomic_set_mask(ZFCP_STATUS_COMMON_OPEN |
ZFCP_STATUS_PORT_PHYS_OPEN, &port->status);
- atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED |
- ZFCP_STATUS_COMMON_ACCESS_BOXED,
+ atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_BOXED,
&port->status);
/* check whether D_ID has changed during open */
/*
diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c
index 7b353647cb90..75f4bfc2b98a 100644
--- a/drivers/s390/scsi/zfcp_scsi.c
+++ b/drivers/s390/scsi/zfcp_scsi.c
@@ -32,25 +32,6 @@ static bool allow_lun_scan = 1;
module_param(allow_lun_scan, bool, 0600);
MODULE_PARM_DESC(allow_lun_scan, "For NPIV, scan and attach all storage LUNs");
-static int zfcp_scsi_change_queue_depth(struct scsi_device *sdev, int depth,
- int reason)
-{
- switch (reason) {
- case SCSI_QDEPTH_DEFAULT:
- scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth);
- break;
- case SCSI_QDEPTH_QFULL:
- scsi_track_queue_full(sdev, depth);
- break;
- case SCSI_QDEPTH_RAMP_UP:
- scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth);
- break;
- default:
- return -EOPNOTSUPP;
- }
- return sdev->queue_depth;
-}
-
static void zfcp_scsi_slave_destroy(struct scsi_device *sdev)
{
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
@@ -66,9 +47,7 @@ static void zfcp_scsi_slave_destroy(struct scsi_device *sdev)
static int zfcp_scsi_slave_configure(struct scsi_device *sdp)
{
if (sdp->tagged_supported)
- scsi_adjust_queue_depth(sdp, MSG_SIMPLE_TAG, default_depth);
- else
- scsi_adjust_queue_depth(sdp, 0, 1);
+ scsi_change_queue_depth(sdp, default_depth);
return 0;
}
@@ -307,7 +286,7 @@ static struct scsi_host_template zfcp_scsi_host_template = {
.slave_alloc = zfcp_scsi_slave_alloc,
.slave_configure = zfcp_scsi_slave_configure,
.slave_destroy = zfcp_scsi_slave_destroy,
- .change_queue_depth = zfcp_scsi_change_queue_depth,
+ .change_queue_depth = scsi_change_queue_depth,
.proc_name = "zfcp",
.can_queue = 4096,
.this_id = -1,
@@ -322,6 +301,7 @@ static struct scsi_host_template zfcp_scsi_host_template = {
.use_clustering = 1,
.shost_attrs = zfcp_sysfs_shost_attrs,
.sdev_attrs = zfcp_sysfs_sdev_attrs,
+ .track_queue_depth = 1,
};
/**
diff --git a/drivers/s390/scsi/zfcp_sysfs.c b/drivers/s390/scsi/zfcp_sysfs.c
index 672b57219e11..96a0be13e841 100644
--- a/drivers/s390/scsi/zfcp_sysfs.c
+++ b/drivers/s390/scsi/zfcp_sysfs.c
@@ -73,9 +73,7 @@ ZFCP_DEFINE_ATTR(zfcp_port, port, status, "0x%08x\n",
ZFCP_DEFINE_ATTR(zfcp_port, port, in_recovery, "%d\n",
(atomic_read(&port->status) &
ZFCP_STATUS_COMMON_ERP_INUSE) != 0);
-ZFCP_DEFINE_ATTR(zfcp_port, port, access_denied, "%d\n",
- (atomic_read(&port->status) &
- ZFCP_STATUS_COMMON_ACCESS_DENIED) != 0);
+ZFCP_DEFINE_ATTR_CONST(port, access_denied, "%d\n", 0);
ZFCP_DEFINE_ATTR(zfcp_unit, unit, status, "0x%08x\n",
zfcp_unit_sdev_status(unit));
@@ -223,9 +221,13 @@ static ssize_t zfcp_sysfs_port_rescan_store(struct device *dev,
if (!adapter)
return -ENODEV;
- /* sync the user-space- with the kernel-invocation of scan_work */
- queue_work(adapter->work_queue, &adapter->scan_work);
- flush_work(&adapter->scan_work);
+ /*
+ * Users wish is our command: immediately schedule and flush a
+ * worker to conduct a synchronous port scan, that is, neither
+ * a random delay nor a rate limit is applied here.
+ */
+ queue_delayed_work(adapter->work_queue, &adapter->scan_work, 0);
+ flush_delayed_work(&adapter->scan_work);
zfcp_ccw_adapter_put(adapter);
return (ssize_t) count;
@@ -439,16 +441,15 @@ static ssize_t zfcp_sysfs_scsi_##_name##_show(struct device *dev, \
{ \
struct scsi_device *sdev = to_scsi_device(dev); \
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev); \
- struct zfcp_port *port = zfcp_sdev->port; \
\
return sprintf(buf, _format, _value); \
} \
static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_scsi_##_name##_show, NULL);
ZFCP_DEFINE_SCSI_ATTR(hba_id, "%s\n",
- dev_name(&port->adapter->ccw_device->dev));
+ dev_name(&zfcp_sdev->port->adapter->ccw_device->dev));
ZFCP_DEFINE_SCSI_ATTR(wwpn, "0x%016llx\n",
- (unsigned long long) port->wwpn);
+ (unsigned long long) zfcp_sdev->port->wwpn);
static ssize_t zfcp_sysfs_scsi_fcp_lun_show(struct device *dev,
struct device_attribute *attr,
@@ -460,6 +461,49 @@ static ssize_t zfcp_sysfs_scsi_fcp_lun_show(struct device *dev,
}
static DEVICE_ATTR(fcp_lun, S_IRUGO, zfcp_sysfs_scsi_fcp_lun_show, NULL);
+ZFCP_DEFINE_SCSI_ATTR(zfcp_access_denied, "%d\n",
+ (atomic_read(&zfcp_sdev->status) &
+ ZFCP_STATUS_COMMON_ACCESS_DENIED) != 0);
+
+static ssize_t zfcp_sysfs_scsi_zfcp_failed_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ unsigned int status = atomic_read(&sdev_to_zfcp(sdev)->status);
+ unsigned int failed = status & ZFCP_STATUS_COMMON_ERP_FAILED ? 1 : 0;
+
+ return sprintf(buf, "%d\n", failed);
+}
+
+static ssize_t zfcp_sysfs_scsi_zfcp_failed_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val) || val != 0)
+ return -EINVAL;
+
+ zfcp_erp_set_lun_status(sdev, ZFCP_STATUS_COMMON_RUNNING);
+ zfcp_erp_lun_reopen(sdev, ZFCP_STATUS_COMMON_ERP_FAILED,
+ "syufai3");
+ zfcp_erp_wait(sdev_to_zfcp(sdev)->port->adapter);
+
+ return count;
+}
+static DEVICE_ATTR(zfcp_failed, S_IWUSR | S_IRUGO,
+ zfcp_sysfs_scsi_zfcp_failed_show,
+ zfcp_sysfs_scsi_zfcp_failed_store);
+
+ZFCP_DEFINE_SCSI_ATTR(zfcp_in_recovery, "%d\n",
+ (atomic_read(&zfcp_sdev->status) &
+ ZFCP_STATUS_COMMON_ERP_INUSE) != 0);
+
+ZFCP_DEFINE_SCSI_ATTR(zfcp_status, "0x%08x\n",
+ atomic_read(&zfcp_sdev->status));
+
struct device_attribute *zfcp_sysfs_sdev_attrs[] = {
&dev_attr_fcp_lun,
&dev_attr_wwpn,
@@ -467,6 +511,10 @@ struct device_attribute *zfcp_sysfs_sdev_attrs[] = {
&dev_attr_read_latency,
&dev_attr_write_latency,
&dev_attr_cmd_latency,
+ &dev_attr_zfcp_access_denied,
+ &dev_attr_zfcp_failed,
+ &dev_attr_zfcp_in_recovery,
+ &dev_attr_zfcp_status,
NULL
};