diff options
Diffstat (limited to 'drivers/s390/block/dasd_eckd.c')
| -rw-r--r-- | drivers/s390/block/dasd_eckd.c | 2406 |
1 files changed, 1853 insertions, 553 deletions
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 21254793c60..2e8e0755070 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -1,17 +1,15 @@ /* - * File...........: linux/drivers/s390/block/dasd_eckd.c * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> * Horst Hummel <Horst.Hummel@de.ibm.com> * Carsten Otte <Cotte@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> * Bugreports.to..: <Linux390@de.ibm.com> - * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000 + * Copyright IBM Corp. 1999, 2009 * EMC Symmetrix ioctl Copyright EMC Corporation, 2008 * Author.........: Nigel Hislop <hislop_nigel@emc.com> - * */ -#define KMSG_COMPONENT "dasd" +#define KMSG_COMPONENT "dasd-eckd" #include <linux/stddef.h> #include <linux/kernel.h> @@ -19,13 +17,14 @@ #include <linux/hdreg.h> /* HDIO_GETGEO */ #include <linux/bio.h> #include <linux/module.h> +#include <linux/compat.h> #include <linux/init.h> +#include <asm/css_chars.h> #include <asm/debug.h> #include <asm/idals.h> #include <asm/ebcdic.h> #include <asm/io.h> -#include <asm/todclk.h> #include <asm/uaccess.h> #include <asm/cio.h> #include <asm/ccwdev.h> @@ -33,8 +32,6 @@ #include "dasd_int.h" #include "dasd_eckd.h" -#include "../cio/chsc.h" - #ifdef PRINTK_HEADER #undef PRINTK_HEADER @@ -55,6 +52,15 @@ #define ECKD_F7(i) (i->factor7) #define ECKD_F8(i) (i->factor8) +/* + * raw track access always map to 64k in memory + * so it maps to 16 blocks of 4k per track + */ +#define DASD_RAW_BLOCK_PER_TRACK 16 +#define DASD_RAW_BLOCKSIZE 4096 +/* 64k are 128 x 512 byte sectors */ +#define DASD_RAW_SECTORS_PER_TRACK 128 + MODULE_LICENSE("GPL"); static struct dasd_discipline dasd_eckd_discipline; @@ -64,7 +70,7 @@ static struct dasd_discipline dasd_eckd_discipline; static struct ccw_device_id dasd_eckd_ids[] = { { CCW_DEVICE_DEVTYPE (0x3990, 0, 0x3390, 0), .driver_info = 0x1}, { CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3390, 0), .driver_info = 0x2}, - { CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3390, 0), .driver_info = 0x3}, + { CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3380, 0), .driver_info = 0x3}, { CCW_DEVICE_DEVTYPE (0x3990, 0, 0x3380, 0), .driver_info = 0x4}, { CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3380, 0), .driver_info = 0x5}, { CCW_DEVICE_DEVTYPE (0x9343, 0, 0x9345, 0), .driver_info = 0x6}, @@ -79,6 +85,33 @@ MODULE_DEVICE_TABLE(ccw, dasd_eckd_ids); static struct ccw_driver dasd_eckd_driver; /* see below */ +static void *rawpadpage; + +#define INIT_CQR_OK 0 +#define INIT_CQR_UNFORMATTED 1 +#define INIT_CQR_ERROR 2 + +/* emergency request for reserve/release */ +static struct { + struct dasd_ccw_req cqr; + struct ccw1 ccw; + char data[32]; +} *dasd_reserve_req; +static DEFINE_MUTEX(dasd_reserve_mutex); + +/* definitions for the path verification worker */ +struct path_verification_work_data { + struct work_struct worker; + struct dasd_device *device; + struct dasd_ccw_req cqr; + struct ccw1 ccw; + __u8 rcd_buffer[DASD_ECKD_RCD_DATA_SIZE]; + int isglobal; + __u8 tbvpm; +}; +static struct path_verification_work_data *path_verification_worker; +static DEFINE_MUTEX(dasd_path_verification_mutex); + /* initial attempt at a probe function. this can be simplified once * the other detection code is gone */ static int @@ -87,11 +120,12 @@ dasd_eckd_probe (struct ccw_device *cdev) int ret; /* set ECKD specific ccw-device options */ - ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE); + ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE | + CCWDEV_DO_PATHGROUP | CCWDEV_DO_MULTIPATH); if (ret) { - DBF_EVENT(DBF_WARNING, - "dasd_eckd_probe: could not set ccw-device options " - "for %s\n", dev_name(&cdev->dev)); + DBF_EVENT_DEVID(DBF_WARNING, cdev, "%s", + "dasd_eckd_probe: could not set " + "ccw-device options"); return ret; } ret = dasd_generic_probe(cdev, &dasd_eckd_discipline); @@ -104,20 +138,13 @@ dasd_eckd_set_online(struct ccw_device *cdev) return dasd_generic_set_online(cdev, &dasd_eckd_discipline); } -static struct ccw_driver dasd_eckd_driver = { - .name = "dasd-eckd", - .owner = THIS_MODULE, - .ids = dasd_eckd_ids, - .probe = dasd_eckd_probe, - .remove = dasd_generic_remove, - .set_offline = dasd_generic_set_offline, - .set_online = dasd_eckd_set_online, - .notify = dasd_generic_notify, -}; - static const int sizes_trk0[] = { 28, 148, 84 }; #define LABEL_SIZE 140 +/* head and record addresses of count_area read in analysis ccw */ +static const int count_area_head[] = { 0, 0, 0, 0, 2 }; +static const int count_area_rec[] = { 1, 2, 3, 4, 1 }; + static inline unsigned int round_up_multiple(unsigned int no, unsigned int mult) { @@ -190,7 +217,7 @@ check_XRC (struct ccw1 *de_ccw, rc = get_sync_clock(&data->ep_sys_time); /* Ignore return code if sync clock is switched off. */ - if (rc == -ENOSYS || rc == -EACCES) + if (rc == -EOPNOTSUPP || rc == -EACCES) rc = 0; de_ccw->count = sizeof(struct DE_eckd_data); @@ -301,7 +328,7 @@ static int check_XRC_on_prefix(struct PFX_eckd_data *pfxdata, rc = get_sync_clock(&pfxdata->define_extent.ep_sys_time); /* Ignore return code if sync clock is switched off. */ - if (rc == -ENOSYS || rc == -EACCES) + if (rc == -EOPNOTSUPP || rc == -EACCES) rc = 0; return rc; } @@ -371,6 +398,23 @@ static void fill_LRE_data(struct LRE_eckd_data *data, unsigned int trk, data->length = reclen; data->operation.operation = 0x03; break; + case DASD_ECKD_CCW_WRITE_FULL_TRACK: + data->operation.orientation = 0x0; + data->operation.operation = 0x3F; + data->extended_operation = 0x11; + data->length = 0; + data->extended_parameter_length = 0x02; + if (data->count > 8) { + data->extended_parameter[0] = 0xFF; + data->extended_parameter[1] = 0xFF; + data->extended_parameter[1] <<= (16 - count); + } else { + data->extended_parameter[0] = 0xFF; + data->extended_parameter[0] <<= (8 - count); + data->extended_parameter[1] = 0x00; + } + data->sector = 0xFF; + break; case DASD_ECKD_CCW_WRITE_TRACK_DATA: data->auxiliary.length_valid = 0x1; data->length = reclen; /* not tlf, as one might think */ @@ -394,6 +438,12 @@ static void fill_LRE_data(struct LRE_eckd_data *data, unsigned int trk, case DASD_ECKD_CCW_READ_COUNT: data->operation.operation = 0x06; break; + case DASD_ECKD_CCW_READ_TRACK: + data->operation.orientation = 0x1; + data->operation.operation = 0x0C; + data->extended_parameter_length = 0; + data->sector = 0xFF; + break; case DASD_ECKD_CCW_READ_TRACK_DATA: data->auxiliary.length_valid = 0x1; data->length = tlf; @@ -437,10 +487,16 @@ static int prefix_LRE(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, ccw->cmd_code = DASD_ECKD_CCW_PFX; ccw->flags = 0; - ccw->count = sizeof(*pfxdata); - ccw->cda = (__u32) __pa(pfxdata); + if (cmd == DASD_ECKD_CCW_WRITE_FULL_TRACK) { + ccw->count = sizeof(*pfxdata) + 2; + ccw->cda = (__u32) __pa(pfxdata); + memset(pfxdata, 0, sizeof(*pfxdata) + 2); + } else { + ccw->count = sizeof(*pfxdata); + ccw->cda = (__u32) __pa(pfxdata); + memset(pfxdata, 0, sizeof(*pfxdata)); + } - memset(pfxdata, 0, sizeof(*pfxdata)); /* prefix data */ if (format > 1) { DBF_DEV_EVENT(DBF_ERR, basedev, @@ -474,6 +530,7 @@ static int prefix_LRE(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, dedata->mask.perm = 0x1; dedata->attributes.operation = basepriv->attrib.operation; break; + case DASD_ECKD_CCW_READ_TRACK: case DASD_ECKD_CCW_READ_TRACK_DATA: dedata->mask.perm = 0x1; dedata->attributes.operation = basepriv->attrib.operation; @@ -500,6 +557,11 @@ static int prefix_LRE(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, dedata->attributes.operation = DASD_BYPASS_CACHE; rc = check_XRC_on_prefix(pfxdata, basedev); break; + case DASD_ECKD_CCW_WRITE_FULL_TRACK: + dedata->mask.perm = 0x03; + dedata->attributes.operation = basepriv->attrib.operation; + dedata->blk_size = 0; + break; case DASD_ECKD_CCW_WRITE_TRACK_DATA: dedata->mask.perm = 0x02; dedata->attributes.operation = basepriv->attrib.operation; @@ -694,22 +756,13 @@ dasd_eckd_cdl_reclen(int recid) return sizes_trk0[recid]; return LABEL_SIZE; } - -/* - * Generate device unique id that specifies the physical device. - */ -static int dasd_eckd_generate_uid(struct dasd_device *device, - struct dasd_uid *uid) +/* create unique id from private structure. */ +static void create_uid(struct dasd_eckd_private *private) { - struct dasd_eckd_private *private; int count; + struct dasd_uid *uid; - private = (struct dasd_eckd_private *) device->private; - if (!private) - return -ENODEV; - if (!private->ned || !private->gneq) - return -ENODEV; - + uid = &private->uid; memset(uid, 0, sizeof(struct dasd_uid)); memcpy(uid->vendor, private->ned->HDA_manufacturer, sizeof(uid->vendor) - 1); @@ -718,7 +771,7 @@ static int dasd_eckd_generate_uid(struct dasd_device *device, sizeof(uid->serial) - 1); EBCASC(uid->serial, sizeof(uid->serial) - 1); uid->ssid = private->gneq->subsystemID; - uid->real_unit_addr = private->ned->unit_addr;; + uid->real_unit_addr = private->ned->unit_addr; if (private->sneq) { uid->type = private->sneq->sua_flags; if (uid->type == UA_BASE_PAV_ALIAS) @@ -732,39 +785,136 @@ static int dasd_eckd_generate_uid(struct dasd_device *device, private->vdsneq->uit[count]); } } - return 0; } -static struct dasd_ccw_req *dasd_eckd_build_rcd_lpm(struct dasd_device *device, - void *rcd_buffer, - struct ciw *ciw, __u8 lpm) +/* + * Generate device unique id that specifies the physical device. + */ +static int dasd_eckd_generate_uid(struct dasd_device *device) { - struct dasd_ccw_req *cqr; - struct ccw1 *ccw; + struct dasd_eckd_private *private; + unsigned long flags; + + private = (struct dasd_eckd_private *) device->private; + if (!private) + return -ENODEV; + if (!private->ned || !private->gneq) + return -ENODEV; + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + create_uid(private); + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + return 0; +} - cqr = dasd_smalloc_request("ECKD", 1 /* RCD */, ciw->count, device); +static int dasd_eckd_get_uid(struct dasd_device *device, struct dasd_uid *uid) +{ + struct dasd_eckd_private *private; + unsigned long flags; - if (IS_ERR(cqr)) { - DBF_DEV_EVENT(DBF_WARNING, device, "%s", - "Could not allocate RCD request"); - return cqr; + if (device->private) { + private = (struct dasd_eckd_private *)device->private; + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + *uid = private->uid; + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + return 0; } + return -EINVAL; +} + +/* + * compare device UID with data of a given dasd_eckd_private structure + * return 0 for match + */ +static int dasd_eckd_compare_path_uid(struct dasd_device *device, + struct dasd_eckd_private *private) +{ + struct dasd_uid device_uid; + + create_uid(private); + dasd_eckd_get_uid(device, &device_uid); + + return memcmp(&device_uid, &private->uid, sizeof(struct dasd_uid)); +} + +static void dasd_eckd_fill_rcd_cqr(struct dasd_device *device, + struct dasd_ccw_req *cqr, + __u8 *rcd_buffer, + __u8 lpm) +{ + struct ccw1 *ccw; + /* + * buffer has to start with EBCDIC "V1.0" to show + * support for virtual device SNEQ + */ + rcd_buffer[0] = 0xE5; + rcd_buffer[1] = 0xF1; + rcd_buffer[2] = 0x4B; + rcd_buffer[3] = 0xF0; ccw = cqr->cpaddr; - ccw->cmd_code = ciw->cmd; + ccw->cmd_code = DASD_ECKD_CCW_RCD; + ccw->flags = 0; ccw->cda = (__u32)(addr_t)rcd_buffer; - ccw->count = ciw->count; + ccw->count = DASD_ECKD_RCD_DATA_SIZE; + cqr->magic = DASD_ECKD_MAGIC; cqr->startdev = device; cqr->memdev = device; cqr->block = NULL; cqr->expires = 10*HZ; cqr->lpm = lpm; - clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); - cqr->retries = 2; - cqr->buildclk = get_clock(); + cqr->retries = 256; + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; - return cqr; + set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags); +} + +/* + * Wakeup helper for read_conf + * if the cqr is not done and needs some error recovery + * the buffer has to be re-initialized with the EBCDIC "V1.0" + * to show support for virtual device SNEQ + */ +static void read_conf_cb(struct dasd_ccw_req *cqr, void *data) +{ + struct ccw1 *ccw; + __u8 *rcd_buffer; + + if (cqr->status != DASD_CQR_DONE) { + ccw = cqr->cpaddr; + rcd_buffer = (__u8 *)((addr_t) ccw->cda); + memset(rcd_buffer, 0, sizeof(*rcd_buffer)); + + rcd_buffer[0] = 0xE5; + rcd_buffer[1] = 0xF1; + rcd_buffer[2] = 0x4B; + rcd_buffer[3] = 0xF0; + } + dasd_wakeup_cb(cqr, data); +} + +static int dasd_eckd_read_conf_immediately(struct dasd_device *device, + struct dasd_ccw_req *cqr, + __u8 *rcd_buffer, + __u8 lpm) +{ + struct ciw *ciw; + int rc; + /* + * sanity check: scan for RCD command in extended SenseID data + * some devices do not support RCD + */ + ciw = ccw_device_get_ciw(device->cdev, CIW_TYPE_RCD); + if (!ciw || ciw->cmd != DASD_ECKD_CCW_RCD) + return -EOPNOTSUPP; + + dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buffer, lpm); + clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); + set_bit(DASD_CQR_ALLOW_SLOCK, &cqr->flags); + cqr->retries = 5; + cqr->callback = read_conf_cb; + rc = dasd_sleep_on_immediatly(cqr); + return rc; } static int dasd_eckd_read_conf_lpm(struct dasd_device *device, @@ -777,32 +927,30 @@ static int dasd_eckd_read_conf_lpm(struct dasd_device *device, struct dasd_ccw_req *cqr; /* - * scan for RCD command in extended SenseID data + * sanity check: scan for RCD command in extended SenseID data + * some devices do not support RCD */ ciw = ccw_device_get_ciw(device->cdev, CIW_TYPE_RCD); - if (!ciw || ciw->cmd == 0) { + if (!ciw || ciw->cmd != DASD_ECKD_CCW_RCD) { ret = -EOPNOTSUPP; goto out_error; } - rcd_buf = kzalloc(ciw->count, GFP_KERNEL | GFP_DMA); + rcd_buf = kzalloc(DASD_ECKD_RCD_DATA_SIZE, GFP_KERNEL | GFP_DMA); if (!rcd_buf) { ret = -ENOMEM; goto out_error; } - - /* - * buffer has to start with EBCDIC "V1.0" to show - * support for virtual device SNEQ - */ - rcd_buf[0] = 0xE5; - rcd_buf[1] = 0xF1; - rcd_buf[2] = 0x4B; - rcd_buf[3] = 0xF0; - cqr = dasd_eckd_build_rcd_lpm(device, rcd_buf, ciw, lpm); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* RCD */, + 0, /* use rcd_buf as data ara */ + device); if (IS_ERR(cqr)) { - ret = PTR_ERR(cqr); + DBF_DEV_EVENT(DBF_WARNING, device, "%s", + "Could not allocate RCD request"); + ret = -ENOMEM; goto out_error; } + dasd_eckd_fill_rcd_cqr(device, cqr, rcd_buf, lpm); + cqr->callback = read_conf_cb; ret = dasd_sleep_on(cqr); /* * on success we update the user input parms @@ -811,7 +959,7 @@ static int dasd_eckd_read_conf_lpm(struct dasd_device *device, if (ret) goto out_error; - *rcd_buffer_size = ciw->count; + *rcd_buffer_size = DASD_ECKD_RCD_DATA_SIZE; *rcd_buffer = rcd_buf; return 0; out_error: @@ -880,59 +1028,378 @@ static int dasd_eckd_read_conf(struct dasd_device *device) { void *conf_data; int conf_len, conf_data_saved; - int rc; - __u8 lpm; - struct dasd_eckd_private *private; - struct dasd_eckd_path *path_data; + int rc, path_err; + __u8 lpm, opm; + struct dasd_eckd_private *private, path_private; + struct dasd_path *path_data; + struct dasd_uid *uid; + char print_path_uid[60], print_device_uid[60]; private = (struct dasd_eckd_private *) device->private; - path_data = (struct dasd_eckd_path *) &private->path_data; - path_data->opm = ccw_device_get_path_mask(device->cdev); - lpm = 0x80; + path_data = &device->path_data; + opm = ccw_device_get_path_mask(device->cdev); conf_data_saved = 0; + path_err = 0; /* get configuration data per operational path */ for (lpm = 0x80; lpm; lpm>>= 1) { - if (lpm & path_data->opm){ - rc = dasd_eckd_read_conf_lpm(device, &conf_data, - &conf_len, lpm); - if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */ - DBF_EVENT(DBF_WARNING, - "Read configuration data returned " - "error %d for device: %s", rc, - dev_name(&device->cdev->dev)); - return rc; + if (!(lpm & opm)) + continue; + rc = dasd_eckd_read_conf_lpm(device, &conf_data, + &conf_len, lpm); + if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */ + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, + "Read configuration data returned " + "error %d", rc); + return rc; + } + if (conf_data == NULL) { + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", + "No configuration data " + "retrieved"); + /* no further analysis possible */ + path_data->opm |= lpm; + continue; /* no error */ + } + /* save first valid configuration data */ + if (!conf_data_saved) { + kfree(private->conf_data); + private->conf_data = conf_data; + private->conf_len = conf_len; + if (dasd_eckd_identify_conf_parts(private)) { + private->conf_data = NULL; + private->conf_len = 0; + kfree(conf_data); + continue; } - if (conf_data == NULL) { - DBF_EVENT(DBF_WARNING, "No configuration " - "data retrieved for device: %s", - dev_name(&device->cdev->dev)); - continue; /* no error */ + /* + * build device UID that other path data + * can be compared to it + */ + dasd_eckd_generate_uid(device); + conf_data_saved++; + } else { + path_private.conf_data = conf_data; + path_private.conf_len = DASD_ECKD_RCD_DATA_SIZE; + if (dasd_eckd_identify_conf_parts( + &path_private)) { + path_private.conf_data = NULL; + path_private.conf_len = 0; + kfree(conf_data); + continue; } - /* save first valid configuration data */ - if (!conf_data_saved) { - kfree(private->conf_data); - private->conf_data = conf_data; - private->conf_len = conf_len; - if (dasd_eckd_identify_conf_parts(private)) { - private->conf_data = NULL; - private->conf_len = 0; - kfree(conf_data); - continue; - } - conf_data_saved++; + + if (dasd_eckd_compare_path_uid( + device, &path_private)) { + uid = &path_private.uid; + if (strlen(uid->vduit) > 0) + snprintf(print_path_uid, + sizeof(print_path_uid), + "%s.%s.%04x.%02x.%s", + uid->vendor, uid->serial, + uid->ssid, uid->real_unit_addr, + uid->vduit); + else + snprintf(print_path_uid, + sizeof(print_path_uid), + "%s.%s.%04x.%02x", + uid->vendor, uid->serial, + uid->ssid, + uid->real_unit_addr); + uid = &private->uid; + if (strlen(uid->vduit) > 0) + snprintf(print_device_uid, + sizeof(print_device_uid), + "%s.%s.%04x.%02x.%s", + uid->vendor, uid->serial, + uid->ssid, uid->real_unit_addr, + uid->vduit); + else + snprintf(print_device_uid, + sizeof(print_device_uid), + "%s.%s.%04x.%02x", + uid->vendor, uid->serial, + uid->ssid, + uid->real_unit_addr); + dev_err(&device->cdev->dev, + "Not all channel paths lead to " + "the same device, path %02X leads to " + "device %s instead of %s\n", lpm, + print_path_uid, print_device_uid); + path_err = -EINVAL; + continue; } - switch (dasd_eckd_path_access(conf_data, conf_len)) { + + path_private.conf_data = NULL; + path_private.conf_len = 0; + } + switch (dasd_eckd_path_access(conf_data, conf_len)) { + case 0x02: + path_data->npm |= lpm; + break; + case 0x03: + path_data->ppm |= lpm; + break; + } + path_data->opm |= lpm; + + if (conf_data != private->conf_data) + kfree(conf_data); + } + + return path_err; +} + +static int verify_fcx_max_data(struct dasd_device *device, __u8 lpm) +{ + struct dasd_eckd_private *private; + int mdc; + u32 fcx_max_data; + + private = (struct dasd_eckd_private *) device->private; + if (private->fcx_max_data) { + mdc = ccw_device_get_mdc(device->cdev, lpm); + if ((mdc < 0)) { + dev_warn(&device->cdev->dev, + "Detecting the maximum data size for zHPF " + "requests failed (rc=%d) for a new path %x\n", + mdc, lpm); + return mdc; + } + fcx_max_data = mdc * FCX_MAX_DATA_FACTOR; + if (fcx_max_data < private->fcx_max_data) { + dev_warn(&device->cdev->dev, + "The maximum data size for zHPF requests %u " + "on a new path %x is below the active maximum " + "%u\n", fcx_max_data, lpm, + private->fcx_max_data); + return -EACCES; + } + } + return 0; +} + +static int rebuild_device_uid(struct dasd_device *device, + struct path_verification_work_data *data) +{ + struct dasd_eckd_private *private; + struct dasd_path *path_data; + __u8 lpm, opm; + int rc; + + rc = -ENODEV; + private = (struct dasd_eckd_private *) device->private; + path_data = &device->path_data; + opm = device->path_data.opm; + + for (lpm = 0x80; lpm; lpm >>= 1) { + if (!(lpm & opm)) + continue; + memset(&data->rcd_buffer, 0, sizeof(data->rcd_buffer)); + memset(&data->cqr, 0, sizeof(data->cqr)); + data->cqr.cpaddr = &data->ccw; + rc = dasd_eckd_read_conf_immediately(device, &data->cqr, + data->rcd_buffer, + lpm); + + if (rc) { + if (rc == -EOPNOTSUPP) /* -EOPNOTSUPP is ok */ + continue; + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, + "Read configuration data " + "returned error %d", rc); + break; + } + memcpy(private->conf_data, data->rcd_buffer, + DASD_ECKD_RCD_DATA_SIZE); + if (dasd_eckd_identify_conf_parts(private)) { + rc = -ENODEV; + } else /* first valid path is enough */ + break; + } + + if (!rc) + rc = dasd_eckd_generate_uid(device); + + return rc; +} + +static void do_path_verification_work(struct work_struct *work) +{ + struct path_verification_work_data *data; + struct dasd_device *device; + 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; + unsigned long flags; + char print_uid[60]; + int rc; + + data = container_of(work, struct path_verification_work_data, worker); + device = data->device; + + /* delay path verification until device was resumed */ + if (test_bit(DASD_FLAG_SUSPENDED, &device->flags)) { + schedule_work(work); + return; + } + + opm = 0; + npm = 0; + ppm = 0; + epm = 0; + for (lpm = 0x80; lpm; lpm >>= 1) { + if (!(lpm & data->tbvpm)) + continue; + memset(&data->rcd_buffer, 0, sizeof(data->rcd_buffer)); + memset(&data->cqr, 0, sizeof(data->cqr)); + data->cqr.cpaddr = &data->ccw; + rc = dasd_eckd_read_conf_immediately(device, &data->cqr, + data->rcd_buffer, + lpm); + if (!rc) { + switch (dasd_eckd_path_access(data->rcd_buffer, + DASD_ECKD_RCD_DATA_SIZE) + ) { case 0x02: - path_data->npm |= lpm; + npm |= lpm; break; case 0x03: - path_data->ppm |= lpm; + ppm |= lpm; break; } - if (conf_data != private->conf_data) - kfree(conf_data); + opm |= lpm; + } else if (rc == -EOPNOTSUPP) { + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", + "path verification: No configuration " + "data retrieved"); + opm |= lpm; + } else if (rc == -EAGAIN) { + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", + "path verification: device is stopped," + " try again later"); + epm |= lpm; + } else { + dev_warn(&device->cdev->dev, + "Reading device feature codes failed " + "(rc=%d) for new path %x\n", rc, lpm); + continue; + } + if (verify_fcx_max_data(device, lpm)) { + opm &= ~lpm; + npm &= ~lpm; + ppm &= ~lpm; + continue; + } + + /* + * save conf_data for comparison after + * rebuild_device_uid may have changed + * the original data + */ + memcpy(&path_rcd_buf, data->rcd_buffer, + DASD_ECKD_RCD_DATA_SIZE); + path_private.conf_data = (void *) &path_rcd_buf; + path_private.conf_len = DASD_ECKD_RCD_DATA_SIZE; + if (dasd_eckd_identify_conf_parts(&path_private)) { + path_private.conf_data = NULL; + path_private.conf_len = 0; + continue; + } + + /* + * compare path UID with device UID only if at least + * one valid path is left + * in other case the device UID may have changed and + * the first working path UID will be used as device UID + */ + if (device->path_data.opm && + dasd_eckd_compare_path_uid(device, &path_private)) { + /* + * the comparison was not successful + * rebuild the device UID with at least one + * known path in case a z/VM hyperswap command + * has changed the device + * + * after this compare again + * + * if either the rebuild or the recompare fails + * the path can not be used + */ + if (rebuild_device_uid(device, data) || + dasd_eckd_compare_path_uid( + device, &path_private)) { + uid = &path_private.uid; + if (strlen(uid->vduit) > 0) + snprintf(print_uid, sizeof(print_uid), + "%s.%s.%04x.%02x.%s", + uid->vendor, uid->serial, + uid->ssid, uid->real_unit_addr, + uid->vduit); + else + snprintf(print_uid, sizeof(print_uid), + "%s.%s.%04x.%02x", + uid->vendor, uid->serial, + uid->ssid, + uid->real_unit_addr); + dev_err(&device->cdev->dev, + "The newly added channel path %02X " + "will not be used because it leads " + "to a different device %s\n", + lpm, print_uid); + opm &= ~lpm; + npm &= ~lpm; + ppm &= ~lpm; + continue; + } } + + /* + * There is a small chance that a path is lost again between + * above path verification and the following modification of + * the device opm mask. We could avoid that race here by using + * yet another path mask, but we rather deal with this unlikely + * situation in dasd_start_IO. + */ + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + if (!device->path_data.opm && opm) { + device->path_data.opm = opm; + dasd_generic_path_operational(device); + } else + device->path_data.opm |= opm; + device->path_data.npm |= npm; + device->path_data.ppm |= ppm; + device->path_data.tbvpm |= epm; + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + } + + dasd_put_device(device); + if (data->isglobal) + mutex_unlock(&dasd_path_verification_mutex); + else + kfree(data); +} + +static int dasd_eckd_verify_path(struct dasd_device *device, __u8 lpm) +{ + struct path_verification_work_data *data; + + data = kmalloc(sizeof(*data), GFP_ATOMIC | GFP_DMA); + if (!data) { + if (mutex_trylock(&dasd_path_verification_mutex)) { + data = path_verification_worker; + data->isglobal = 1; + } else + return -ENOMEM; + } else { + memset(data, 0, sizeof(*data)); + data->isglobal = 0; } + INIT_WORK(&data->worker, do_path_verification_work); + dasd_get_device(device); + data->device = device; + data->tbvpm = lpm; + schedule_work(&data->worker); return 0; } @@ -946,22 +1413,20 @@ static int dasd_eckd_read_features(struct dasd_device *device) struct dasd_eckd_private *private; private = (struct dasd_eckd_private *) device->private; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - 1 /* PSF */ + 1 /* RSSD */ , + memset(&private->features, 0, sizeof(struct dasd_rssd_features)); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */, (sizeof(struct dasd_psf_prssd_data) + sizeof(struct dasd_rssd_features)), device); if (IS_ERR(cqr)) { - DBF_EVENT(DBF_WARNING, "Could not allocate initialization " - "request for device: %s", - dev_name(&device->cdev->dev)); + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", "Could not " + "allocate initialization request"); return PTR_ERR(cqr); } cqr->startdev = device; cqr->memdev = device; cqr->block = NULL; - clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); - cqr->retries = 5; + cqr->retries = 256; cqr->expires = 10 * HZ; /* Prepare for Read Subsystem Data */ @@ -986,7 +1451,7 @@ static int dasd_eckd_read_features(struct dasd_device *device) ccw->count = sizeof(struct dasd_rssd_features); ccw->cda = (__u32)(addr_t) features; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; rc = dasd_sleep_on(cqr); if (rc == 0) { @@ -994,7 +1459,9 @@ static int dasd_eckd_read_features(struct dasd_device *device) features = (struct dasd_rssd_features *) (prssdp + 1); memcpy(&private->features, features, sizeof(struct dasd_rssd_features)); - } + } else + dev_warn(&device->cdev->dev, "Reading device feature codes" + " failed with rc=%d\n", rc); dasd_sfree_request(cqr, cqr->memdev); return rc; } @@ -1010,7 +1477,7 @@ static struct dasd_ccw_req *dasd_eckd_build_psf_ssc(struct dasd_device *device, struct dasd_psf_ssc_data *psf_ssc_data; struct ccw1 *ccw; - cqr = dasd_smalloc_request("ECKD", 1 /* PSF */ , + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ , sizeof(struct dasd_psf_ssc_data), device); @@ -1021,9 +1488,9 @@ static struct dasd_ccw_req *dasd_eckd_build_psf_ssc(struct dasd_device *device, } psf_ssc_data = (struct dasd_psf_ssc_data *)cqr->data; psf_ssc_data->order = PSF_ORDER_SSC; - psf_ssc_data->suborder = 0x40; + psf_ssc_data->suborder = 0xc0; if (enable_pav) { - psf_ssc_data->suborder |= 0x88; + psf_ssc_data->suborder |= 0x08; psf_ssc_data->reserved[0] = 0x88; } ccw = cqr->cpaddr; @@ -1034,8 +1501,9 @@ static struct dasd_ccw_req *dasd_eckd_build_psf_ssc(struct dasd_device *device, cqr->startdev = device; cqr->memdev = device; cqr->block = NULL; + cqr->retries = 256; cqr->expires = 10*HZ; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; return cqr; } @@ -1046,7 +1514,8 @@ static struct dasd_ccw_req *dasd_eckd_build_psf_ssc(struct dasd_device *device, * call might change behaviour of DASD devices. */ static int -dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav) +dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav, + unsigned long flags) { struct dasd_ccw_req *cqr; int rc; @@ -1055,10 +1524,19 @@ dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav) if (IS_ERR(cqr)) return PTR_ERR(cqr); + /* + * set flags e.g. turn on failfast, to prevent blocking + * the calling function should handle failed requests + */ + cqr->flags |= flags; + rc = dasd_sleep_on(cqr); if (!rc) /* trigger CIO to reprobe devices */ css_schedule_reprobe(); + else if (cqr->intrc == -EAGAIN) + rc = -EAGAIN; + dasd_sfree_request(cqr, cqr->memdev); return rc; } @@ -1066,26 +1544,92 @@ dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav) /* * Valide storage server of current device. */ -static int dasd_eckd_validate_server(struct dasd_device *device) +static int dasd_eckd_validate_server(struct dasd_device *device, + unsigned long flags) { int rc; struct dasd_eckd_private *private; int enable_pav; + private = (struct dasd_eckd_private *) device->private; + if (private->uid.type == UA_BASE_PAV_ALIAS || + private->uid.type == UA_HYPER_PAV_ALIAS) + return 0; if (dasd_nopav || MACHINE_IS_VM) enable_pav = 0; else enable_pav = 1; - rc = dasd_eckd_psf_ssc(device, enable_pav); + rc = dasd_eckd_psf_ssc(device, enable_pav, flags); + /* may be requested feature is not available on server, * therefore just report error and go ahead */ + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "PSF-SSC for SSID %04x " + "returned rc=%d", private->uid.ssid, rc); + return rc; +} + +/* + * worker to do a validate server in case of a lost pathgroup + */ +static void dasd_eckd_do_validate_server(struct work_struct *work) +{ + struct dasd_device *device = container_of(work, struct dasd_device, + kick_validate); + unsigned long flags = 0; + + set_bit(DASD_CQR_FLAGS_FAILFAST, &flags); + if (dasd_eckd_validate_server(device, flags) + == -EAGAIN) { + /* schedule worker again if failed */ + schedule_work(&device->kick_validate); + return; + } + + dasd_put_device(device); +} + +static void dasd_eckd_kick_validate_server(struct dasd_device *device) +{ + dasd_get_device(device); + /* exit if device not online or in offline processing */ + if (test_bit(DASD_FLAG_OFFLINE, &device->flags) || + device->state < DASD_STATE_ONLINE) { + dasd_put_device(device); + return; + } + /* queue call to do_validate_server to the kernel event daemon. */ + schedule_work(&device->kick_validate); +} + +static u32 get_fcx_max_data(struct dasd_device *device) +{ +#if defined(CONFIG_64BIT) + int tpm, mdc; + int fcx_in_css, fcx_in_gneq, fcx_in_features; + struct dasd_eckd_private *private; + + if (dasd_nofcx) + return 0; + /* is transport mode supported? */ private = (struct dasd_eckd_private *) device->private; - DBF_EVENT(DBF_WARNING, "PSF-SSC on storage subsystem %s.%s.%04x " - "returned rc=%d for device: %s", - private->uid.vendor, private->uid.serial, - private->uid.ssid, rc, dev_name(&device->cdev->dev)); - /* RE-Read Configuration Data */ - return dasd_eckd_read_conf(device); + fcx_in_css = css_general_characteristics.fcx; + fcx_in_gneq = private->gneq->reserved2[7] & 0x04; + fcx_in_features = private->features.feature[40] & 0x80; + tpm = fcx_in_css && fcx_in_gneq && fcx_in_features; + + if (!tpm) + return 0; + + mdc = ccw_device_get_mdc(device->cdev, 0); + if (mdc < 0) { + dev_warn(&device->cdev->dev, "Detecting the maximum supported" + " data size for zHPF requests failed\n"); + return 0; + } else + return mdc * FCX_MAX_DATA_FACTOR; +#else + return 0; +#endif } /* @@ -1097,20 +1641,35 @@ dasd_eckd_check_characteristics(struct dasd_device *device) { struct dasd_eckd_private *private; struct dasd_block *block; - void *rdc_data; - int is_known, rc; + struct dasd_uid temp_uid; + int rc, i; + int readonly; + unsigned long value; + + /* setup work queue for validate server*/ + INIT_WORK(&device->kick_validate, dasd_eckd_do_validate_server); + if (!ccw_device_is_pathgroup(device->cdev)) { + dev_warn(&device->cdev->dev, + "A channel path group could not be established\n"); + return -EIO; + } + if (!ccw_device_is_multipath(device->cdev)) { + dev_info(&device->cdev->dev, + "The DASD is not operating in multipath mode\n"); + } private = (struct dasd_eckd_private *) device->private; - if (private == NULL) { - private = kzalloc(sizeof(struct dasd_eckd_private), - GFP_KERNEL | GFP_DMA); - if (private == NULL) { + if (!private) { + private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA); + if (!private) { dev_warn(&device->cdev->dev, "Allocating memory for private DASD data " "failed\n"); return -ENOMEM; } device->private = (void *) private; + } else { + memset(private, 0, sizeof(*private)); } /* Invalidate status of initial analysis. */ private->init_cqr_status = -1; @@ -1123,18 +1682,28 @@ dasd_eckd_check_characteristics(struct dasd_device *device) if (rc) goto out_err1; - /* Generate device unique id and register in devmap */ - rc = dasd_eckd_generate_uid(device, &private->uid); - if (rc) - goto out_err1; - dasd_set_uid(device->cdev, &private->uid); + /* set default timeout */ + device->default_expires = DASD_EXPIRES; + /* set default retry count */ + device->default_retries = DASD_RETRIES; + + if (private->gneq) { + value = 1; + for (i = 0; i < private->gneq->timeout.value; i++) + value = 10 * value; + value = value * private->gneq->timeout.number; + /* do not accept useless values */ + if (value != 0 && value <= DASD_EXPIRES_MAX) + device->default_expires = value; + } - if (private->uid.type == UA_BASE_DEVICE) { + dasd_eckd_get_uid(device, &temp_uid); + if (temp_uid.type == UA_BASE_DEVICE) { block = dasd_alloc_block(); if (IS_ERR(block)) { - DBF_EVENT(DBF_WARNING, "could not allocate dasd " - "block structure for device: %s", - dev_name(&device->cdev->dev)); + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", + "could not allocate dasd " + "block structure"); rc = PTR_ERR(block); goto out_err1; } @@ -1142,50 +1711,61 @@ dasd_eckd_check_characteristics(struct dasd_device *device) block->base = device; } - /* register lcu with alias handling, enable PAV if this is a new lcu */ - is_known = dasd_alias_make_device_known_to_lcu(device); - if (is_known < 0) { - rc = is_known; + /* register lcu with alias handling, enable PAV */ + rc = dasd_alias_make_device_known_to_lcu(device); + if (rc) goto out_err2; - } - if (!is_known) { - /* new lcu found */ - rc = dasd_eckd_validate_server(device); /* will switch pav on */ - if (rc) - goto out_err3; - } - /* Read Feature Codes */ - rc = dasd_eckd_read_features(device); + dasd_eckd_validate_server(device, 0); + + /* device may report different configuration data after LCU setup */ + rc = dasd_eckd_read_conf(device); if (rc) goto out_err3; + /* Read Feature Codes */ + dasd_eckd_read_features(device); + /* Read Device Characteristics */ - rdc_data = (void *) &(private->rdc_data); - memset(rdc_data, 0, sizeof(rdc_data)); - rc = dasd_generic_read_dev_chars(device, "ECKD", &rdc_data, 64); + rc = dasd_generic_read_dev_chars(device, DASD_ECKD_MAGIC, + &private->rdc_data, 64); if (rc) { - DBF_EVENT(DBF_WARNING, - "Read device characteristics failed, rc=%d for " - "device: %s", rc, dev_name(&device->cdev->dev)); + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, + "Read device characteristic failed, rc=%d", rc); goto out_err3; } - /* find the vaild cylinder size */ + + if ((device->features & DASD_FEATURE_USERAW) && + !(private->rdc_data.facilities.RT_in_LR)) { + dev_err(&device->cdev->dev, "The storage server does not " + "support raw-track access\n"); + rc = -EINVAL; + goto out_err3; + } + + /* find the valid cylinder size */ if (private->rdc_data.no_cyl == LV_COMPAT_CYL && private->rdc_data.long_no_cyl) private->real_cyl = private->rdc_data.long_no_cyl; else private->real_cyl = private->rdc_data.no_cyl; + private->fcx_max_data = get_fcx_max_data(device); + + readonly = dasd_device_is_ro(device); + if (readonly) + set_bit(DASD_FLAG_DEVICE_RO, &device->flags); + dev_info(&device->cdev->dev, "New DASD %04X/%02X (CU %04X/%02X) " - "with %d cylinders, %d heads, %d sectors\n", + "with %d cylinders, %d heads, %d sectors%s\n", private->rdc_data.dev_type, private->rdc_data.dev_model, private->rdc_data.cu_type, private->rdc_data.cu_model.model, - private->real_cyl, + private->real_cyl, private->rdc_data.trk_per_cyl, - private->rdc_data.sec_per_trk); + private->rdc_data.sec_per_trk, + readonly ? ", read-only device" : ""); return 0; out_err3: @@ -1230,8 +1810,7 @@ dasd_eckd_analysis_ccw(struct dasd_device *device) cplength = 8; datasize = sizeof(struct DE_eckd_data) + 2*sizeof(struct LO_eckd_data); - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - cplength, datasize, device); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, device); if (IS_ERR(cqr)) return cqr; ccw = cqr->cpaddr; @@ -1269,12 +1848,29 @@ dasd_eckd_analysis_ccw(struct dasd_device *device) cqr->block = NULL; cqr->startdev = device; cqr->memdev = device; - cqr->retries = 0; - cqr->buildclk = get_clock(); + cqr->retries = 255; + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; return cqr; } +/* differentiate between 'no record found' and any other error */ +static int dasd_eckd_analysis_evaluation(struct dasd_ccw_req *init_cqr) +{ + char *sense; + if (init_cqr->status == DASD_CQR_DONE) + return INIT_CQR_OK; + else if (init_cqr->status == DASD_CQR_NEED_ERP || + init_cqr->status == DASD_CQR_FAILED) { + sense = dasd_get_sense(&init_cqr->irb); + if (sense && (sense[1] & SNS1_NO_REC_FOUND)) + return INIT_CQR_UNFORMATTED; + else + return INIT_CQR_ERROR; + } else + return INIT_CQR_ERROR; +} + /* * This is the callback function for the init_analysis cqr. It saves * the status of the initial analysis ccw before it frees it and kicks @@ -1282,53 +1878,74 @@ dasd_eckd_analysis_ccw(struct dasd_device *device) * dasd_eckd_do_analysis again (if the devices has not been marked * for deletion in the meantime). */ -static void -dasd_eckd_analysis_callback(struct dasd_ccw_req *init_cqr, void *data) +static void dasd_eckd_analysis_callback(struct dasd_ccw_req *init_cqr, + void *data) { struct dasd_eckd_private *private; struct dasd_device *device; device = init_cqr->startdev; private = (struct dasd_eckd_private *) device->private; - private->init_cqr_status = init_cqr->status; + private->init_cqr_status = dasd_eckd_analysis_evaluation(init_cqr); dasd_sfree_request(init_cqr, device); dasd_kick_device(device); } -static int -dasd_eckd_start_analysis(struct dasd_block *block) +static int dasd_eckd_start_analysis(struct dasd_block *block) { - struct dasd_eckd_private *private; struct dasd_ccw_req *init_cqr; - private = (struct dasd_eckd_private *) block->base->private; init_cqr = dasd_eckd_analysis_ccw(block->base); if (IS_ERR(init_cqr)) return PTR_ERR(init_cqr); init_cqr->callback = dasd_eckd_analysis_callback; init_cqr->callback_data = NULL; init_cqr->expires = 5*HZ; + /* first try without ERP, so we can later handle unformatted + * devices as special case + */ + clear_bit(DASD_CQR_FLAGS_USE_ERP, &init_cqr->flags); + init_cqr->retries = 0; dasd_add_request_head(init_cqr); return -EAGAIN; } -static int -dasd_eckd_end_analysis(struct dasd_block *block) +static int dasd_eckd_end_analysis(struct dasd_block *block) { struct dasd_device *device; struct dasd_eckd_private *private; struct eckd_count *count_area; unsigned int sb, blk_per_trk; int status, i; + struct dasd_ccw_req *init_cqr; device = block->base; private = (struct dasd_eckd_private *) device->private; status = private->init_cqr_status; private->init_cqr_status = -1; - if (status != DASD_CQR_DONE) { - dev_warn(&device->cdev->dev, - "The DASD is not formatted\n"); + if (status == INIT_CQR_ERROR) { + /* try again, this time with full ERP */ + init_cqr = dasd_eckd_analysis_ccw(device); + dasd_sleep_on(init_cqr); + status = dasd_eckd_analysis_evaluation(init_cqr); + dasd_sfree_request(init_cqr, device); + } + + if (device->features & DASD_FEATURE_USERAW) { + block->bp_block = DASD_RAW_BLOCKSIZE; + blk_per_trk = DASD_RAW_BLOCK_PER_TRACK; + block->s2b_shift = 3; + goto raw; + } + + if (status == INIT_CQR_UNFORMATTED) { + dev_warn(&device->cdev->dev, "The DASD is not formatted\n"); return -EMEDIUMTYPE; + } else if (status == INIT_CQR_ERROR) { + dev_err(&device->cdev->dev, + "Detecting the DASD disk layout failed because " + "of an I/O error\n"); + return -EIO; } private->uses_cdl = 1; @@ -1336,7 +1953,10 @@ dasd_eckd_end_analysis(struct dasd_block *block) count_area = NULL; for (i = 0; i < 3; i++) { if (private->count_area[i].kl != 4 || - private->count_area[i].dl != dasd_eckd_cdl_reclen(i) - 4) { + private->count_area[i].dl != dasd_eckd_cdl_reclen(i) - 4 || + private->count_area[i].cyl != 0 || + private->count_area[i].head != count_area_head[i] || + private->count_area[i].record != count_area_rec[i]) { private->uses_cdl = 0; break; } @@ -1348,7 +1968,10 @@ dasd_eckd_end_analysis(struct dasd_block *block) for (i = 0; i < 5; i++) { if ((private->count_area[i].kl != 0) || (private->count_area[i].dl != - private->count_area[0].dl)) + private->count_area[0].dl) || + private->count_area[i].cyl != 0 || + private->count_area[i].head != count_area_head[i] || + private->count_area[i].record != count_area_rec[i]) break; } if (i == 5) @@ -1358,6 +1981,7 @@ dasd_eckd_end_analysis(struct dasd_block *block) dev_warn(&device->cdev->dev, "Track 0 has no records following the VTOC\n"); } + if (count_area != NULL && count_area->kl == 0) { /* we found notthing violating our disk layout */ if (dasd_check_blocksize(count_area->dl) == 0) @@ -1373,6 +1997,8 @@ dasd_eckd_end_analysis(struct dasd_block *block) block->s2b_shift++; blk_per_trk = recs_per_track(&private->rdc_data, 0, block->bp_block); + +raw: block->blocks = (private->real_cyl * private->rdc_data.trk_per_cyl * blk_per_trk); @@ -1401,13 +2027,20 @@ static int dasd_eckd_do_analysis(struct dasd_block *block) return dasd_eckd_end_analysis(block); } -static int dasd_eckd_ready_to_online(struct dasd_device *device) +static int dasd_eckd_basic_to_ready(struct dasd_device *device) { return dasd_alias_add_device(device); }; static int dasd_eckd_online_to_ready(struct dasd_device *device) { + cancel_work_sync(&device->reload_device); + cancel_work_sync(&device->kick_validate); + return 0; +}; + +static int dasd_eckd_ready_to_basic(struct dasd_device *device) +{ return dasd_alias_remove_device(device); }; @@ -1427,45 +2060,35 @@ dasd_eckd_fill_geometry(struct dasd_block *block, struct hd_geometry *geo) } static struct dasd_ccw_req * -dasd_eckd_format_device(struct dasd_device * device, - struct format_data_t * fdata) +dasd_eckd_build_format(struct dasd_device *base, + struct format_data_t *fdata) { - struct dasd_eckd_private *private; + struct dasd_eckd_private *base_priv; + struct dasd_eckd_private *start_priv; + struct dasd_device *startdev; struct dasd_ccw_req *fcp; struct eckd_count *ect; + struct ch_t address; struct ccw1 *ccw; void *data; int rpt; - struct ch_t address; int cplength, datasize; - int i; + int i, j; int intensity = 0; int r0_perm; + int nr_tracks; + int use_prefix; - private = (struct dasd_eckd_private *) device->private; - rpt = recs_per_track(&private->rdc_data, 0, fdata->blksize); - set_ch_t(&address, - fdata->start_unit / private->rdc_data.trk_per_cyl, - fdata->start_unit % private->rdc_data.trk_per_cyl); + startdev = dasd_alias_get_start_dev(base); + if (!startdev) + startdev = base; - /* Sanity checks. */ - if (fdata->start_unit >= - (private->real_cyl * private->rdc_data.trk_per_cyl)) { - dev_warn(&device->cdev->dev, "Start track number %d used in " - "formatting is too big\n", fdata->start_unit); - return ERR_PTR(-EINVAL); - } - if (fdata->start_unit > fdata->stop_unit) { - dev_warn(&device->cdev->dev, "Start track %d used in " - "formatting exceeds end track\n", fdata->start_unit); - return ERR_PTR(-EINVAL); - } - if (dasd_check_blocksize(fdata->blksize) != 0) { - dev_warn(&device->cdev->dev, - "The DASD cannot be formatted with block size %d\n", - fdata->blksize); - return ERR_PTR(-EINVAL); - } + start_priv = (struct dasd_eckd_private *) startdev->private; + base_priv = (struct dasd_eckd_private *) base->private; + + rpt = recs_per_track(&base_priv->rdc_data, 0, fdata->blksize); + + nr_tracks = fdata->stop_unit - fdata->start_unit + 1; /* * fdata->intensity is a bit string that tells us what to do: @@ -1483,157 +2106,336 @@ dasd_eckd_format_device(struct dasd_device * device, r0_perm = 1; intensity = fdata->intensity; } + + use_prefix = base_priv->features.feature[8] & 0x01; + switch (intensity) { case 0x00: /* Normal format */ case 0x08: /* Normal format, use cdl. */ - cplength = 2 + rpt; - datasize = sizeof(struct DE_eckd_data) + - sizeof(struct LO_eckd_data) + - rpt * sizeof(struct eckd_count); + cplength = 2 + (rpt*nr_tracks); + if (use_prefix) + datasize = sizeof(struct PFX_eckd_data) + + sizeof(struct LO_eckd_data) + + rpt * nr_tracks * sizeof(struct eckd_count); + else + datasize = sizeof(struct DE_eckd_data) + + sizeof(struct LO_eckd_data) + + rpt * nr_tracks * sizeof(struct eckd_count); break; case 0x01: /* Write record zero and format track. */ case 0x09: /* Write record zero and format track, use cdl. */ - cplength = 3 + rpt; - datasize = sizeof(struct DE_eckd_data) + - sizeof(struct LO_eckd_data) + - sizeof(struct eckd_count) + - rpt * sizeof(struct eckd_count); + cplength = 2 + rpt * nr_tracks; + if (use_prefix) + datasize = sizeof(struct PFX_eckd_data) + + sizeof(struct LO_eckd_data) + + sizeof(struct eckd_count) + + rpt * nr_tracks * sizeof(struct eckd_count); + else + datasize = sizeof(struct DE_eckd_data) + + sizeof(struct LO_eckd_data) + + sizeof(struct eckd_count) + + rpt * nr_tracks * sizeof(struct eckd_count); break; case 0x04: /* Invalidate track. */ case 0x0c: /* Invalidate track, use cdl. */ cplength = 3; - datasize = sizeof(struct DE_eckd_data) + - sizeof(struct LO_eckd_data) + - sizeof(struct eckd_count); + if (use_prefix) + datasize = sizeof(struct PFX_eckd_data) + + sizeof(struct LO_eckd_data) + + sizeof(struct eckd_count); + else + datasize = sizeof(struct DE_eckd_data) + + sizeof(struct LO_eckd_data) + + sizeof(struct eckd_count); break; default: - dev_warn(&device->cdev->dev, "An I/O control call used " - "incorrect flags 0x%x\n", fdata->intensity); + dev_warn(&startdev->cdev->dev, + "An I/O control call used incorrect flags 0x%x\n", + fdata->intensity); return ERR_PTR(-EINVAL); } /* Allocate the format ccw request. */ - fcp = dasd_smalloc_request(dasd_eckd_discipline.name, - cplength, datasize, device); + fcp = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, + datasize, startdev); if (IS_ERR(fcp)) return fcp; + start_priv->count++; data = fcp->data; ccw = fcp->cpaddr; switch (intensity & ~0x08) { case 0x00: /* Normal format. */ - define_extent(ccw++, (struct DE_eckd_data *) data, - fdata->start_unit, fdata->start_unit, - DASD_ECKD_CCW_WRITE_CKD, device); - /* grant subsystem permission to format R0 */ - if (r0_perm) - ((struct DE_eckd_data *)data)->ga_extended |= 0x04; - data += sizeof(struct DE_eckd_data); + if (use_prefix) { + prefix(ccw++, (struct PFX_eckd_data *) data, + fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_WRITE_CKD, base, startdev); + /* grant subsystem permission to format R0 */ + if (r0_perm) + ((struct PFX_eckd_data *)data) + ->define_extent.ga_extended |= 0x04; + data += sizeof(struct PFX_eckd_data); + } else { + define_extent(ccw++, (struct DE_eckd_data *) data, + fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_WRITE_CKD, startdev); + /* grant subsystem permission to format R0 */ + if (r0_perm) + ((struct DE_eckd_data *) data) + ->ga_extended |= 0x04; + data += sizeof(struct DE_eckd_data); + } ccw[-1].flags |= CCW_FLAG_CC; locate_record(ccw++, (struct LO_eckd_data *) data, - fdata->start_unit, 0, rpt, - DASD_ECKD_CCW_WRITE_CKD, device, + fdata->start_unit, 0, rpt*nr_tracks, + DASD_ECKD_CCW_WRITE_CKD, base, fdata->blksize); data += sizeof(struct LO_eckd_data); break; case 0x01: /* Write record zero + format track. */ - define_extent(ccw++, (struct DE_eckd_data *) data, - fdata->start_unit, fdata->start_unit, - DASD_ECKD_CCW_WRITE_RECORD_ZERO, - device); - data += sizeof(struct DE_eckd_data); + if (use_prefix) { + prefix(ccw++, (struct PFX_eckd_data *) data, + fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_WRITE_RECORD_ZERO, + base, startdev); + data += sizeof(struct PFX_eckd_data); + } else { + define_extent(ccw++, (struct DE_eckd_data *) data, + fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_WRITE_RECORD_ZERO, startdev); + data += sizeof(struct DE_eckd_data); + } ccw[-1].flags |= CCW_FLAG_CC; locate_record(ccw++, (struct LO_eckd_data *) data, - fdata->start_unit, 0, rpt + 1, - DASD_ECKD_CCW_WRITE_RECORD_ZERO, device, - device->block->bp_block); + fdata->start_unit, 0, rpt * nr_tracks + 1, + DASD_ECKD_CCW_WRITE_RECORD_ZERO, base, + base->block->bp_block); data += sizeof(struct LO_eckd_data); break; case 0x04: /* Invalidate track. */ - define_extent(ccw++, (struct DE_eckd_data *) data, - fdata->start_unit, fdata->start_unit, - DASD_ECKD_CCW_WRITE_CKD, device); - data += sizeof(struct DE_eckd_data); + if (use_prefix) { + prefix(ccw++, (struct PFX_eckd_data *) data, + fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_WRITE_CKD, base, startdev); + data += sizeof(struct PFX_eckd_data); + } else { + define_extent(ccw++, (struct DE_eckd_data *) data, + fdata->start_unit, fdata->stop_unit, + DASD_ECKD_CCW_WRITE_CKD, startdev); + data += sizeof(struct DE_eckd_data); + } ccw[-1].flags |= CCW_FLAG_CC; locate_record(ccw++, (struct LO_eckd_data *) data, fdata->start_unit, 0, 1, - DASD_ECKD_CCW_WRITE_CKD, device, 8); + DASD_ECKD_CCW_WRITE_CKD, base, 8); data += sizeof(struct LO_eckd_data); break; } - if (intensity & 0x01) { /* write record zero */ - ect = (struct eckd_count *) data; - data += sizeof(struct eckd_count); - ect->cyl = address.cyl; - ect->head = address.head; - ect->record = 0; - ect->kl = 0; - ect->dl = 8; - ccw[-1].flags |= CCW_FLAG_CC; - ccw->cmd_code = DASD_ECKD_CCW_WRITE_RECORD_ZERO; - ccw->flags = CCW_FLAG_SLI; - ccw->count = 8; - ccw->cda = (__u32)(addr_t) ect; - ccw++; - } - if ((intensity & ~0x08) & 0x04) { /* erase track */ - ect = (struct eckd_count *) data; - data += sizeof(struct eckd_count); - ect->cyl = address.cyl; - ect->head = address.head; - ect->record = 1; - ect->kl = 0; - ect->dl = 0; - ccw[-1].flags |= CCW_FLAG_CC; - ccw->cmd_code = DASD_ECKD_CCW_WRITE_CKD; - ccw->flags = CCW_FLAG_SLI; - ccw->count = 8; - ccw->cda = (__u32)(addr_t) ect; - } else { /* write remaining records */ - for (i = 0; i < rpt; i++) { + + for (j = 0; j < nr_tracks; j++) { + /* calculate cylinder and head for the current track */ + set_ch_t(&address, + (fdata->start_unit + j) / + base_priv->rdc_data.trk_per_cyl, + (fdata->start_unit + j) % + base_priv->rdc_data.trk_per_cyl); + if (intensity & 0x01) { /* write record zero */ ect = (struct eckd_count *) data; data += sizeof(struct eckd_count); ect->cyl = address.cyl; ect->head = address.head; - ect->record = i + 1; + ect->record = 0; ect->kl = 0; - ect->dl = fdata->blksize; - /* Check for special tracks 0-1 when formatting CDL */ - if ((intensity & 0x08) && - fdata->start_unit == 0) { - if (i < 3) { - ect->kl = 4; - ect->dl = sizes_trk0[i] - 4; - } - } - if ((intensity & 0x08) && - fdata->start_unit == 1) { - ect->kl = 44; - ect->dl = LABEL_SIZE - 44; - } + ect->dl = 8; ccw[-1].flags |= CCW_FLAG_CC; - ccw->cmd_code = DASD_ECKD_CCW_WRITE_CKD; + ccw->cmd_code = DASD_ECKD_CCW_WRITE_RECORD_ZERO; ccw->flags = CCW_FLAG_SLI; ccw->count = 8; ccw->cda = (__u32)(addr_t) ect; ccw++; } + if ((intensity & ~0x08) & 0x04) { /* erase track */ + ect = (struct eckd_count *) data; + data += sizeof(struct eckd_count); + ect->cyl = address.cyl; + ect->head = address.head; + ect->record = 1; + ect->kl = 0; + ect->dl = 0; + ccw[-1].flags |= CCW_FLAG_CC; + ccw->cmd_code = DASD_ECKD_CCW_WRITE_CKD; + ccw->flags = CCW_FLAG_SLI; + ccw->count = 8; + ccw->cda = (__u32)(addr_t) ect; + } else { /* write remaining records */ + for (i = 0; i < rpt; i++) { + ect = (struct eckd_count *) data; + data += sizeof(struct eckd_count); + ect->cyl = address.cyl; + ect->head = address.head; + ect->record = i + 1; + ect->kl = 0; + ect->dl = fdata->blksize; + /* + * Check for special tracks 0-1 + * when formatting CDL + */ + if ((intensity & 0x08) && + fdata->start_unit == 0) { + if (i < 3) { + ect->kl = 4; + ect->dl = sizes_trk0[i] - 4; + } + } + if ((intensity & 0x08) && + fdata->start_unit == 1) { + ect->kl = 44; + ect->dl = LABEL_SIZE - 44; + } + ccw[-1].flags |= CCW_FLAG_CC; + if (i != 0 || j == 0) + ccw->cmd_code = + DASD_ECKD_CCW_WRITE_CKD; + else + ccw->cmd_code = + DASD_ECKD_CCW_WRITE_CKD_MT; + ccw->flags = CCW_FLAG_SLI; + ccw->count = 8; + ccw->cda = (__u32)(addr_t) ect; + ccw++; + } + } } - fcp->startdev = device; - fcp->memdev = device; - clear_bit(DASD_CQR_FLAGS_USE_ERP, &fcp->flags); - fcp->retries = 5; /* set retry counter to enable default ERP */ - fcp->buildclk = get_clock(); + + fcp->startdev = startdev; + fcp->memdev = startdev; + fcp->retries = 256; + fcp->expires = startdev->default_expires * HZ; + fcp->buildclk = get_tod_clock(); fcp->status = DASD_CQR_FILLED; + return fcp; } +static int +dasd_eckd_format_device(struct dasd_device *base, + struct format_data_t *fdata) +{ + struct dasd_ccw_req *cqr, *n; + struct dasd_block *block; + struct dasd_eckd_private *private; + struct list_head format_queue; + struct dasd_device *device; + int old_stop, format_step; + int step, rc = 0; + + block = base->block; + private = (struct dasd_eckd_private *) base->private; + + /* Sanity checks. */ + if (fdata->start_unit >= + (private->real_cyl * private->rdc_data.trk_per_cyl)) { + dev_warn(&base->cdev->dev, + "Start track number %u used in formatting is too big\n", + fdata->start_unit); + return -EINVAL; + } + if (fdata->stop_unit >= + (private->real_cyl * private->rdc_data.trk_per_cyl)) { + dev_warn(&base->cdev->dev, + "Stop track number %u used in formatting is too big\n", + fdata->stop_unit); + return -EINVAL; + } + if (fdata->start_unit > fdata->stop_unit) { + dev_warn(&base->cdev->dev, + "Start track %u used in formatting exceeds end track\n", + fdata->start_unit); + return -EINVAL; + } + if (dasd_check_blocksize(fdata->blksize) != 0) { + dev_warn(&base->cdev->dev, + "The DASD cannot be formatted with block size %u\n", + fdata->blksize); + return -EINVAL; + } + + INIT_LIST_HEAD(&format_queue); + old_stop = fdata->stop_unit; + + while (fdata->start_unit <= 1) { + fdata->stop_unit = fdata->start_unit; + cqr = dasd_eckd_build_format(base, fdata); + list_add(&cqr->blocklist, &format_queue); + + fdata->stop_unit = old_stop; + fdata->start_unit++; + + if (fdata->start_unit > fdata->stop_unit) + goto sleep; + } + +retry: + format_step = 255 / recs_per_track(&private->rdc_data, 0, + fdata->blksize); + while (fdata->start_unit <= old_stop) { + step = fdata->stop_unit - fdata->start_unit + 1; + if (step > format_step) + fdata->stop_unit = fdata->start_unit + format_step - 1; + + cqr = dasd_eckd_build_format(base, fdata); + if (IS_ERR(cqr)) { + if (PTR_ERR(cqr) == -ENOMEM) { + /* + * not enough memory available + * go to out and start requests + * retry after first requests were finished + */ + fdata->stop_unit = old_stop; + goto sleep; + } else + return PTR_ERR(cqr); + } + list_add(&cqr->blocklist, &format_queue); + + fdata->start_unit = fdata->stop_unit + 1; + fdata->stop_unit = old_stop; + } + +sleep: + dasd_sleep_on_queue(&format_queue); + + list_for_each_entry_safe(cqr, n, &format_queue, blocklist) { + device = cqr->startdev; + private = (struct dasd_eckd_private *) device->private; + if (cqr->status == DASD_CQR_FAILED) + rc = -EIO; + list_del_init(&cqr->blocklist); + dasd_sfree_request(cqr, device); + private->count--; + } + + /* + * in case of ENOMEM we need to retry after + * first requests are finished + */ + if (fdata->start_unit <= fdata->stop_unit) + goto retry; + + return rc; +} + static void dasd_eckd_handle_terminated_request(struct dasd_ccw_req *cqr) { + if (cqr->retries < 0) { + cqr->status = DASD_CQR_FAILED; + return; + } cqr->status = DASD_CQR_FILLED; if (cqr->block && (cqr->startdev != cqr->block->base)) { dasd_eckd_reset_ccw_to_base_io(cqr); cqr->startdev = cqr->block->base; + cqr->lpm = cqr->block->base->path_data.opm; } }; @@ -1662,61 +2464,73 @@ dasd_eckd_erp_postaction(struct dasd_ccw_req * cqr) return dasd_default_erp_postaction; } - -static void dasd_eckd_handle_unsolicited_interrupt(struct dasd_device *device, - struct irb *irb) +static void dasd_eckd_check_for_device_change(struct dasd_device *device, + struct dasd_ccw_req *cqr, + struct irb *irb) { char mask; char *sense = NULL; + struct dasd_eckd_private *private; + private = (struct dasd_eckd_private *) device->private; /* first of all check for state change pending interrupt */ mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP; if ((scsw_dstat(&irb->scsw) & mask) == mask) { + /* + * for alias only, not in offline processing + * and only if not suspended + */ + if (!device->block && private->lcu && + device->state == DASD_STATE_ONLINE && + !test_bit(DASD_FLAG_OFFLINE, &device->flags) && + !test_bit(DASD_FLAG_SUSPENDED, &device->flags)) { + /* + * the state change could be caused by an alias + * reassignment remove device from alias handling + * to prevent new requests from being scheduled on + * the wrong alias device + */ + dasd_alias_remove_device(device); + + /* schedule worker to reload device */ + dasd_reload_device(device); + } dasd_generic_handle_state_change(device); return; } + sense = dasd_get_sense(irb); + if (!sense) + return; + /* summary unit check */ - if ((scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK) && - (irb->ecw[7] == 0x0D)) { + if ((sense[27] & DASD_SENSE_BIT_0) && (sense[7] == 0x0D) && + (scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK)) { dasd_alias_handle_summary_unit_check(device, irb); return; } - sense = dasd_get_sense(irb); /* service information message SIM */ - if (sense && !(sense[27] & DASD_SENSE_BIT_0) && + if (!cqr && !(sense[27] & DASD_SENSE_BIT_0) && ((sense[6] & DASD_SIM_SENSE) == DASD_SIM_SENSE)) { dasd_3990_erp_handle_sim(device, sense); - dasd_schedule_device_bh(device); return; } - if ((scsw_cc(&irb->scsw) == 1) && - (scsw_fctl(&irb->scsw) & SCSW_FCTL_START_FUNC) && - (scsw_actl(&irb->scsw) & SCSW_ACTL_START_PEND) && - (scsw_stctl(&irb->scsw) & SCSW_STCTL_STATUS_PEND)) { - /* fake irb do nothing, they are handled elsewhere */ - dasd_schedule_device_bh(device); - return; - } - - if (!sense) { - /* just report other unsolicited interrupts */ - DBF_DEV_EVENT(DBF_ERR, device, "%s", - "unsolicited interrupt received"); - } else { - DBF_DEV_EVENT(DBF_ERR, device, "%s", - "unsolicited interrupt received " - "(sense available)"); - device->discipline->dump_sense_dbf(device, NULL, irb, - "unsolicited"); + /* loss of device reservation is handled via base devices only + * as alias devices may be used with several bases + */ + if (device->block && (sense[27] & DASD_SENSE_BIT_0) && + (sense[7] == 0x3F) && + (scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK) && + test_bit(DASD_FLAG_IS_RESERVED, &device->flags)) { + if (device->features & DASD_FEATURE_FAILONSLCK) + set_bit(DASD_FLAG_LOCK_STOLEN, &device->flags); + clear_bit(DASD_FLAG_IS_RESERVED, &device->flags); + dev_err(&device->cdev->dev, + "The device reservation was lost\n"); } - - dasd_schedule_device_bh(device); - return; -}; - +} static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( struct dasd_device *startdev, @@ -1737,7 +2551,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( struct dasd_ccw_req *cqr; struct ccw1 *ccw; struct req_iterator iter; - struct bio_vec *bv; + struct bio_vec bv; char *dst; unsigned int off; int count, cidaw, cplength, datasize; @@ -1759,13 +2573,13 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( count = 0; cidaw = 0; rq_for_each_segment(bv, req, iter) { - if (bv->bv_len & (blksize - 1)) + if (bv.bv_len & (blksize - 1)) /* Eckd can only do full blocks. */ return ERR_PTR(-EINVAL); - count += bv->bv_len >> (block->s2b_shift + 9); + count += bv.bv_len >> (block->s2b_shift + 9); #if defined(CONFIG_64BIT) - if (idal_is_needed (page_address(bv->bv_page), bv->bv_len)) - cidaw += bv->bv_len >> (block->s2b_shift + 9); + if (idal_is_needed (page_address(bv.bv_page), bv.bv_len)) + cidaw += bv.bv_len >> (block->s2b_shift + 9); #endif } /* Paranoia. */ @@ -1797,8 +2611,8 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( datasize += count*sizeof(struct LO_eckd_data); } /* Allocate the ccw request. */ - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - cplength, datasize, startdev); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, + startdev); if (IS_ERR(cqr)) return cqr; ccw = cqr->cpaddr; @@ -1816,7 +2630,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( sizeof(struct PFX_eckd_data)); } else { if (define_extent(ccw++, cqr->data, first_trk, - last_trk, cmd, startdev) == -EAGAIN) { + last_trk, cmd, basedev) == -EAGAIN) { /* Clock not in sync and XRC is enabled. * Try again later. */ @@ -1836,16 +2650,16 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( last_rec - recid + 1, cmd, basedev, blksize); } rq_for_each_segment(bv, req, iter) { - dst = page_address(bv->bv_page) + bv->bv_offset; + dst = page_address(bv.bv_page) + bv.bv_offset; if (dasd_page_cache) { char *copy = kmem_cache_alloc(dasd_page_cache, GFP_DMA | __GFP_NOWARN); if (copy && rq_data_dir(req) == WRITE) - memcpy(copy + bv->bv_offset, dst, bv->bv_len); + memcpy(copy + bv.bv_offset, dst, bv.bv_len); if (copy) - dst = copy + bv->bv_offset; + dst = copy + bv.bv_offset; } - for (off = 0; off < bv->bv_len; off += blksize) { + for (off = 0; off < bv.bv_len; off += blksize) { sector_t trkid = recid; unsigned int recoffs = sector_div(trkid, blk_per_trk); rcmd = cmd; @@ -1896,10 +2710,10 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( cqr->startdev = startdev; cqr->memdev = startdev; cqr->block = block; - cqr->expires = 5 * 60 * HZ; /* 5 minutes */ - cqr->lpm = private->path_data.ppm; - cqr->retries = 256; - cqr->buildclk = get_clock(); + cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */ + cqr->lpm = startdev->path_data.ppm; + cqr->retries = startdev->default_retries; + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; return cqr; } @@ -1917,12 +2731,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( unsigned int blk_per_trk, unsigned int blksize) { - struct dasd_eckd_private *private; unsigned long *idaws; struct dasd_ccw_req *cqr; struct ccw1 *ccw; struct req_iterator iter; - struct bio_vec *bv; + struct bio_vec bv; char *dst, *idaw_dst; unsigned int cidaw, cplength, datasize; unsigned int tlf; @@ -1936,7 +2749,6 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( unsigned int recoffs; basedev = block->base; - private = (struct dasd_eckd_private *) basedev->private; if (rq_data_dir(req) == READ) cmd = DASD_ECKD_CCW_READ_TRACK_DATA; else if (rq_data_dir(req) == WRITE) @@ -1962,8 +2774,8 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( cidaw * sizeof(unsigned long long); /* Allocate the ccw request. */ - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - cplength, datasize, startdev); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, + startdev); if (IS_ERR(cqr)) return cqr; ccw = cqr->cpaddr; @@ -1998,11 +2810,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( new_track = 1; end_idaw = 0; len_to_track_end = 0; - idaw_dst = 0; + idaw_dst = NULL; idaw_len = 0; rq_for_each_segment(bv, req, iter) { - dst = page_address(bv->bv_page) + bv->bv_offset; - seg_len = bv->bv_len; + dst = page_address(bv.bv_page) + bv.bv_offset; + seg_len = bv.bv_len; while (seg_len) { if (new_track) { trkid = recid; @@ -2019,15 +2831,23 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( ccw++; recid += count; new_track = 0; + /* first idaw for a ccw may start anywhere */ + if (!idaw_dst) + idaw_dst = dst; } - /* If we start a new idaw, everything is fine and the - * start of the new idaw is the start of this segment. + /* If we start a new idaw, we must make sure that it + * starts on an IDA_BLOCK_SIZE boundary. * If we continue an idaw, we must make sure that the * current segment begins where the so far accumulated * idaw ends */ - if (!idaw_dst) - idaw_dst = dst; + if (!idaw_dst) { + if (__pa(dst) & (IDA_BLOCK_SIZE-1)) { + dasd_sfree_request(cqr, startdev); + return ERR_PTR(-ERANGE); + } else + idaw_dst = dst; + } if ((idaw_dst + idaw_len) != dst) { dasd_sfree_request(cqr, startdev); return ERR_PTR(-ERANGE); @@ -2052,7 +2872,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( if (end_idaw) { idaws = idal_create_words(idaws, idaw_dst, idaw_len); - idaw_dst = 0; + idaw_dst = NULL; idaw_len = 0; end_idaw = 0; } @@ -2065,10 +2885,10 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( cqr->startdev = startdev; cqr->memdev = startdev; cqr->block = block; - cqr->expires = 5 * 60 * HZ; /* 5 minutes */ - cqr->lpm = private->path_data.ppm; - cqr->retries = 256; - cqr->buildclk = get_clock(); + cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */ + cqr->lpm = startdev->path_data.ppm; + cqr->retries = startdev->default_retries; + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; return cqr; } @@ -2201,8 +3021,7 @@ static int prepare_itcw(struct itcw *itcw, dcw = itcw_add_dcw(itcw, pfx_cmd, 0, &pfxdata, sizeof(pfxdata), total_data_size); - - return rc; + return PTR_RET(dcw); } static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( @@ -2218,10 +3037,9 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( unsigned int blk_per_trk, unsigned int blksize) { - struct dasd_eckd_private *private; struct dasd_ccw_req *cqr; struct req_iterator iter; - struct bio_vec *bv; + struct bio_vec bv; char *dst; unsigned int trkcount, ctidaw; unsigned char cmd; @@ -2231,9 +3049,15 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( struct tidaw *last_tidaw = NULL; int itcw_op; size_t itcw_size; + u8 tidaw_flags; + unsigned int seg_len, part_len, len_to_track_end; + unsigned char new_track; + sector_t recid, trkid; + unsigned int offs; + unsigned int count, count_to_trk_end; + int ret; basedev = block->base; - private = (struct dasd_eckd_private *) basedev->private; if (rq_data_dir(req) == READ) { cmd = DASD_ECKD_CCW_READ_TRACK_DATA; itcw_op = ITCW_OP_READ; @@ -2246,29 +3070,23 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( /* trackbased I/O needs address all memory via TIDAWs, * not just for 64 bit addresses. This allows us to map * each segment directly to one tidaw. + * In the case of write requests, additional tidaws may + * be needed when a segment crosses a track boundary. */ trkcount = last_trk - first_trk + 1; ctidaw = 0; rq_for_each_segment(bv, req, iter) { ++ctidaw; } + if (rq_data_dir(req) == WRITE) + ctidaw += (last_trk - first_trk); /* Allocate the ccw request. */ itcw_size = itcw_calc_size(0, ctidaw, 0); - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - 0, itcw_size, startdev); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev); if (IS_ERR(cqr)) return cqr; - cqr->cpmode = 1; - cqr->startdev = startdev; - cqr->memdev = startdev; - cqr->block = block; - cqr->expires = 100*HZ; - cqr->buildclk = get_clock(); - cqr->status = DASD_CQR_FILLED; - cqr->retries = 10; - /* transfer length factor: how many bytes to read from the last track */ if (first_trk == last_trk) tlf = last_offs - first_offs + 1; @@ -2277,8 +3095,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( tlf *= blksize; itcw = itcw_init(cqr->data, itcw_size, itcw_op, 0, ctidaw, 0); + if (IS_ERR(itcw)) { + ret = -EINVAL; + goto out_error; + } cqr->cpaddr = itcw_get_tcw(itcw); - if (prepare_itcw(itcw, first_trk, last_trk, cmd, basedev, startdev, first_offs + 1, @@ -2288,55 +3109,101 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( /* Clock not in sync and XRC is enabled. * Try again later. */ - dasd_sfree_request(cqr, startdev); - return ERR_PTR(-EAGAIN); + ret = -EAGAIN; + goto out_error; } - + len_to_track_end = 0; /* * A tidaw can address 4k of memory, but must not cross page boundaries * We can let the block layer handle this by setting * blk_queue_segment_boundary to page boundaries and * blk_max_segment_size to page size when setting up the request queue. + * For write requests, a TIDAW must not cross track boundaries, because + * we have to set the CBC flag on the last tidaw for each track. */ - rq_for_each_segment(bv, req, iter) { - dst = page_address(bv->bv_page) + bv->bv_offset; - last_tidaw = itcw_add_tidaw(itcw, 0x00, dst, bv->bv_len); - if (IS_ERR(last_tidaw)) - return (struct dasd_ccw_req *)last_tidaw; + if (rq_data_dir(req) == WRITE) { + new_track = 1; + recid = first_rec; + rq_for_each_segment(bv, req, iter) { + dst = page_address(bv.bv_page) + bv.bv_offset; + seg_len = bv.bv_len; + while (seg_len) { + if (new_track) { + trkid = recid; + offs = sector_div(trkid, blk_per_trk); + count_to_trk_end = blk_per_trk - offs; + count = min((last_rec - recid + 1), + (sector_t)count_to_trk_end); + len_to_track_end = count * blksize; + recid += count; + new_track = 0; + } + part_len = min(seg_len, len_to_track_end); + seg_len -= part_len; + len_to_track_end -= part_len; + /* We need to end the tidaw at track end */ + if (!len_to_track_end) { + new_track = 1; + tidaw_flags = TIDAW_FLAGS_INSERT_CBC; + } else + tidaw_flags = 0; + last_tidaw = itcw_add_tidaw(itcw, tidaw_flags, + dst, part_len); + if (IS_ERR(last_tidaw)) { + ret = -EINVAL; + goto out_error; + } + dst += part_len; + } + } + } else { + rq_for_each_segment(bv, req, iter) { + dst = page_address(bv.bv_page) + bv.bv_offset; + last_tidaw = itcw_add_tidaw(itcw, 0x00, + dst, bv.bv_len); + if (IS_ERR(last_tidaw)) { + ret = -EINVAL; + goto out_error; + } + } } - - last_tidaw->flags |= 0x80; + last_tidaw->flags |= TIDAW_FLAGS_LAST; + last_tidaw->flags &= ~TIDAW_FLAGS_INSERT_CBC; itcw_finalize(itcw); if (blk_noretry_request(req) || block->base->features & DASD_FEATURE_FAILFAST) set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); + cqr->cpmode = 1; cqr->startdev = startdev; cqr->memdev = startdev; cqr->block = block; - cqr->expires = 5 * 60 * HZ; /* 5 minutes */ - cqr->lpm = private->path_data.ppm; - cqr->retries = 256; - cqr->buildclk = get_clock(); + cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */ + cqr->lpm = startdev->path_data.ppm; + cqr->retries = startdev->default_retries; + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; return cqr; +out_error: + dasd_sfree_request(cqr, startdev); + return ERR_PTR(ret); } static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, struct dasd_block *block, struct request *req) { - int tpm, cmdrtd, cmdwtd; + int cmdrtd, cmdwtd; int use_prefix; - + int fcx_multitrack; struct dasd_eckd_private *private; - int fcx_in_css, fcx_in_gneq, fcx_in_features; struct dasd_device *basedev; sector_t first_rec, last_rec; sector_t first_trk, last_trk; unsigned int first_offs, last_offs; unsigned int blk_per_trk, blksize; int cdlspecial; + unsigned int data_size; struct dasd_ccw_req *cqr; basedev = block->base; @@ -2345,19 +3212,23 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, /* Calculate number of blocks/records per track. */ blksize = block->bp_block; blk_per_trk = recs_per_track(&private->rdc_data, 0, blksize); + if (blk_per_trk == 0) + return ERR_PTR(-EINVAL); /* Calculate record id of first and last block. */ - first_rec = first_trk = req->sector >> block->s2b_shift; + first_rec = first_trk = blk_rq_pos(req) >> block->s2b_shift; first_offs = sector_div(first_trk, blk_per_trk); last_rec = last_trk = - (req->sector + req->nr_sectors - 1) >> block->s2b_shift; + (blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift; last_offs = sector_div(last_trk, blk_per_trk); cdlspecial = (private->uses_cdl && first_rec < 2*blk_per_trk); - /* is transport mode supported ? */ - fcx_in_css = css_general_characteristics.fcx; - fcx_in_gneq = private->gneq->reserved2[7] & 0x04; - fcx_in_features = private->features.feature[40] & 0x80; - tpm = fcx_in_css && fcx_in_gneq && fcx_in_features; + fcx_multitrack = private->features.feature[40] & 0x20; + data_size = blk_rq_bytes(req); + if (data_size % blksize) + return ERR_PTR(-EINVAL); + /* tpm write request add CBC data on each track boundary */ + if (rq_data_dir(req) == WRITE) + data_size += (last_trk - first_trk) * 4; /* is read track data and write track data in command mode supported? */ cmdrtd = private->features.feature[9] & 0x20; @@ -2367,13 +3238,15 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, cqr = NULL; if (cdlspecial || dasd_page_cache) { /* do nothing, just fall through to the cmd mode single case */ - } else if (!dasd_nofcx && tpm && (first_trk == last_trk)) { + } else if ((data_size <= private->fcx_max_data) + && (fcx_multitrack || (first_trk == last_trk))) { cqr = dasd_eckd_build_cp_tpm_track(startdev, block, req, first_rec, last_rec, first_trk, last_trk, first_offs, last_offs, blk_per_trk, blksize); - if (IS_ERR(cqr) && PTR_ERR(cqr) != -EAGAIN) + if (IS_ERR(cqr) && (PTR_ERR(cqr) != -EAGAIN) && + (PTR_ERR(cqr) != -ENOMEM)) cqr = NULL; } else if (use_prefix && (((rq_data_dir(req) == READ) && cmdrtd) || @@ -2383,7 +3256,8 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, first_trk, last_trk, first_offs, last_offs, blk_per_trk, blksize); - if (IS_ERR(cqr) && PTR_ERR(cqr) != -EAGAIN) + if (IS_ERR(cqr) && (PTR_ERR(cqr) != -EAGAIN) && + (PTR_ERR(cqr) != -ENOMEM)) cqr = NULL; } if (!cqr) @@ -2395,13 +3269,162 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, return cqr; } +static struct dasd_ccw_req *dasd_raw_build_cp(struct dasd_device *startdev, + struct dasd_block *block, + struct request *req) +{ + unsigned long *idaws; + struct dasd_device *basedev; + struct dasd_ccw_req *cqr; + struct ccw1 *ccw; + struct req_iterator iter; + struct bio_vec bv; + char *dst; + unsigned char cmd; + unsigned int trkcount; + unsigned int seg_len, len_to_track_end; + unsigned int first_offs; + unsigned int cidaw, cplength, datasize; + sector_t first_trk, last_trk, sectors; + sector_t start_padding_sectors, end_sector_offset, end_padding_sectors; + unsigned int pfx_datasize; + + /* + * raw track access needs to be mutiple of 64k and on 64k boundary + * For read requests we can fix an incorrect alignment by padding + * the request with dummy pages. + */ + start_padding_sectors = blk_rq_pos(req) % DASD_RAW_SECTORS_PER_TRACK; + end_sector_offset = (blk_rq_pos(req) + blk_rq_sectors(req)) % + DASD_RAW_SECTORS_PER_TRACK; + end_padding_sectors = (DASD_RAW_SECTORS_PER_TRACK - end_sector_offset) % + DASD_RAW_SECTORS_PER_TRACK; + basedev = block->base; + if ((start_padding_sectors || end_padding_sectors) && + (rq_data_dir(req) == WRITE)) { + DBF_DEV_EVENT(DBF_ERR, basedev, + "raw write not track aligned (%lu,%lu) req %p", + start_padding_sectors, end_padding_sectors, req); + cqr = ERR_PTR(-EINVAL); + goto out; + } + + first_trk = blk_rq_pos(req) / DASD_RAW_SECTORS_PER_TRACK; + last_trk = (blk_rq_pos(req) + blk_rq_sectors(req) - 1) / + DASD_RAW_SECTORS_PER_TRACK; + trkcount = last_trk - first_trk + 1; + first_offs = 0; + + if (rq_data_dir(req) == READ) + cmd = DASD_ECKD_CCW_READ_TRACK; + else if (rq_data_dir(req) == WRITE) + cmd = DASD_ECKD_CCW_WRITE_FULL_TRACK; + else { + cqr = ERR_PTR(-EINVAL); + goto out; + } + + /* + * Raw track based I/O needs IDAWs for each page, + * and not just for 64 bit addresses. + */ + cidaw = trkcount * DASD_RAW_BLOCK_PER_TRACK; + + /* 1x prefix + one read/write ccw per track */ + cplength = 1 + trkcount; + + /* + * struct PFX_eckd_data has up to 2 byte as extended parameter + * this is needed for write full track and has to be mentioned + * separately + * add 8 instead of 2 to keep 8 byte boundary + */ + pfx_datasize = sizeof(struct PFX_eckd_data) + 8; + + datasize = pfx_datasize + cidaw * sizeof(unsigned long long); + + /* Allocate the ccw request. */ + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, + datasize, startdev); + if (IS_ERR(cqr)) + goto out; + ccw = cqr->cpaddr; + + if (prefix_LRE(ccw++, cqr->data, first_trk, last_trk, cmd, + basedev, startdev, 1 /* format */, first_offs + 1, + trkcount, 0, 0) == -EAGAIN) { + /* Clock not in sync and XRC is enabled. + * Try again later. + */ + dasd_sfree_request(cqr, startdev); + cqr = ERR_PTR(-EAGAIN); + goto out; + } + + idaws = (unsigned long *)(cqr->data + pfx_datasize); + len_to_track_end = 0; + if (start_padding_sectors) { + ccw[-1].flags |= CCW_FLAG_CC; + ccw->cmd_code = cmd; + /* maximum 3390 track size */ + ccw->count = 57326; + /* 64k map to one track */ + len_to_track_end = 65536 - start_padding_sectors * 512; + ccw->cda = (__u32)(addr_t)idaws; + ccw->flags |= CCW_FLAG_IDA; + ccw->flags |= CCW_FLAG_SLI; + ccw++; + for (sectors = 0; sectors < start_padding_sectors; sectors += 8) + idaws = idal_create_words(idaws, rawpadpage, PAGE_SIZE); + } + rq_for_each_segment(bv, req, iter) { + dst = page_address(bv.bv_page) + bv.bv_offset; + seg_len = bv.bv_len; + if (cmd == DASD_ECKD_CCW_READ_TRACK) + memset(dst, 0, seg_len); + if (!len_to_track_end) { + ccw[-1].flags |= CCW_FLAG_CC; + ccw->cmd_code = cmd; + /* maximum 3390 track size */ + ccw->count = 57326; + /* 64k map to one track */ + len_to_track_end = 65536; + ccw->cda = (__u32)(addr_t)idaws; + ccw->flags |= CCW_FLAG_IDA; + ccw->flags |= CCW_FLAG_SLI; + ccw++; + } + len_to_track_end -= seg_len; + idaws = idal_create_words(idaws, dst, seg_len); + } + for (sectors = 0; sectors < end_padding_sectors; sectors += 8) + idaws = idal_create_words(idaws, rawpadpage, PAGE_SIZE); + if (blk_noretry_request(req) || + block->base->features & DASD_FEATURE_FAILFAST) + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); + cqr->startdev = startdev; + cqr->memdev = startdev; + cqr->block = block; + cqr->expires = startdev->default_expires * HZ; + cqr->lpm = startdev->path_data.ppm; + cqr->retries = startdev->default_retries; + cqr->buildclk = get_tod_clock(); + cqr->status = DASD_CQR_FILLED; + + if (IS_ERR(cqr) && PTR_ERR(cqr) != -EAGAIN) + cqr = NULL; +out: + return cqr; +} + + static int dasd_eckd_free_cp(struct dasd_ccw_req *cqr, struct request *req) { struct dasd_eckd_private *private; struct ccw1 *ccw; struct req_iterator iter; - struct bio_vec *bv; + struct bio_vec bv; char *dst, *cda; unsigned int blksize, blk_per_trk, off; sector_t recid; @@ -2412,15 +3435,15 @@ dasd_eckd_free_cp(struct dasd_ccw_req *cqr, struct request *req) private = (struct dasd_eckd_private *) cqr->block->base->private; blksize = cqr->block->bp_block; blk_per_trk = recs_per_track(&private->rdc_data, 0, blksize); - recid = req->sector >> cqr->block->s2b_shift; + recid = blk_rq_pos(req) >> cqr->block->s2b_shift; ccw = cqr->cpaddr; /* Skip over define extent & locate record. */ ccw++; if (private->uses_cdl == 0 || recid > 2*blk_per_trk) ccw++; rq_for_each_segment(bv, req, iter) { - dst = page_address(bv->bv_page) + bv->bv_offset; - for (off = 0; off < bv->bv_len; off += blksize) { + dst = page_address(bv.bv_page) + bv.bv_offset; + for (off = 0; off < bv.bv_len; off += blksize) { /* Skip locate record. */ if (private->uses_cdl && recid <= 2*blk_per_trk) ccw++; @@ -2431,7 +3454,7 @@ dasd_eckd_free_cp(struct dasd_ccw_req *cqr, struct request *req) cda = (char *)((addr_t) ccw->cda); if (dst != cda) { if (rq_data_dir(req) == READ) - memcpy(dst, cda, bv->bv_len); + memcpy(dst, cda, bv.bv_len); kmem_cache_free(dasd_page_cache, (void *)((addr_t)cda & PAGE_MASK)); } @@ -2499,7 +3522,10 @@ static struct dasd_ccw_req *dasd_eckd_build_alias_cp(struct dasd_device *base, spin_lock_irqsave(get_ccwdev_lock(startdev->cdev), flags); private->count++; - cqr = dasd_eckd_build_cp(startdev, block, req); + if ((base->features & DASD_FEATURE_USERAW)) + cqr = dasd_raw_build_cp(startdev, block, req); + else + cqr = dasd_eckd_build_cp(startdev, block, req); if (IS_ERR(cqr)) private->count--; spin_unlock_irqrestore(get_ccwdev_lock(startdev->cdev), flags); @@ -2554,16 +3580,23 @@ dasd_eckd_release(struct dasd_device *device) struct dasd_ccw_req *cqr; int rc; struct ccw1 *ccw; + int useglobal; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - 1, 32, device); + useglobal = 0; + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 32, device); if (IS_ERR(cqr)) { - DBF_DEV_EVENT(DBF_WARNING, device, "%s", - "Could not allocate initialization request"); - return PTR_ERR(cqr); + mutex_lock(&dasd_reserve_mutex); + useglobal = 1; + cqr = &dasd_reserve_req->cqr; + memset(cqr, 0, sizeof(*cqr)); + memset(&dasd_reserve_req->ccw, 0, + sizeof(dasd_reserve_req->ccw)); + cqr->cpaddr = &dasd_reserve_req->ccw; + cqr->data = &dasd_reserve_req->data; + cqr->magic = DASD_ECKD_MAGIC; } ccw = cqr->cpaddr; ccw->cmd_code = DASD_ECKD_CCW_RELEASE; @@ -2576,12 +3609,17 @@ dasd_eckd_release(struct dasd_device *device) set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->retries = 2; /* set retry counter to enable basic ERP */ cqr->expires = 2 * HZ; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; rc = dasd_sleep_on_immediatly(cqr); + if (!rc) + clear_bit(DASD_FLAG_IS_RESERVED, &device->flags); - dasd_sfree_request(cqr, cqr->memdev); + if (useglobal) + mutex_unlock(&dasd_reserve_mutex); + else + dasd_sfree_request(cqr, cqr->memdev); return rc; } @@ -2597,16 +3635,23 @@ dasd_eckd_reserve(struct dasd_device *device) struct dasd_ccw_req *cqr; int rc; struct ccw1 *ccw; + int useglobal; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - 1, 32, device); + useglobal = 0; + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 32, device); if (IS_ERR(cqr)) { - DBF_DEV_EVENT(DBF_WARNING, device, "%s", - "Could not allocate initialization request"); - return PTR_ERR(cqr); + mutex_lock(&dasd_reserve_mutex); + useglobal = 1; + cqr = &dasd_reserve_req->cqr; + memset(cqr, 0, sizeof(*cqr)); + memset(&dasd_reserve_req->ccw, 0, + sizeof(dasd_reserve_req->ccw)); + cqr->cpaddr = &dasd_reserve_req->ccw; + cqr->data = &dasd_reserve_req->data; + cqr->magic = DASD_ECKD_MAGIC; } ccw = cqr->cpaddr; ccw->cmd_code = DASD_ECKD_CCW_RESERVE; @@ -2619,12 +3664,17 @@ dasd_eckd_reserve(struct dasd_device *device) set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->retries = 2; /* set retry counter to enable basic ERP */ cqr->expires = 2 * HZ; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; rc = dasd_sleep_on_immediatly(cqr); + if (!rc) + set_bit(DASD_FLAG_IS_RESERVED, &device->flags); - dasd_sfree_request(cqr, cqr->memdev); + if (useglobal) + mutex_unlock(&dasd_reserve_mutex); + else + dasd_sfree_request(cqr, cqr->memdev); return rc; } @@ -2639,16 +3689,23 @@ dasd_eckd_steal_lock(struct dasd_device *device) struct dasd_ccw_req *cqr; int rc; struct ccw1 *ccw; + int useglobal; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - 1, 32, device); + useglobal = 0; + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 32, device); if (IS_ERR(cqr)) { - DBF_DEV_EVENT(DBF_WARNING, device, "%s", - "Could not allocate initialization request"); - return PTR_ERR(cqr); + mutex_lock(&dasd_reserve_mutex); + useglobal = 1; + cqr = &dasd_reserve_req->cqr; + memset(cqr, 0, sizeof(*cqr)); + memset(&dasd_reserve_req->ccw, 0, + sizeof(dasd_reserve_req->ccw)); + cqr->cpaddr = &dasd_reserve_req->ccw; + cqr->data = &dasd_reserve_req->data; + cqr->magic = DASD_ECKD_MAGIC; } ccw = cqr->cpaddr; ccw->cmd_code = DASD_ECKD_CCW_SLCK; @@ -2661,12 +3718,85 @@ dasd_eckd_steal_lock(struct dasd_device *device) set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->retries = 2; /* set retry counter to enable basic ERP */ cqr->expires = 2 * HZ; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; rc = dasd_sleep_on_immediatly(cqr); + if (!rc) + set_bit(DASD_FLAG_IS_RESERVED, &device->flags); - dasd_sfree_request(cqr, cqr->memdev); + if (useglobal) + mutex_unlock(&dasd_reserve_mutex); + else + dasd_sfree_request(cqr, cqr->memdev); + return rc; +} + +/* + * SNID - Sense Path Group ID + * This ioctl may be used in situations where I/O is stalled due to + * a reserve, so if the normal dasd_smalloc_request fails, we use the + * preallocated dasd_reserve_req. + */ +static int dasd_eckd_snid(struct dasd_device *device, + void __user *argp) +{ + struct dasd_ccw_req *cqr; + int rc; + struct ccw1 *ccw; + int useglobal; + struct dasd_snid_ioctl_data usrparm; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (copy_from_user(&usrparm, argp, sizeof(usrparm))) + return -EFAULT; + + useglobal = 0; + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, + sizeof(struct dasd_snid_data), device); + if (IS_ERR(cqr)) { + mutex_lock(&dasd_reserve_mutex); + useglobal = 1; + cqr = &dasd_reserve_req->cqr; + memset(cqr, 0, sizeof(*cqr)); + memset(&dasd_reserve_req->ccw, 0, + sizeof(dasd_reserve_req->ccw)); + cqr->cpaddr = &dasd_reserve_req->ccw; + cqr->data = &dasd_reserve_req->data; + cqr->magic = DASD_ECKD_MAGIC; + } + ccw = cqr->cpaddr; + ccw->cmd_code = DASD_ECKD_CCW_SNID; + ccw->flags |= CCW_FLAG_SLI; + ccw->count = 12; + ccw->cda = (__u32)(addr_t) cqr->data; + cqr->startdev = device; + cqr->memdev = device; + clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); + set_bit(DASD_CQR_ALLOW_SLOCK, &cqr->flags); + cqr->retries = 5; + cqr->expires = 10 * HZ; + cqr->buildclk = get_tod_clock(); + cqr->status = DASD_CQR_FILLED; + cqr->lpm = usrparm.path_mask; + + rc = dasd_sleep_on_immediatly(cqr); + /* verify that I/O processing didn't modify the path mask */ + if (!rc && usrparm.path_mask && (cqr->lpm != usrparm.path_mask)) + rc = -EIO; + if (!rc) { + usrparm.data = *((struct dasd_snid_data *)cqr->data); + if (copy_to_user(argp, &usrparm, sizeof(usrparm))) + rc = -EFAULT; + } + + if (useglobal) + mutex_unlock(&dasd_reserve_mutex); + else + dasd_sfree_request(cqr, cqr->memdev); return rc; } @@ -2682,8 +3812,7 @@ dasd_eckd_performance(struct dasd_device *device, void __user *argp) struct ccw1 *ccw; int rc; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, - 1 /* PSF */ + 1 /* RSSD */ , + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */, (sizeof(struct dasd_psf_prssd_data) + sizeof(struct dasd_rssd_perf_stats_t)), device); @@ -2695,6 +3824,7 @@ dasd_eckd_performance(struct dasd_device *device, void __user *argp) cqr->startdev = device; cqr->memdev = device; cqr->retries = 0; + clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); cqr->expires = 10 * HZ; /* Prepare for Read Subsystem Data */ @@ -2719,7 +3849,7 @@ dasd_eckd_performance(struct dasd_device *device, void __user *argp) ccw->count = sizeof(struct dasd_rssd_perf_stats_t); ccw->cda = (__u32)(addr_t) stats; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; rc = dasd_sleep_on(cqr); if (rc == 0) { @@ -2794,19 +3924,27 @@ static int dasd_symm_io(struct dasd_device *device, void __user *argp) char *psf_data, *rssd_result; struct dasd_ccw_req *cqr; struct ccw1 *ccw; + char psf0, psf1; int rc; + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EACCES; + psf0 = psf1 = 0; + /* Copy parms from caller */ rc = -EFAULT; if (copy_from_user(&usrparm, argp, sizeof(usrparm))) goto out; -#ifndef CONFIG_64BIT - /* Make sure pointers are sane even on 31 bit. */ - if ((usrparm.psf_data >> 32) != 0 || (usrparm.rssd_result >> 32) != 0) { + if (is_compat_task() || sizeof(long) == 4) { + /* Make sure pointers are sane even on 31 bit. */ rc = -EINVAL; - goto out; + if ((usrparm.psf_data >> 32) != 0) + goto out; + if ((usrparm.rssd_result >> 32) != 0) + goto out; + usrparm.psf_data &= 0x7fffffffULL; + usrparm.rssd_result &= 0x7fffffffULL; } -#endif /* alloc I/O data area */ psf_data = kzalloc(usrparm.psf_data_len, GFP_KERNEL | GFP_DMA); rssd_result = kzalloc(usrparm.rssd_result_len, GFP_KERNEL | GFP_DMA); @@ -2821,15 +3959,11 @@ static int dasd_symm_io(struct dasd_device *device, void __user *argp) (void __user *)(unsigned long) usrparm.psf_data, usrparm.psf_data_len)) goto out_free; - - /* sanity check on syscall header */ - if (psf_data[0] != 0x17 && psf_data[1] != 0xce) { - rc = -EINVAL; - goto out_free; - } + psf0 = psf_data[0]; + psf1 = psf_data[1]; /* setup CCWs for PSF + RSSD */ - cqr = dasd_smalloc_request("ECKD", 2 , 0, device); + cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 2 , 0, device); if (IS_ERR(cqr)) { DBF_DEV_EVENT(DBF_WARNING, device, "%s", "Could not allocate initialization request"); @@ -2841,7 +3975,7 @@ static int dasd_symm_io(struct dasd_device *device, void __user *argp) cqr->memdev = device; cqr->retries = 3; cqr->expires = 10 * HZ; - cqr->buildclk = get_clock(); + cqr->buildclk = get_tod_clock(); cqr->status = DASD_CQR_FILLED; /* Build the ccws */ @@ -2877,7 +4011,9 @@ out_free: kfree(rssd_result); kfree(psf_data); out: - DBF_DEV_EVENT(DBF_WARNING, device, "Symmetrix ioctl: rc=%d", rc); + DBF_DEV_EVENT(DBF_WARNING, device, + "Symmetrix ioctl (0x%02x 0x%02x): rc=%d", + (int) psf0, (int) psf1, rc); return rc; } @@ -2899,10 +4035,12 @@ dasd_eckd_ioctl(struct dasd_block *block, unsigned int cmd, void __user *argp) return dasd_eckd_reserve(device); case BIODASDSLCK: return dasd_eckd_steal_lock(device); + case BIODASDSNID: + return dasd_eckd_snid(device, argp); case BIODASDSYMMIO: return dasd_symm_io(device, argp); default: - return -ENOIOCTLCMD; + return -ENOTTY; } } @@ -2918,7 +4056,7 @@ dasd_eckd_dump_ccw_range(struct ccw1 *from, struct ccw1 *to, char *page) len = 0; while (from <= to) { - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " CCW %p: %08X %08X DAT:", from, ((int *) from)[0], ((int *) from)[1]); @@ -2941,45 +4079,23 @@ dasd_eckd_dump_ccw_range(struct ccw1 *from, struct ccw1 *to, char *page) } static void -dasd_eckd_dump_sense_dbf(struct dasd_device *device, struct dasd_ccw_req *req, - struct irb *irb, char *reason) +dasd_eckd_dump_sense_dbf(struct dasd_device *device, struct irb *irb, + char *reason) { u64 *sense; - int sl; - struct tsb *tsb; + u64 *stat; - sense = NULL; - tsb = NULL; - if (req && scsw_is_tm(&req->irb.scsw)) { - if (irb->scsw.tm.tcw) - tsb = tcw_get_tsb( - (struct tcw *)(unsigned long)irb->scsw.tm.tcw); - if (tsb && (irb->scsw.tm.fcxs == 0x01)) { - switch (tsb->flags & 0x07) { - case 1: /* tsa_iostat */ - sense = (u64 *)tsb->tsa.iostat.sense; - break; - case 2: /* ts_ddpc */ - sense = (u64 *)tsb->tsa.ddpc.sense; - break; - case 3: /* tsa_intrg */ - break; - } - } - } else { - if (irb->esw.esw0.erw.cons) - sense = (u64 *)irb->ecw; - } + sense = (u64 *) dasd_get_sense(irb); + stat = (u64 *) &irb->scsw; if (sense) { - for (sl = 0; sl < 4; sl++) { - DBF_DEV_EVENT(DBF_EMERG, device, - "%s: %016llx %016llx %016llx %016llx", - reason, sense[0], sense[1], sense[2], - sense[3]); - } + DBF_DEV_EVENT(DBF_EMERG, device, "%s: %016llx %08x : " + "%016llx %016llx %016llx %016llx", + reason, *stat, *((u32 *) (stat + 1)), + sense[0], sense[1], sense[2], sense[3]); } else { - DBF_DEV_EVENT(DBF_EMERG, device, "%s", - "SORRY - NO VALID SENSE AVAILABLE\n"); + DBF_DEV_EVENT(DBF_EMERG, device, "%s: %016llx %08x : %s", + reason, *stat, *((u32 *) (stat + 1)), + "NO VALID SENSE"); } } @@ -3001,19 +4117,23 @@ static void dasd_eckd_dump_sense_ccw(struct dasd_device *device, return; } /* dump the sense data */ - len = sprintf(page, KERN_ERR PRINTK_HEADER + len = sprintf(page, PRINTK_HEADER " I/O status report for device %s:\n", dev_name(&device->cdev->dev)); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - " in req: %p CS: 0x%02X DS: 0x%02X\n", req, - scsw_cstat(&irb->scsw), scsw_dstat(&irb->scsw)); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER + " in req: %p CC:%02X FC:%02X AC:%02X SC:%02X DS:%02X " + "CS:%02X RC:%d\n", + req, scsw_cc(&irb->scsw), scsw_fctl(&irb->scsw), + scsw_actl(&irb->scsw), scsw_stctl(&irb->scsw), + scsw_dstat(&irb->scsw), scsw_cstat(&irb->scsw), + req ? req->intrc : 0); + len += sprintf(page + len, PRINTK_HEADER " device %s: Failing CCW: %p\n", dev_name(&device->cdev->dev), (void *) (addr_t) irb->scsw.cmd.cpa); if (irb->esw.esw0.erw.cons) { for (sl = 0; sl < 4; sl++) { - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " Sense(hex) %2d-%2d:", (8 * sl), ((8 * sl) + 7)); @@ -3026,23 +4146,23 @@ static void dasd_eckd_dump_sense_ccw(struct dasd_device *device, if (irb->ecw[27] & DASD_SENSE_BIT_0) { /* 24 Byte Sense Data */ - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " 24 Byte: %x MSG %x, " "%s MSGb to SYSOP\n", irb->ecw[7] >> 4, irb->ecw[7] & 0x0f, irb->ecw[1] & 0x10 ? "" : "no"); } else { /* 32 Byte Sense Data */ - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " 32 Byte: Format: %x " "Exception class %x\n", irb->ecw[6] & 0x0f, irb->ecw[22] >> 4); } } else { - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " SORRY - NO VALID SENSE AVAILABLE\n"); } - printk("%s", page); + printk(KERN_ERR "%s", page); if (req) { /* req == NULL for unsolicited interrupts */ @@ -3051,10 +4171,10 @@ static void dasd_eckd_dump_sense_ccw(struct dasd_device *device, first = req->cpaddr; for (last = first; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++); to = min(first + 6, last); - len = sprintf(page, KERN_ERR PRINTK_HEADER + len = sprintf(page, PRINTK_HEADER " Related CP in req: %p\n", req); dasd_eckd_dump_ccw_range(first, to, page + len); - printk("%s", page); + printk(KERN_ERR "%s", page); /* print failing CCW area (maximum 4) */ /* scsw->cda is either valid or zero */ @@ -3064,7 +4184,7 @@ static void dasd_eckd_dump_sense_ccw(struct dasd_device *device, irb->scsw.cmd.cpa; /* failing CCW */ if (from < fail - 2) { from = fail - 2; /* there is a gap - print header */ - len += sprintf(page, KERN_ERR PRINTK_HEADER "......\n"); + len += sprintf(page, PRINTK_HEADER "......\n"); } to = min(fail + 1, last); len += dasd_eckd_dump_ccw_range(from, to, page + len); @@ -3073,11 +4193,11 @@ static void dasd_eckd_dump_sense_ccw(struct dasd_device *device, from = max(from, ++to); if (from < last - 1) { from = last - 1; /* there is a gap - print header */ - len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n"); + len += sprintf(page + len, PRINTK_HEADER "......\n"); } len += dasd_eckd_dump_ccw_range(from, last, page + len); if (len > 0) - printk("%s", page); + printk(KERN_ERR "%s", page); } free_page((unsigned long) page); } @@ -3091,10 +4211,8 @@ static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, { char *page; int len, sl, sct, residual; - struct tsb *tsb; - u8 *sense; - + u8 *sense, *rcq; page = (char *) get_zeroed_page(GFP_ATOMIC); if (page == NULL) { @@ -3103,81 +4221,85 @@ static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, return; } /* dump the sense data */ - len = sprintf(page, KERN_ERR PRINTK_HEADER + len = sprintf(page, PRINTK_HEADER " I/O status report for device %s:\n", dev_name(&device->cdev->dev)); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - " in req: %p CS: 0x%02X DS: 0x%02X " - "fcxs: 0x%02X schxs: 0x%02X\n", req, - scsw_cstat(&irb->scsw), scsw_dstat(&irb->scsw), - irb->scsw.tm.fcxs, irb->scsw.tm.schxs); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER + " in req: %p CC:%02X FC:%02X AC:%02X SC:%02X DS:%02X " + "CS:%02X fcxs:%02X schxs:%02X RC:%d\n", + req, scsw_cc(&irb->scsw), scsw_fctl(&irb->scsw), + scsw_actl(&irb->scsw), scsw_stctl(&irb->scsw), + scsw_dstat(&irb->scsw), scsw_cstat(&irb->scsw), + irb->scsw.tm.fcxs, irb->scsw.tm.schxs, + req ? req->intrc : 0); + len += sprintf(page + len, PRINTK_HEADER " device %s: Failing TCW: %p\n", dev_name(&device->cdev->dev), (void *) (addr_t) irb->scsw.tm.tcw); tsb = NULL; sense = NULL; - if (irb->scsw.tm.tcw) + if (irb->scsw.tm.tcw && (irb->scsw.tm.fcxs & 0x01)) tsb = tcw_get_tsb( (struct tcw *)(unsigned long)irb->scsw.tm.tcw); - if (tsb && (irb->scsw.tm.fcxs == 0x01)) { - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + if (tsb) { + len += sprintf(page + len, PRINTK_HEADER " tsb->length %d\n", tsb->length); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->flags %x\n", tsb->flags); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->dcw_offset %d\n", tsb->dcw_offset); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->count %d\n", tsb->count); residual = tsb->count - 28; - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " residual %d\n", residual); switch (tsb->flags & 0x07) { case 1: /* tsa_iostat */ - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->tsa.iostat.dev_time %d\n", tsb->tsa.iostat.dev_time); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->tsa.iostat.def_time %d\n", tsb->tsa.iostat.def_time); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->tsa.iostat.queue_time %d\n", tsb->tsa.iostat.queue_time); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->tsa.iostat.dev_busy_time %d\n", tsb->tsa.iostat.dev_busy_time); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->tsa.iostat.dev_act_time %d\n", tsb->tsa.iostat.dev_act_time); sense = tsb->tsa.iostat.sense; break; case 2: /* ts_ddpc */ - len += sprintf(page + len, KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " tsb->tsa.ddpc.rc %d\n", tsb->tsa.ddpc.rc); - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - " tsb->tsa.ddpc.rcq: "); - for (sl = 0; sl < 16; sl++) { + for (sl = 0; sl < 2; sl++) { + len += sprintf(page + len, PRINTK_HEADER + " tsb->tsa.ddpc.rcq %2d-%2d: ", + (8 * sl), ((8 * sl) + 7)); + rcq = tsb->tsa.ddpc.rcq; for (sct = 0; sct < 8; sct++) { len += sprintf(page + len, " %02x", - tsb->tsa.ddpc.rcq[sl]); + rcq[8 * sl + sct]); } len += sprintf(page + len, "\n"); } sense = tsb->tsa.ddpc.sense; break; case 3: /* tsa_intrg */ - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - " tsb->tsa.intrg.: not supportet yet \n"); + len += sprintf(page + len, PRINTK_HEADER + " tsb->tsa.intrg.: not supportet yet\n"); break; } if (sense) { for (sl = 0; sl < 4; sl++) { - len += sprintf(page + len, - KERN_ERR PRINTK_HEADER + len += sprintf(page + len, PRINTK_HEADER " Sense(hex) %2d-%2d:", (8 * sl), ((8 * sl) + 7)); for (sct = 0; sct < 8; sct++) { @@ -3189,39 +4311,181 @@ static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, if (sense[27] & DASD_SENSE_BIT_0) { /* 24 Byte Sense Data */ - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " 24 Byte: %x MSG %x, " "%s MSGb to SYSOP\n", sense[7] >> 4, sense[7] & 0x0f, sense[1] & 0x10 ? "" : "no"); } else { /* 32 Byte Sense Data */ - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " 32 Byte: Format: %x " "Exception class %x\n", sense[6] & 0x0f, sense[22] >> 4); } } else { - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " SORRY - NO VALID SENSE AVAILABLE\n"); } } else { - sprintf(page + len, KERN_ERR PRINTK_HEADER + sprintf(page + len, PRINTK_HEADER " SORRY - NO TSB DATA AVAILABLE\n"); } - printk("%s", page); + printk(KERN_ERR "%s", page); free_page((unsigned long) page); } static void dasd_eckd_dump_sense(struct dasd_device *device, struct dasd_ccw_req *req, struct irb *irb) { - if (req && scsw_is_tm(&req->irb.scsw)) + if (scsw_is_tm(&irb->scsw)) dasd_eckd_dump_sense_tcw(device, req, irb); else dasd_eckd_dump_sense_ccw(device, req, irb); } +static int dasd_eckd_pm_freeze(struct dasd_device *device) +{ + /* + * the device should be disconnected from our LCU structure + * on restore we will reconnect it and reread LCU specific + * information like PAV support that might have changed + */ + dasd_alias_remove_device(device); + dasd_alias_disconnect_device_from_lcu(device); + + return 0; +} + +static int dasd_eckd_restore_device(struct dasd_device *device) +{ + struct dasd_eckd_private *private; + struct dasd_eckd_characteristics temp_rdc_data; + int rc; + struct dasd_uid temp_uid; + unsigned long flags; + unsigned long cqr_flags = 0; + + private = (struct dasd_eckd_private *) device->private; + + /* Read Configuration Data */ + dasd_eckd_read_conf(device); + + dasd_eckd_get_uid(device, &temp_uid); + /* Generate device unique id */ + rc = dasd_eckd_generate_uid(device); + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + if (memcmp(&private->uid, &temp_uid, sizeof(struct dasd_uid)) != 0) + dev_err(&device->cdev->dev, "The UID of the DASD has " + "changed\n"); + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + if (rc) + goto out_err; + + /* register lcu with alias handling, enable PAV if this is a new lcu */ + rc = dasd_alias_make_device_known_to_lcu(device); + if (rc) + return rc; + + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr_flags); + dasd_eckd_validate_server(device, cqr_flags); + + /* RE-Read Configuration Data */ + dasd_eckd_read_conf(device); + + /* Read Feature Codes */ + dasd_eckd_read_features(device); + + /* Read Device Characteristics */ + rc = dasd_generic_read_dev_chars(device, DASD_ECKD_MAGIC, + &temp_rdc_data, 64); + if (rc) { + DBF_EVENT_DEVID(DBF_WARNING, device->cdev, + "Read device characteristic failed, rc=%d", rc); + goto out_err; + } + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + memcpy(&private->rdc_data, &temp_rdc_data, sizeof(temp_rdc_data)); + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + + /* add device to alias management */ + dasd_alias_add_device(device); + + return 0; + +out_err: + return -1; +} + +static int dasd_eckd_reload_device(struct dasd_device *device) +{ + struct dasd_eckd_private *private; + int rc, old_base; + char print_uid[60]; + struct dasd_uid uid; + unsigned long flags; + + private = (struct dasd_eckd_private *) device->private; + + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + old_base = private->uid.base_unit_addr; + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + + /* Read Configuration Data */ + rc = dasd_eckd_read_conf(device); + if (rc) + goto out_err; + + rc = dasd_eckd_generate_uid(device); + if (rc) + goto out_err; + /* + * update unit address configuration and + * add device to alias management + */ + dasd_alias_update_add_device(device); + + dasd_eckd_get_uid(device, &uid); + + if (old_base != uid.base_unit_addr) { + if (strlen(uid.vduit) > 0) + snprintf(print_uid, sizeof(print_uid), + "%s.%s.%04x.%02x.%s", uid.vendor, uid.serial, + uid.ssid, uid.base_unit_addr, uid.vduit); + else + snprintf(print_uid, sizeof(print_uid), + "%s.%s.%04x.%02x", uid.vendor, uid.serial, + uid.ssid, uid.base_unit_addr); + + dev_info(&device->cdev->dev, + "An Alias device was reassigned to a new base device " + "with UID: %s\n", print_uid); + } + return 0; + +out_err: + return -1; +} + +static struct ccw_driver dasd_eckd_driver = { + .driver = { + .name = "dasd-eckd", + .owner = THIS_MODULE, + }, + .ids = dasd_eckd_ids, + .probe = dasd_eckd_probe, + .remove = dasd_generic_remove, + .set_offline = dasd_generic_set_offline, + .set_online = dasd_eckd_set_online, + .notify = dasd_generic_notify, + .path_event = dasd_generic_path_event, + .shutdown = dasd_generic_shutdown, + .freeze = dasd_generic_pm_freeze, + .thaw = dasd_generic_restore_device, + .restore = dasd_generic_restore_device, + .uc_handler = dasd_generic_uc_handler, + .int_class = IRQIO_DAS, +}; /* * max_blocks is dependent on the amount of storage that is available @@ -3240,12 +4504,14 @@ static struct dasd_discipline dasd_eckd_discipline = { .owner = THIS_MODULE, .name = "ECKD", .ebcname = "ECKD", - .max_blocks = 240, + .max_blocks = 190, .check_device = dasd_eckd_check_characteristics, .uncheck_device = dasd_eckd_uncheck_device, .do_analysis = dasd_eckd_do_analysis, - .ready_to_online = dasd_eckd_ready_to_online, + .verify_path = dasd_eckd_verify_path, + .basic_to_ready = dasd_eckd_basic_to_ready, .online_to_ready = dasd_eckd_online_to_ready, + .ready_to_basic = dasd_eckd_ready_to_basic, .fill_geometry = dasd_eckd_fill_geometry, .start_IO = dasd_start_IO, .term_IO = dasd_term_IO, @@ -3253,26 +4519,60 @@ static struct dasd_discipline dasd_eckd_discipline = { .format_device = dasd_eckd_format_device, .erp_action = dasd_eckd_erp_action, .erp_postaction = dasd_eckd_erp_postaction, - .handle_unsolicited_interrupt = dasd_eckd_handle_unsolicited_interrupt, + .check_for_device_change = dasd_eckd_check_for_device_change, .build_cp = dasd_eckd_build_alias_cp, .free_cp = dasd_eckd_free_alias_cp, .dump_sense = dasd_eckd_dump_sense, .dump_sense_dbf = dasd_eckd_dump_sense_dbf, .fill_info = dasd_eckd_fill_info, .ioctl = dasd_eckd_ioctl, + .freeze = dasd_eckd_pm_freeze, + .restore = dasd_eckd_restore_device, + .reload = dasd_eckd_reload_device, + .get_uid = dasd_eckd_get_uid, + .kick_validate = dasd_eckd_kick_validate_server, }; static int __init dasd_eckd_init(void) { + int ret; + ASCEBC(dasd_eckd_discipline.ebcname, 4); - return ccw_driver_register(&dasd_eckd_driver); + dasd_reserve_req = kmalloc(sizeof(*dasd_reserve_req), + GFP_KERNEL | GFP_DMA); + if (!dasd_reserve_req) + return -ENOMEM; + path_verification_worker = kmalloc(sizeof(*path_verification_worker), + GFP_KERNEL | GFP_DMA); + if (!path_verification_worker) { + kfree(dasd_reserve_req); + return -ENOMEM; + } + rawpadpage = (void *)__get_free_page(GFP_KERNEL); + if (!rawpadpage) { + kfree(path_verification_worker); + kfree(dasd_reserve_req); + return -ENOMEM; + } + ret = ccw_driver_register(&dasd_eckd_driver); + if (!ret) + wait_for_device_probe(); + else { + kfree(path_verification_worker); + kfree(dasd_reserve_req); + free_page((unsigned long)rawpadpage); + } + return ret; } static void __exit dasd_eckd_cleanup(void) { ccw_driver_unregister(&dasd_eckd_driver); + kfree(path_verification_worker); + kfree(dasd_reserve_req); + free_page((unsigned long)rawpadpage); } module_init(dasd_eckd_init); |
