| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> |
| * Horst Hummel <Horst.Hummel@de.ibm.com> |
| * Carsten Otte <Cotte@de.ibm.com> |
| * Martin Schwidefsky <schwidefsky@de.ibm.com> |
| * Bugreports.to..: <Linux390@de.ibm.com> |
| * Copyright IBM Corp. 1999, 2009 |
| * EMC Symmetrix ioctl Copyright EMC Corporation, 2008 |
| * Author.........: Nigel Hislop <hislop_nigel@emc.com> |
| */ |
| |
| #define KMSG_COMPONENT "dasd-eckd" |
| |
| #include <linux/stddef.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/hdreg.h> /* HDIO_GETGEO */ |
| #include <linux/bio.h> |
| #include <linux/module.h> |
| #include <linux/compat.h> |
| #include <linux/init.h> |
| #include <linux/seq_file.h> |
| |
| #include <asm/css_chars.h> |
| #include <asm/debug.h> |
| #include <asm/idals.h> |
| #include <asm/ebcdic.h> |
| #include <asm/io.h> |
| #include <linux/uaccess.h> |
| #include <asm/cio.h> |
| #include <asm/ccwdev.h> |
| #include <asm/itcw.h> |
| #include <asm/schid.h> |
| #include <asm/chpid.h> |
| |
| #include "dasd_int.h" |
| #include "dasd_eckd.h" |
| |
| #ifdef PRINTK_HEADER |
| #undef PRINTK_HEADER |
| #endif /* PRINTK_HEADER */ |
| #define PRINTK_HEADER "dasd(eckd):" |
| |
| /* |
| * 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; |
| |
| /* The ccw bus type uses this table to find devices that it sends to |
| * dasd_eckd_probe */ |
| 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, 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}, |
| { CCW_DEVICE_DEVTYPE (0x2107, 0, 0x3390, 0), .driver_info = 0x7}, |
| { CCW_DEVICE_DEVTYPE (0x2107, 0, 0x3380, 0), .driver_info = 0x8}, |
| { CCW_DEVICE_DEVTYPE (0x1750, 0, 0x3390, 0), .driver_info = 0x9}, |
| { CCW_DEVICE_DEVTYPE (0x1750, 0, 0x3380, 0), .driver_info = 0xa}, |
| { /* end of list */ }, |
| }; |
| |
| 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); |
| |
| static struct { |
| struct dasd_ccw_req cqr; |
| struct ccw1 ccw[2]; |
| char data[40]; |
| } *dasd_vol_info_req; |
| static DEFINE_MUTEX(dasd_vol_info_mutex); |
| |
| struct ext_pool_exhaust_work_data { |
| struct work_struct worker; |
| struct dasd_device *device; |
| struct dasd_device *base; |
| }; |
| |
| /* definitions for the path verification worker */ |
| struct pe_handler_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; |
| __u8 fcsecpm; |
| }; |
| static struct pe_handler_work_data *pe_handler_worker; |
| static DEFINE_MUTEX(dasd_pe_handler_mutex); |
| |
| struct check_attention_work_data { |
| struct work_struct worker; |
| struct dasd_device *device; |
| __u8 lpum; |
| }; |
| |
| static int dasd_eckd_ext_pool_id(struct dasd_device *); |
| static int prepare_itcw(struct itcw *, unsigned int, unsigned int, int, |
| struct dasd_device *, struct dasd_device *, |
| unsigned int, int, unsigned int, unsigned int, |
| unsigned int, unsigned int); |
| static int dasd_eckd_query_pprc_status(struct dasd_device *, |
| struct dasd_pprc_data_sc4 *); |
| |
| /* initial attempt at a probe function. this can be simplified once |
| * the other detection code is gone */ |
| static int |
| dasd_eckd_probe (struct ccw_device *cdev) |
| { |
| int ret; |
| |
| /* set ECKD specific ccw-device options */ |
| ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE | |
| CCWDEV_DO_PATHGROUP | CCWDEV_DO_MULTIPATH); |
| if (ret) { |
| DBF_EVENT_DEVID(DBF_WARNING, cdev, "%s", |
| "dasd_eckd_probe: could not set " |
| "ccw-device options"); |
| return ret; |
| } |
| ret = dasd_generic_probe(cdev); |
| return ret; |
| } |
| |
| static int |
| dasd_eckd_set_online(struct ccw_device *cdev) |
| { |
| return dasd_generic_set_online(cdev, &dasd_eckd_discipline); |
| } |
| |
| 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, 1 }; |
| static const int count_area_rec[] = { 1, 2, 3, 4, 1 }; |
| |
| static inline unsigned int |
| ceil_quot(unsigned int d1, unsigned int d2) |
| { |
| return (d1 + (d2 - 1)) / d2; |
| } |
| |
| static unsigned int |
| recs_per_track(struct dasd_eckd_characteristics * rdc, |
| unsigned int kl, unsigned int dl) |
| { |
| int dn, kn; |
| |
| switch (rdc->dev_type) { |
| case 0x3380: |
| if (kl) |
| return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) + |
| ceil_quot(dl + 12, 32)); |
| else |
| return 1499 / (15 + ceil_quot(dl + 12, 32)); |
| case 0x3390: |
| dn = ceil_quot(dl + 6, 232) + 1; |
| if (kl) { |
| kn = ceil_quot(kl + 6, 232) + 1; |
| return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) + |
| 9 + ceil_quot(dl + 6 * dn, 34)); |
| } else |
| return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34)); |
| case 0x9345: |
| dn = ceil_quot(dl + 6, 232) + 1; |
| if (kl) { |
| kn = ceil_quot(kl + 6, 232) + 1; |
| return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) + |
| ceil_quot(dl + 6 * dn, 34)); |
| } else |
| return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34)); |
| } |
| return 0; |
| } |
| |
| static void set_ch_t(struct ch_t *geo, __u32 cyl, __u8 head) |
| { |
| geo->cyl = (__u16) cyl; |
| geo->head = cyl >> 16; |
| geo->head <<= 4; |
| geo->head |= head; |
| } |
| |
| /* |
| * calculate failing track from sense data depending if |
| * it is an EAV device or not |
| */ |
| static int dasd_eckd_track_from_irb(struct irb *irb, struct dasd_device *device, |
| sector_t *track) |
| { |
| struct dasd_eckd_private *private = device->private; |
| u8 *sense = NULL; |
| u32 cyl; |
| u8 head; |
| |
| sense = dasd_get_sense(irb); |
| if (!sense) { |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", |
| "ESE error no sense data\n"); |
| return -EINVAL; |
| } |
| if (!(sense[27] & DASD_SENSE_BIT_2)) { |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", |
| "ESE error no valid track data\n"); |
| return -EINVAL; |
| } |
| |
| if (sense[27] & DASD_SENSE_BIT_3) { |
| /* enhanced addressing */ |
| cyl = sense[30] << 20; |
| cyl |= (sense[31] & 0xF0) << 12; |
| cyl |= sense[28] << 8; |
| cyl |= sense[29]; |
| } else { |
| cyl = sense[29] << 8; |
| cyl |= sense[30]; |
| } |
| head = sense[31] & 0x0F; |
| *track = cyl * private->rdc_data.trk_per_cyl + head; |
| return 0; |
| } |
| |
| static int set_timestamp(struct ccw1 *ccw, struct DE_eckd_data *data, |
| struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int rc; |
| |
| rc = get_phys_clock(&data->ep_sys_time); |
| /* |
| * Ignore return code if XRC is not supported or |
| * sync clock is switched off |
| */ |
| if ((rc && !private->rdc_data.facilities.XRC_supported) || |
| rc == -EOPNOTSUPP || rc == -EACCES) |
| return 0; |
| |
| /* switch on System Time Stamp - needed for XRC Support */ |
| data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */ |
| data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */ |
| |
| if (ccw) { |
| ccw->count = sizeof(struct DE_eckd_data); |
| ccw->flags |= CCW_FLAG_SLI; |
| } |
| |
| return rc; |
| } |
| |
| static int |
| define_extent(struct ccw1 *ccw, struct DE_eckd_data *data, unsigned int trk, |
| unsigned int totrk, int cmd, struct dasd_device *device, |
| int blksize) |
| { |
| struct dasd_eckd_private *private = device->private; |
| u16 heads, beghead, endhead; |
| u32 begcyl, endcyl; |
| int rc = 0; |
| |
| if (ccw) { |
| ccw->cmd_code = DASD_ECKD_CCW_DEFINE_EXTENT; |
| ccw->flags = 0; |
| ccw->count = 16; |
| ccw->cda = (__u32)__pa(data); |
| } |
| |
| memset(data, 0, sizeof(struct DE_eckd_data)); |
| switch (cmd) { |
| case DASD_ECKD_CCW_READ_HOME_ADDRESS: |
| case DASD_ECKD_CCW_READ_RECORD_ZERO: |
| case DASD_ECKD_CCW_READ: |
| case DASD_ECKD_CCW_READ_MT: |
| case DASD_ECKD_CCW_READ_CKD: |
| case DASD_ECKD_CCW_READ_CKD_MT: |
| case DASD_ECKD_CCW_READ_KD: |
| case DASD_ECKD_CCW_READ_KD_MT: |
| data->mask.perm = 0x1; |
| data->attributes.operation = private->attrib.operation; |
| break; |
| case DASD_ECKD_CCW_READ_COUNT: |
| data->mask.perm = 0x1; |
| data->attributes.operation = DASD_BYPASS_CACHE; |
| break; |
| case DASD_ECKD_CCW_READ_TRACK: |
| case DASD_ECKD_CCW_READ_TRACK_DATA: |
| data->mask.perm = 0x1; |
| data->attributes.operation = private->attrib.operation; |
| data->blk_size = 0; |
| break; |
| case DASD_ECKD_CCW_WRITE: |
| case DASD_ECKD_CCW_WRITE_MT: |
| case DASD_ECKD_CCW_WRITE_KD: |
| case DASD_ECKD_CCW_WRITE_KD_MT: |
| data->mask.perm = 0x02; |
| data->attributes.operation = private->attrib.operation; |
| rc = set_timestamp(ccw, data, device); |
| break; |
| case DASD_ECKD_CCW_WRITE_CKD: |
| case DASD_ECKD_CCW_WRITE_CKD_MT: |
| data->attributes.operation = DASD_BYPASS_CACHE; |
| rc = set_timestamp(ccw, data, device); |
| break; |
| case DASD_ECKD_CCW_ERASE: |
| case DASD_ECKD_CCW_WRITE_HOME_ADDRESS: |
| case DASD_ECKD_CCW_WRITE_RECORD_ZERO: |
| data->mask.perm = 0x3; |
| data->mask.auth = 0x1; |
| data->attributes.operation = DASD_BYPASS_CACHE; |
| rc = set_timestamp(ccw, data, device); |
| break; |
| case DASD_ECKD_CCW_WRITE_FULL_TRACK: |
| data->mask.perm = 0x03; |
| data->attributes.operation = private->attrib.operation; |
| data->blk_size = 0; |
| break; |
| case DASD_ECKD_CCW_WRITE_TRACK_DATA: |
| data->mask.perm = 0x02; |
| data->attributes.operation = private->attrib.operation; |
| data->blk_size = blksize; |
| rc = set_timestamp(ccw, data, device); |
| break; |
| default: |
| dev_err(&device->cdev->dev, |
| "0x%x is not a known command\n", cmd); |
| break; |
| } |
| |
| data->attributes.mode = 0x3; /* ECKD */ |
| |
| if ((private->rdc_data.cu_type == 0x2105 || |
| private->rdc_data.cu_type == 0x2107 || |
| private->rdc_data.cu_type == 0x1750) |
| && !(private->uses_cdl && trk < 2)) |
| data->ga_extended |= 0x40; /* Regular Data Format Mode */ |
| |
| heads = private->rdc_data.trk_per_cyl; |
| begcyl = trk / heads; |
| beghead = trk % heads; |
| endcyl = totrk / heads; |
| endhead = totrk % heads; |
| |
| /* check for sequential prestage - enhance cylinder range */ |
| if (data->attributes.operation == DASD_SEQ_PRESTAGE || |
| data->attributes.operation == DASD_SEQ_ACCESS) { |
| |
| if (endcyl + private->attrib.nr_cyl < private->real_cyl) |
| endcyl += private->attrib.nr_cyl; |
| else |
| endcyl = (private->real_cyl - 1); |
| } |
| |
| set_ch_t(&data->beg_ext, begcyl, beghead); |
| set_ch_t(&data->end_ext, endcyl, endhead); |
| return rc; |
| } |
| |
| |
| static void locate_record_ext(struct ccw1 *ccw, struct LRE_eckd_data *data, |
| unsigned int trk, unsigned int rec_on_trk, |
| int count, int cmd, struct dasd_device *device, |
| unsigned int reclen, unsigned int tlf) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int sector; |
| int dn, d; |
| |
| if (ccw) { |
| ccw->cmd_code = DASD_ECKD_CCW_LOCATE_RECORD_EXT; |
| ccw->flags = 0; |
| if (cmd == DASD_ECKD_CCW_WRITE_FULL_TRACK) |
| ccw->count = 22; |
| else |
| ccw->count = 20; |
| ccw->cda = (__u32)__pa(data); |
| } |
| |
| memset(data, 0, sizeof(*data)); |
| sector = 0; |
| if (rec_on_trk) { |
| switch (private->rdc_data.dev_type) { |
| case 0x3390: |
| dn = ceil_quot(reclen + 6, 232); |
| d = 9 + ceil_quot(reclen + 6 * (dn + 1), 34); |
| sector = (49 + (rec_on_trk - 1) * (10 + d)) / 8; |
| break; |
| case 0x3380: |
| d = 7 + ceil_quot(reclen + 12, 32); |
| sector = (39 + (rec_on_trk - 1) * (8 + d)) / 7; |
| break; |
| } |
| } |
| data->sector = sector; |
| /* note: meaning of count depends on the operation |
| * for record based I/O it's the number of records, but for |
| * track based I/O it's the number of tracks |
| */ |
| data->count = count; |
| switch (cmd) { |
| case DASD_ECKD_CCW_WRITE_HOME_ADDRESS: |
| data->operation.orientation = 0x3; |
| data->operation.operation = 0x03; |
| break; |
| case DASD_ECKD_CCW_READ_HOME_ADDRESS: |
| data->operation.orientation = 0x3; |
| data->operation.operation = 0x16; |
| break; |
| case DASD_ECKD_CCW_WRITE_RECORD_ZERO: |
| data->operation.orientation = 0x1; |
| data->operation.operation = 0x03; |
| data->count++; |
| break; |
| case DASD_ECKD_CCW_READ_RECORD_ZERO: |
| data->operation.orientation = 0x3; |
| data->operation.operation = 0x16; |
| data->count++; |
| break; |
| case DASD_ECKD_CCW_WRITE: |
| case DASD_ECKD_CCW_WRITE_MT: |
| case DASD_ECKD_CCW_WRITE_KD: |
| case DASD_ECKD_CCW_WRITE_KD_MT: |
| data->auxiliary.length_valid = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x01; |
| break; |
| case DASD_ECKD_CCW_WRITE_CKD: |
| case DASD_ECKD_CCW_WRITE_CKD_MT: |
| data->auxiliary.length_valid = 0x1; |
| 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 */ |
| data->operation.operation = 0x3F; |
| data->extended_operation = 0x23; |
| break; |
| case DASD_ECKD_CCW_READ: |
| case DASD_ECKD_CCW_READ_MT: |
| case DASD_ECKD_CCW_READ_KD: |
| case DASD_ECKD_CCW_READ_KD_MT: |
| data->auxiliary.length_valid = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x06; |
| break; |
| case DASD_ECKD_CCW_READ_CKD: |
| case DASD_ECKD_CCW_READ_CKD_MT: |
| data->auxiliary.length_valid = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x16; |
| break; |
| 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; |
| data->operation.operation = 0x0C; |
| break; |
| case DASD_ECKD_CCW_ERASE: |
| data->length = reclen; |
| data->auxiliary.length_valid = 0x1; |
| data->operation.operation = 0x0b; |
| break; |
| default: |
| DBF_DEV_EVENT(DBF_ERR, device, |
| "fill LRE unknown opcode 0x%x", cmd); |
| BUG(); |
| } |
| set_ch_t(&data->seek_addr, |
| trk / private->rdc_data.trk_per_cyl, |
| trk % private->rdc_data.trk_per_cyl); |
| data->search_arg.cyl = data->seek_addr.cyl; |
| data->search_arg.head = data->seek_addr.head; |
| data->search_arg.record = rec_on_trk; |
| } |
| |
| static int prefix_LRE(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, |
| unsigned int trk, unsigned int totrk, int cmd, |
| struct dasd_device *basedev, struct dasd_device *startdev, |
| unsigned int format, unsigned int rec_on_trk, int count, |
| unsigned int blksize, unsigned int tlf) |
| { |
| struct dasd_eckd_private *basepriv, *startpriv; |
| struct LRE_eckd_data *lredata; |
| struct DE_eckd_data *dedata; |
| int rc = 0; |
| |
| basepriv = basedev->private; |
| startpriv = startdev->private; |
| dedata = &pfxdata->define_extent; |
| lredata = &pfxdata->locate_record; |
| |
| ccw->cmd_code = DASD_ECKD_CCW_PFX; |
| ccw->flags = 0; |
| 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)); |
| } |
| |
| /* prefix data */ |
| if (format > 1) { |
| DBF_DEV_EVENT(DBF_ERR, basedev, |
| "PFX LRE unknown format 0x%x", format); |
| BUG(); |
| return -EINVAL; |
| } |
| pfxdata->format = format; |
| pfxdata->base_address = basepriv->conf.ned->unit_addr; |
| pfxdata->base_lss = basepriv->conf.ned->ID; |
| pfxdata->validity.define_extent = 1; |
| |
| /* private uid is kept up to date, conf_data may be outdated */ |
| if (startpriv->uid.type == UA_BASE_PAV_ALIAS) |
| pfxdata->validity.verify_base = 1; |
| |
| if (startpriv->uid.type == UA_HYPER_PAV_ALIAS) { |
| pfxdata->validity.verify_base = 1; |
| pfxdata->validity.hyper_pav = 1; |
| } |
| |
| rc = define_extent(NULL, dedata, trk, totrk, cmd, basedev, blksize); |
| |
| /* |
| * For some commands the System Time Stamp is set in the define extent |
| * data when XRC is supported. The validity of the time stamp must be |
| * reflected in the prefix data as well. |
| */ |
| if (dedata->ga_extended & 0x08 && dedata->ga_extended & 0x02) |
| pfxdata->validity.time_stamp = 1; /* 'Time Stamp Valid' */ |
| |
| if (format == 1) { |
| locate_record_ext(NULL, lredata, trk, rec_on_trk, count, cmd, |
| basedev, blksize, tlf); |
| } |
| |
| return rc; |
| } |
| |
| static int prefix(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, |
| unsigned int trk, unsigned int totrk, int cmd, |
| struct dasd_device *basedev, struct dasd_device *startdev) |
| { |
| return prefix_LRE(ccw, pfxdata, trk, totrk, cmd, basedev, startdev, |
| 0, 0, 0, 0, 0); |
| } |
| |
| static void |
| locate_record(struct ccw1 *ccw, struct LO_eckd_data *data, unsigned int trk, |
| unsigned int rec_on_trk, int no_rec, int cmd, |
| struct dasd_device * device, int reclen) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int sector; |
| int dn, d; |
| |
| DBF_DEV_EVENT(DBF_INFO, device, |
| "Locate: trk %d, rec %d, no_rec %d, cmd %d, reclen %d", |
| trk, rec_on_trk, no_rec, cmd, reclen); |
| |
| ccw->cmd_code = DASD_ECKD_CCW_LOCATE_RECORD; |
| ccw->flags = 0; |
| ccw->count = 16; |
| ccw->cda = (__u32) __pa(data); |
| |
| memset(data, 0, sizeof(struct LO_eckd_data)); |
| sector = 0; |
| if (rec_on_trk) { |
| switch (private->rdc_data.dev_type) { |
| case 0x3390: |
| dn = ceil_quot(reclen + 6, 232); |
| d = 9 + ceil_quot(reclen + 6 * (dn + 1), 34); |
| sector = (49 + (rec_on_trk - 1) * (10 + d)) / 8; |
| break; |
| case 0x3380: |
| d = 7 + ceil_quot(reclen + 12, 32); |
| sector = (39 + (rec_on_trk - 1) * (8 + d)) / 7; |
| break; |
| } |
| } |
| data->sector = sector; |
| data->count = no_rec; |
| switch (cmd) { |
| case DASD_ECKD_CCW_WRITE_HOME_ADDRESS: |
| data->operation.orientation = 0x3; |
| data->operation.operation = 0x03; |
| break; |
| case DASD_ECKD_CCW_READ_HOME_ADDRESS: |
| data->operation.orientation = 0x3; |
| data->operation.operation = 0x16; |
| break; |
| case DASD_ECKD_CCW_WRITE_RECORD_ZERO: |
| data->operation.orientation = 0x1; |
| data->operation.operation = 0x03; |
| data->count++; |
| break; |
| case DASD_ECKD_CCW_READ_RECORD_ZERO: |
| data->operation.orientation = 0x3; |
| data->operation.operation = 0x16; |
| data->count++; |
| break; |
| case DASD_ECKD_CCW_WRITE: |
| case DASD_ECKD_CCW_WRITE_MT: |
| case DASD_ECKD_CCW_WRITE_KD: |
| case DASD_ECKD_CCW_WRITE_KD_MT: |
| data->auxiliary.last_bytes_used = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x01; |
| break; |
| case DASD_ECKD_CCW_WRITE_CKD: |
| case DASD_ECKD_CCW_WRITE_CKD_MT: |
| data->auxiliary.last_bytes_used = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x03; |
| break; |
| case DASD_ECKD_CCW_READ: |
| case DASD_ECKD_CCW_READ_MT: |
| case DASD_ECKD_CCW_READ_KD: |
| case DASD_ECKD_CCW_READ_KD_MT: |
| data->auxiliary.last_bytes_used = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x06; |
| break; |
| case DASD_ECKD_CCW_READ_CKD: |
| case DASD_ECKD_CCW_READ_CKD_MT: |
| data->auxiliary.last_bytes_used = 0x1; |
| data->length = reclen; |
| data->operation.operation = 0x16; |
| break; |
| case DASD_ECKD_CCW_READ_COUNT: |
| data->operation.operation = 0x06; |
| break; |
| case DASD_ECKD_CCW_ERASE: |
| data->length = reclen; |
| data->auxiliary.last_bytes_used = 0x1; |
| data->operation.operation = 0x0b; |
| break; |
| default: |
| DBF_DEV_EVENT(DBF_ERR, device, "unknown locate record " |
| "opcode 0x%x", cmd); |
| } |
| set_ch_t(&data->seek_addr, |
| trk / private->rdc_data.trk_per_cyl, |
| trk % private->rdc_data.trk_per_cyl); |
| data->search_arg.cyl = data->seek_addr.cyl; |
| data->search_arg.head = data->seek_addr.head; |
| data->search_arg.record = rec_on_trk; |
| } |
| |
| /* |
| * Returns 1 if the block is one of the special blocks that needs |
| * to get read/written with the KD variant of the command. |
| * That is DASD_ECKD_READ_KD_MT instead of DASD_ECKD_READ_MT and |
| * DASD_ECKD_WRITE_KD_MT instead of DASD_ECKD_WRITE_MT. |
| * Luckily the KD variants differ only by one bit (0x08) from the |
| * normal variant. So don't wonder about code like: |
| * if (dasd_eckd_cdl_special(blk_per_trk, recid)) |
| * ccw->cmd_code |= 0x8; |
| */ |
| static inline int |
| dasd_eckd_cdl_special(int blk_per_trk, int recid) |
| { |
| if (recid < 3) |
| return 1; |
| if (recid < blk_per_trk) |
| return 0; |
| if (recid < 2 * blk_per_trk) |
| return 1; |
| return 0; |
| } |
| |
| /* |
| * Returns the record size for the special blocks of the cdl format. |
| * Only returns something useful if dasd_eckd_cdl_special is true |
| * for the recid. |
| */ |
| static inline int |
| dasd_eckd_cdl_reclen(int recid) |
| { |
| if (recid < 3) |
| return sizes_trk0[recid]; |
| return LABEL_SIZE; |
| } |
| /* create unique id from private structure. */ |
| static void create_uid(struct dasd_conf *conf, struct dasd_uid *uid) |
| { |
| int count; |
| |
| memset(uid, 0, sizeof(struct dasd_uid)); |
| memcpy(uid->vendor, conf->ned->HDA_manufacturer, |
| sizeof(uid->vendor) - 1); |
| EBCASC(uid->vendor, sizeof(uid->vendor) - 1); |
| memcpy(uid->serial, &conf->ned->serial, |
| sizeof(uid->serial) - 1); |
| EBCASC(uid->serial, sizeof(uid->serial) - 1); |
| uid->ssid = conf->gneq->subsystemID; |
| uid->real_unit_addr = conf->ned->unit_addr; |
| if (conf->sneq) { |
| uid->type = conf->sneq->sua_flags; |
| if (uid->type == UA_BASE_PAV_ALIAS) |
| uid->base_unit_addr = conf->sneq->base_unit_addr; |
| } else { |
| uid->type = UA_BASE_DEVICE; |
| } |
| if (conf->vdsneq) { |
| for (count = 0; count < 16; count++) { |
| sprintf(uid->vduit+2*count, "%02x", |
| conf->vdsneq->uit[count]); |
| } |
| } |
| } |
| |
| /* |
| * Generate device unique id that specifies the physical device. |
| */ |
| static int dasd_eckd_generate_uid(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| unsigned long flags; |
| |
| if (!private) |
| return -ENODEV; |
| if (!private->conf.ned || !private->conf.gneq) |
| return -ENODEV; |
| spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); |
| create_uid(&private->conf, &private->uid); |
| spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); |
| return 0; |
| } |
| |
| static int dasd_eckd_get_uid(struct dasd_device *device, struct dasd_uid *uid) |
| { |
| struct dasd_eckd_private *private = device->private; |
| unsigned long flags; |
| |
| if (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_conf *path_conf) |
| { |
| struct dasd_uid device_uid; |
| struct dasd_uid path_uid; |
| |
| create_uid(path_conf, &path_uid); |
| dasd_eckd_get_uid(device, &device_uid); |
| |
| return memcmp(&device_uid, &path_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 = DASD_ECKD_CCW_RCD; |
| ccw->flags = 0; |
| ccw->cda = (__u32)(addr_t)rcd_buffer; |
| 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; |
| cqr->retries = 256; |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| 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, |
| void **rcd_buffer, |
| int *rcd_buffer_size, __u8 lpm) |
| { |
| struct ciw *ciw; |
| char *rcd_buf = NULL; |
| int ret; |
| struct dasd_ccw_req *cqr; |
| |
| /* |
| * 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) { |
| ret = -EOPNOTSUPP; |
| goto out_error; |
| } |
| rcd_buf = kzalloc(DASD_ECKD_RCD_DATA_SIZE, GFP_KERNEL | GFP_DMA); |
| if (!rcd_buf) { |
| ret = -ENOMEM; |
| goto out_error; |
| } |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* RCD */, |
| 0, /* use rcd_buf as data ara */ |
| device, NULL); |
| if (IS_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 |
| */ |
| dasd_sfree_request(cqr, cqr->memdev); |
| if (ret) |
| goto out_error; |
| |
| *rcd_buffer_size = DASD_ECKD_RCD_DATA_SIZE; |
| *rcd_buffer = rcd_buf; |
| return 0; |
| out_error: |
| kfree(rcd_buf); |
| *rcd_buffer = NULL; |
| *rcd_buffer_size = 0; |
| return ret; |
| } |
| |
| static int dasd_eckd_identify_conf_parts(struct dasd_conf *conf) |
| { |
| |
| struct dasd_sneq *sneq; |
| int i, count; |
| |
| conf->ned = NULL; |
| conf->sneq = NULL; |
| conf->vdsneq = NULL; |
| conf->gneq = NULL; |
| count = conf->len / sizeof(struct dasd_sneq); |
| sneq = (struct dasd_sneq *)conf->data; |
| for (i = 0; i < count; ++i) { |
| if (sneq->flags.identifier == 1 && sneq->format == 1) |
| conf->sneq = sneq; |
| else if (sneq->flags.identifier == 1 && sneq->format == 4) |
| conf->vdsneq = (struct vd_sneq *)sneq; |
| else if (sneq->flags.identifier == 2) |
| conf->gneq = (struct dasd_gneq *)sneq; |
| else if (sneq->flags.identifier == 3 && sneq->res1 == 1) |
| conf->ned = (struct dasd_ned *)sneq; |
| sneq++; |
| } |
| if (!conf->ned || !conf->gneq) { |
| conf->ned = NULL; |
| conf->sneq = NULL; |
| conf->vdsneq = NULL; |
| conf->gneq = NULL; |
| return -EINVAL; |
| } |
| return 0; |
| |
| }; |
| |
| static unsigned char dasd_eckd_path_access(void *conf_data, int conf_len) |
| { |
| struct dasd_gneq *gneq; |
| int i, count, found; |
| |
| count = conf_len / sizeof(*gneq); |
| gneq = (struct dasd_gneq *)conf_data; |
| found = 0; |
| for (i = 0; i < count; ++i) { |
| if (gneq->flags.identifier == 2) { |
| found = 1; |
| break; |
| } |
| gneq++; |
| } |
| if (found) |
| return ((char *)gneq)[18] & 0x07; |
| else |
| return 0; |
| } |
| |
| static void dasd_eckd_store_conf_data(struct dasd_device *device, |
| struct dasd_conf_data *conf_data, int chp) |
| { |
| struct dasd_eckd_private *private = device->private; |
| struct channel_path_desc_fmt0 *chp_desc; |
| struct subchannel_id sch_id; |
| void *cdp; |
| |
| /* |
| * path handling and read_conf allocate data |
| * free it before replacing the pointer |
| * also replace the old private->conf_data pointer |
| * with the new one if this points to the same data |
| */ |
| cdp = device->path[chp].conf_data; |
| if (private->conf.data == cdp) { |
| private->conf.data = (void *)conf_data; |
| dasd_eckd_identify_conf_parts(&private->conf); |
| } |
| ccw_device_get_schid(device->cdev, &sch_id); |
| device->path[chp].conf_data = conf_data; |
| device->path[chp].cssid = sch_id.cssid; |
| device->path[chp].ssid = sch_id.ssid; |
| chp_desc = ccw_device_get_chp_desc(device->cdev, chp); |
| if (chp_desc) |
| device->path[chp].chpid = chp_desc->chpid; |
| kfree(chp_desc); |
| kfree(cdp); |
| } |
| |
| static void dasd_eckd_clear_conf_data(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int i; |
| |
| private->conf.data = NULL; |
| private->conf.len = 0; |
| for (i = 0; i < 8; i++) { |
| kfree(device->path[i].conf_data); |
| device->path[i].conf_data = NULL; |
| device->path[i].cssid = 0; |
| device->path[i].ssid = 0; |
| device->path[i].chpid = 0; |
| dasd_path_notoper(device, i); |
| } |
| } |
| |
| static void dasd_eckd_read_fc_security(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| u8 esm_valid; |
| u8 esm[8]; |
| int chp; |
| int rc; |
| |
| rc = chsc_scud(private->uid.ssid, (u64 *)esm, &esm_valid); |
| if (rc) { |
| for (chp = 0; chp < 8; chp++) |
| device->path[chp].fc_security = 0; |
| return; |
| } |
| |
| for (chp = 0; chp < 8; chp++) { |
| if (esm_valid & (0x80 >> chp)) |
| device->path[chp].fc_security = esm[chp]; |
| else |
| device->path[chp].fc_security = 0; |
| } |
| } |
| |
| static void dasd_eckd_get_uid_string(struct dasd_conf *conf, |
| char *print_uid) |
| { |
| struct dasd_uid uid; |
| |
| create_uid(conf, &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); |
| } |
| |
| static int dasd_eckd_check_cabling(struct dasd_device *device, |
| void *conf_data, __u8 lpm) |
| { |
| struct dasd_eckd_private *private = device->private; |
| char print_path_uid[60], print_device_uid[60]; |
| struct dasd_conf path_conf; |
| |
| path_conf.data = conf_data; |
| path_conf.len = DASD_ECKD_RCD_DATA_SIZE; |
| if (dasd_eckd_identify_conf_parts(&path_conf)) |
| return 1; |
| |
| if (dasd_eckd_compare_path_uid(device, &path_conf)) { |
| dasd_eckd_get_uid_string(&path_conf, print_path_uid); |
| dasd_eckd_get_uid_string(&private->conf, print_device_uid); |
| 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); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int dasd_eckd_read_conf(struct dasd_device *device) |
| { |
| void *conf_data; |
| int conf_len, conf_data_saved; |
| int rc, path_err, pos; |
| __u8 lpm, opm; |
| struct dasd_eckd_private *private; |
| |
| private = device->private; |
| 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 & 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 */ |
| dasd_path_add_opm(device, opm); |
| continue; /* no error */ |
| } |
| /* save first valid configuration data */ |
| if (!conf_data_saved) { |
| /* initially clear previously stored conf_data */ |
| dasd_eckd_clear_conf_data(device); |
| private->conf.data = conf_data; |
| private->conf.len = conf_len; |
| if (dasd_eckd_identify_conf_parts(&private->conf)) { |
| private->conf.data = NULL; |
| private->conf.len = 0; |
| kfree(conf_data); |
| continue; |
| } |
| /* |
| * build device UID that other path data |
| * can be compared to it |
| */ |
| dasd_eckd_generate_uid(device); |
| conf_data_saved++; |
| } else if (dasd_eckd_check_cabling(device, conf_data, lpm)) { |
| dasd_path_add_cablepm(device, lpm); |
| path_err = -EINVAL; |
| kfree(conf_data); |
| continue; |
| } |
| |
| pos = pathmask_to_pos(lpm); |
| dasd_eckd_store_conf_data(device, conf_data, pos); |
| |
| switch (dasd_eckd_path_access(conf_data, conf_len)) { |
| case 0x02: |
| dasd_path_add_nppm(device, lpm); |
| break; |
| case 0x03: |
| dasd_path_add_ppm(device, lpm); |
| break; |
| } |
| if (!dasd_path_get_opm(device)) { |
| dasd_path_set_opm(device, lpm); |
| dasd_generic_path_operational(device); |
| } else { |
| dasd_path_add_opm(device, lpm); |
| } |
| } |
| |
| return path_err; |
| } |
| |
| static u32 get_fcx_max_data(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int fcx_in_css, fcx_in_gneq, fcx_in_features; |
| unsigned int mdc; |
| int tpm; |
| |
| if (dasd_nofcx) |
| return 0; |
| /* is transport mode supported? */ |
| fcx_in_css = css_general_characteristics.fcx; |
| fcx_in_gneq = private->conf.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 (u32)mdc * FCX_MAX_DATA_FACTOR; |
| } |
| } |
| |
| static int verify_fcx_max_data(struct dasd_device *device, __u8 lpm) |
| { |
| struct dasd_eckd_private *private = device->private; |
| unsigned int mdc; |
| u32 fcx_max_data; |
| |
| 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 = (u32)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 pe_handler_work_data *data) |
| { |
| struct dasd_eckd_private *private = device->private; |
| __u8 lpm, opm = dasd_path_get_opm(device); |
| int rc = -ENODEV; |
| |
| 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->conf)) { |
| rc = -ENODEV; |
| } else /* first valid path is enough */ |
| break; |
| } |
| |
| if (!rc) |
| rc = dasd_eckd_generate_uid(device); |
| |
| return rc; |
| } |
| |
| static void dasd_eckd_path_available_action(struct dasd_device *device, |
| struct pe_handler_work_data *data) |
| { |
| __u8 path_rcd_buf[DASD_ECKD_RCD_DATA_SIZE]; |
| __u8 lpm, opm, npm, ppm, epm, hpfpm, cablepm; |
| struct dasd_conf_data *conf_data; |
| struct dasd_conf path_conf; |
| unsigned long flags; |
| char print_uid[60]; |
| int rc, pos; |
| |
| opm = 0; |
| npm = 0; |
| ppm = 0; |
| epm = 0; |
| hpfpm = 0; |
| cablepm = 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: |
| npm |= lpm; |
| break; |
| case 0x03: |
| ppm |= lpm; |
| break; |
| } |
| 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; |
| hpfpm |= 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_conf.data = (void *)&path_rcd_buf; |
| path_conf.len = DASD_ECKD_RCD_DATA_SIZE; |
| if (dasd_eckd_identify_conf_parts(&path_conf)) { |
| path_conf.data = NULL; |
| path_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 (dasd_path_get_opm(device) && |
| dasd_eckd_compare_path_uid(device, &path_conf)) { |
| /* |
| * 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_conf)) { |
| dasd_eckd_get_uid_string(&path_conf, print_uid); |
| 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; |
| cablepm |= lpm; |
| continue; |
| } |
| } |
| |
| conf_data = kzalloc(DASD_ECKD_RCD_DATA_SIZE, GFP_KERNEL); |
| if (conf_data) { |
| memcpy(conf_data, data->rcd_buffer, |
| DASD_ECKD_RCD_DATA_SIZE); |
| } else { |
| /* |
| * path is operational but path config data could not |
| * be stored due to low mem condition |
| * add it to the error path mask and schedule a path |
| * verification later that this could be added again |
| */ |
| epm |= lpm; |
| } |
| pos = pathmask_to_pos(lpm); |
| dasd_eckd_store_conf_data(device, conf_data, pos); |
| |
| /* |
| * 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 (!dasd_path_get_opm(device) && opm) { |
| dasd_path_set_opm(device, opm); |
| dasd_generic_path_operational(device); |
| } else { |
| dasd_path_add_opm(device, opm); |
| } |
| dasd_path_add_nppm(device, npm); |
| dasd_path_add_ppm(device, ppm); |
| if (epm) { |
| dasd_path_add_tbvpm(device, epm); |
| dasd_device_set_timer(device, 50); |
| } |
| dasd_path_add_cablepm(device, cablepm); |
| dasd_path_add_nohpfpm(device, hpfpm); |
| spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); |
| |
| dasd_path_create_kobj(device, pos); |
| } |
| } |
| |
| static void do_pe_handler_work(struct work_struct *work) |
| { |
| struct pe_handler_work_data *data; |
| struct dasd_device *device; |
| |
| data = container_of(work, struct pe_handler_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; |
| } |
| /* check if path verification already running and delay if so */ |
| if (test_and_set_bit(DASD_FLAG_PATH_VERIFY, &device->flags)) { |
| schedule_work(work); |
| return; |
| } |
| |
| if (data->tbvpm) |
| dasd_eckd_path_available_action(device, data); |
| if (data->fcsecpm) |
| dasd_eckd_read_fc_security(device); |
| |
| clear_bit(DASD_FLAG_PATH_VERIFY, &device->flags); |
| dasd_put_device(device); |
| if (data->isglobal) |
| mutex_unlock(&dasd_pe_handler_mutex); |
| else |
| kfree(data); |
| } |
| |
| static int dasd_eckd_pe_handler(struct dasd_device *device, |
| __u8 tbvpm, __u8 fcsecpm) |
| { |
| struct pe_handler_work_data *data; |
| |
| data = kzalloc(sizeof(*data), GFP_ATOMIC | GFP_DMA); |
| if (!data) { |
| if (mutex_trylock(&dasd_pe_handler_mutex)) { |
| data = pe_handler_worker; |
| data->isglobal = 1; |
| } else { |
| return -ENOMEM; |
| } |
| } |
| INIT_WORK(&data->worker, do_pe_handler_work); |
| dasd_get_device(device); |
| data->device = device; |
| data->tbvpm = tbvpm; |
| data->fcsecpm = fcsecpm; |
| schedule_work(&data->worker); |
| return 0; |
| } |
| |
| static void dasd_eckd_reset_path(struct dasd_device *device, __u8 pm) |
| { |
| struct dasd_eckd_private *private = device->private; |
| unsigned long flags; |
| |
| if (!private->fcx_max_data) |
| private->fcx_max_data = get_fcx_max_data(device); |
| spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); |
| dasd_path_set_tbvpm(device, pm ? : dasd_path_get_notoperpm(device)); |
| dasd_schedule_device_bh(device); |
| spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); |
| } |
| |
| static int dasd_eckd_read_features(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| struct dasd_psf_prssd_data *prssdp; |
| struct dasd_rssd_features *features; |
| struct dasd_ccw_req *cqr; |
| struct ccw1 *ccw; |
| int rc; |
| |
| 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, NULL); |
| if (IS_ERR(cqr)) { |
| 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; |
| cqr->retries = 256; |
| cqr->expires = 10 * HZ; |
| |
| /* Prepare for Read Subsystem Data */ |
| prssdp = (struct dasd_psf_prssd_data *) cqr->data; |
| memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data)); |
| prssdp->order = PSF_ORDER_PRSSD; |
| prssdp->suborder = 0x41; /* Read Feature Codes */ |
| /* all other bytes of prssdp must be zero */ |
| |
| ccw = cqr->cpaddr; |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; |
| ccw->count = sizeof(struct dasd_psf_prssd_data); |
| ccw->flags |= CCW_FLAG_CC; |
| ccw->cda = (__u32)(addr_t) prssdp; |
| |
| /* Read Subsystem Data - feature codes */ |
| features = (struct dasd_rssd_features *) (prssdp + 1); |
| memset(features, 0, sizeof(struct dasd_rssd_features)); |
| |
| ccw++; |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; |
| ccw->count = sizeof(struct dasd_rssd_features); |
| ccw->cda = (__u32)(addr_t) features; |
| |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| rc = dasd_sleep_on(cqr); |
| if (rc == 0) { |
| prssdp = (struct dasd_psf_prssd_data *) cqr->data; |
| 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; |
| } |
| |
| /* Read Volume Information - Volume Storage Query */ |
| static int dasd_eckd_read_vol_info(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| struct dasd_psf_prssd_data *prssdp; |
| struct dasd_rssd_vsq *vsq; |
| struct dasd_ccw_req *cqr; |
| struct ccw1 *ccw; |
| int useglobal; |
| int rc; |
| |
| /* This command cannot be executed on an alias device */ |
| if (private->uid.type == UA_BASE_PAV_ALIAS || |
| private->uid.type == UA_HYPER_PAV_ALIAS) |
| return 0; |
| |
| useglobal = 0; |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 2 /* PSF + RSSD */, |
| sizeof(*prssdp) + sizeof(*vsq), device, NULL); |
| if (IS_ERR(cqr)) { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", |
| "Could not allocate initialization request"); |
| mutex_lock(&dasd_vol_info_mutex); |
| useglobal = 1; |
| cqr = &dasd_vol_info_req->cqr; |
| memset(cqr, 0, sizeof(*cqr)); |
| memset(dasd_vol_info_req, 0, sizeof(*dasd_vol_info_req)); |
| cqr->cpaddr = &dasd_vol_info_req->ccw; |
| cqr->data = &dasd_vol_info_req->data; |
| cqr->magic = DASD_ECKD_MAGIC; |
| } |
| |
| /* Prepare for Read Subsystem Data */ |
| prssdp = cqr->data; |
| prssdp->order = PSF_ORDER_PRSSD; |
| prssdp->suborder = PSF_SUBORDER_VSQ; /* Volume Storage Query */ |
| prssdp->lss = private->conf.ned->ID; |
| prssdp->volume = private->conf.ned->unit_addr; |
| |
| ccw = cqr->cpaddr; |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; |
| ccw->count = sizeof(*prssdp); |
| ccw->flags |= CCW_FLAG_CC; |
| ccw->cda = (__u32)(addr_t)prssdp; |
| |
| /* Read Subsystem Data - Volume Storage Query */ |
| vsq = (struct dasd_rssd_vsq *)(prssdp + 1); |
| memset(vsq, 0, sizeof(*vsq)); |
| |
| ccw++; |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; |
| ccw->count = sizeof(*vsq); |
| ccw->flags |= CCW_FLAG_SLI; |
| ccw->cda = (__u32)(addr_t)vsq; |
| |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| cqr->startdev = device; |
| cqr->memdev = device; |
| cqr->block = NULL; |
| cqr->retries = 256; |
| cqr->expires = device->default_expires * HZ; |
| /* The command might not be supported. Suppress the error output */ |
| __set_bit(DASD_CQR_SUPPRESS_CR, &cqr->flags); |
| |
| rc = dasd_sleep_on_interruptible(cqr); |
| if (rc == 0) { |
| memcpy(&private->vsq, vsq, sizeof(*vsq)); |
| } else { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, |
| "Reading the volume storage information failed with rc=%d", rc); |
| } |
| |
| if (useglobal) |
| mutex_unlock(&dasd_vol_info_mutex); |
| else |
| dasd_sfree_request(cqr, cqr->memdev); |
| |
| return rc; |
| } |
| |
| static int dasd_eckd_is_ese(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->vsq.vol_info.ese; |
| } |
| |
| static int dasd_eckd_ext_pool_id(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->vsq.extent_pool_id; |
| } |
| |
| /* |
| * This value represents the total amount of available space. As more space is |
| * allocated by ESE volumes, this value will decrease. |
| * The data for this value is therefore updated on any call. |
| */ |
| static int dasd_eckd_space_configured(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int rc; |
| |
| rc = dasd_eckd_read_vol_info(device); |
| |
| return rc ? : private->vsq.space_configured; |
| } |
| |
| /* |
| * The value of space allocated by an ESE volume may have changed and is |
| * therefore updated on any call. |
| */ |
| static int dasd_eckd_space_allocated(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int rc; |
| |
| rc = dasd_eckd_read_vol_info(device); |
| |
| return rc ? : private->vsq.space_allocated; |
| } |
| |
| static int dasd_eckd_logical_capacity(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->vsq.logical_capacity; |
| } |
| |
| static void dasd_eckd_ext_pool_exhaust_work(struct work_struct *work) |
| { |
| struct ext_pool_exhaust_work_data *data; |
| struct dasd_device *device; |
| struct dasd_device *base; |
| |
| data = container_of(work, struct ext_pool_exhaust_work_data, worker); |
| device = data->device; |
| base = data->base; |
| |
| if (!base) |
| base = device; |
| if (dasd_eckd_space_configured(base) != 0) { |
| dasd_generic_space_avail(device); |
| } else { |
| dev_warn(&device->cdev->dev, "No space left in the extent pool\n"); |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", "out of space"); |
| } |
| |
| dasd_put_device(device); |
| kfree(data); |
| } |
| |
| static int dasd_eckd_ext_pool_exhaust(struct dasd_device *device, |
| struct dasd_ccw_req *cqr) |
| { |
| struct ext_pool_exhaust_work_data *data; |
| |
| data = kzalloc(sizeof(*data), GFP_ATOMIC); |
| if (!data) |
| return -ENOMEM; |
| INIT_WORK(&data->worker, dasd_eckd_ext_pool_exhaust_work); |
| dasd_get_device(device); |
| data->device = device; |
| |
| if (cqr->block) |
| data->base = cqr->block->base; |
| else if (cqr->basedev) |
| data->base = cqr->basedev; |
| else |
| data->base = NULL; |
| |
| schedule_work(&data->worker); |
| |
| return 0; |
| } |
| |
| static void dasd_eckd_cpy_ext_pool_data(struct dasd_device *device, |
| struct dasd_rssd_lcq *lcq) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int pool_id = dasd_eckd_ext_pool_id(device); |
| struct dasd_ext_pool_sum eps; |
| int i; |
| |
| for (i = 0; i < lcq->pool_count; i++) { |
| eps = lcq->ext_pool_sum[i]; |
| if (eps.pool_id == pool_id) { |
| memcpy(&private->eps, &eps, |
| sizeof(struct dasd_ext_pool_sum)); |
| } |
| } |
| } |
| |
| /* Read Extent Pool Information - Logical Configuration Query */ |
| static int dasd_eckd_read_ext_pool_info(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| struct dasd_psf_prssd_data *prssdp; |
| struct dasd_rssd_lcq *lcq; |
| struct dasd_ccw_req *cqr; |
| struct ccw1 *ccw; |
| int rc; |
| |
| /* This command cannot be executed on an alias device */ |
| if (private->uid.type == UA_BASE_PAV_ALIAS || |
| private->uid.type == UA_HYPER_PAV_ALIAS) |
| return 0; |
| |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 2 /* PSF + RSSD */, |
| sizeof(*prssdp) + sizeof(*lcq), device, NULL); |
| if (IS_ERR(cqr)) { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", |
| "Could not allocate initialization request"); |
| return PTR_ERR(cqr); |
| } |
| |
| /* Prepare for Read Subsystem Data */ |
| prssdp = cqr->data; |
| memset(prssdp, 0, sizeof(*prssdp)); |
| prssdp->order = PSF_ORDER_PRSSD; |
| prssdp->suborder = PSF_SUBORDER_LCQ; /* Logical Configuration Query */ |
| |
| ccw = cqr->cpaddr; |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; |
| ccw->count = sizeof(*prssdp); |
| ccw->flags |= CCW_FLAG_CC; |
| ccw->cda = (__u32)(addr_t)prssdp; |
| |
| lcq = (struct dasd_rssd_lcq *)(prssdp + 1); |
| memset(lcq, 0, sizeof(*lcq)); |
| |
| ccw++; |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; |
| ccw->count = sizeof(*lcq); |
| ccw->flags |= CCW_FLAG_SLI; |
| ccw->cda = (__u32)(addr_t)lcq; |
| |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| cqr->startdev = device; |
| cqr->memdev = device; |
| cqr->block = NULL; |
| cqr->retries = 256; |
| cqr->expires = device->default_expires * HZ; |
| /* The command might not be supported. Suppress the error output */ |
| __set_bit(DASD_CQR_SUPPRESS_CR, &cqr->flags); |
| |
| rc = dasd_sleep_on_interruptible(cqr); |
| if (rc == 0) { |
| dasd_eckd_cpy_ext_pool_data(device, lcq); |
| } else { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, |
| "Reading the logical configuration failed with rc=%d", rc); |
| } |
| |
| dasd_sfree_request(cqr, cqr->memdev); |
| |
| return rc; |
| } |
| |
| /* |
| * Depending on the device type, the extent size is specified either as |
| * cylinders per extent (CKD) or size per extent (FBA) |
| * A 1GB size corresponds to 1113cyl, and 16MB to 21cyl. |
| */ |
| static int dasd_eckd_ext_size(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| struct dasd_ext_pool_sum eps = private->eps; |
| |
| if (!eps.flags.extent_size_valid) |
| return 0; |
| if (eps.extent_size.size_1G) |
| return 1113; |
| if (eps.extent_size.size_16M) |
| return 21; |
| |
| return 0; |
| } |
| |
| static int dasd_eckd_ext_pool_warn_thrshld(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->eps.warn_thrshld; |
| } |
| |
| static int dasd_eckd_ext_pool_cap_at_warnlevel(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->eps.flags.capacity_at_warnlevel; |
| } |
| |
| /* |
| * Extent Pool out of space |
| */ |
| static int dasd_eckd_ext_pool_oos(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->eps.flags.pool_oos; |
| } |
| |
| /* |
| * Build CP for Perform Subsystem Function - SSC. |
| */ |
| static struct dasd_ccw_req *dasd_eckd_build_psf_ssc(struct dasd_device *device, |
| int enable_pav) |
| { |
| struct dasd_ccw_req *cqr; |
| struct dasd_psf_ssc_data *psf_ssc_data; |
| struct ccw1 *ccw; |
| |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ , |
| sizeof(struct dasd_psf_ssc_data), |
| device, NULL); |
| |
| if (IS_ERR(cqr)) { |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", |
| "Could not allocate PSF-SSC request"); |
| return cqr; |
| } |
| psf_ssc_data = (struct dasd_psf_ssc_data *)cqr->data; |
| psf_ssc_data->order = PSF_ORDER_SSC; |
| psf_ssc_data->suborder = 0xc0; |
| if (enable_pav) { |
| psf_ssc_data->suborder |= 0x08; |
| psf_ssc_data->reserved[0] = 0x88; |
| } |
| ccw = cqr->cpaddr; |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; |
| ccw->cda = (__u32)(addr_t)psf_ssc_data; |
| ccw->count = 66; |
| |
| cqr->startdev = device; |
| cqr->memdev = device; |
| cqr->block = NULL; |
| cqr->retries = 256; |
| cqr->expires = 10*HZ; |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| return cqr; |
| } |
| |
| /* |
| * Perform Subsystem Function. |
| * It is necessary to trigger CIO for channel revalidation since this |
| * call might change behaviour of DASD devices. |
| */ |
| static int |
| dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav, |
| unsigned long flags) |
| { |
| struct dasd_ccw_req *cqr; |
| int rc; |
| |
| cqr = dasd_eckd_build_psf_ssc(device, 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; |
| } |
| |
| /* |
| * Valide storage server of current device. |
| */ |
| static int dasd_eckd_validate_server(struct dasd_device *device, |
| unsigned long flags) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int enable_pav, rc; |
| |
| 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, 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. */ |
| if (!schedule_work(&device->kick_validate)) |
| dasd_put_device(device); |
| } |
| |
| /* |
| * return if the device is the copy relation primary if a copy relation is active |
| */ |
| static int dasd_device_is_primary(struct dasd_device *device) |
| { |
| if (!device->copy) |
| return 1; |
| |
| if (device->copy->active->device == device) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int dasd_eckd_alloc_block(struct dasd_device *device) |
| { |
| struct dasd_block *block; |
| struct dasd_uid temp_uid; |
| |
| if (!dasd_device_is_primary(device)) |
| return 0; |
| |
| dasd_eckd_get_uid(device, &temp_uid); |
| if (temp_uid.type == UA_BASE_DEVICE) { |
| block = dasd_alloc_block(); |
| if (IS_ERR(block)) { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", |
| "could not allocate dasd block structure"); |
| return PTR_ERR(block); |
| } |
| device->block = block; |
| block->base = device; |
| } |
| return 0; |
| } |
| |
| static bool dasd_eckd_pprc_enabled(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| return private->rdc_data.facilities.PPRC_enabled; |
| } |
| |
| /* |
| * Check device characteristics. |
| * If the device is accessible using ECKD discipline, the device is enabled. |
| */ |
| static int |
| dasd_eckd_check_characteristics(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| int rc, i; |
| int readonly; |
| unsigned long value; |
| |
| /* setup work queue for validate server*/ |
| INIT_WORK(&device->kick_validate, dasd_eckd_do_validate_server); |
| /* setup work queue for summary unit check */ |
| INIT_WORK(&device->suc_work, dasd_alias_handle_summary_unit_check); |
| |
| 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"); |
| } |
| 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 = private; |
| } else { |
| memset(private, 0, sizeof(*private)); |
| } |
| /* Invalidate status of initial analysis. */ |
| private->init_cqr_status = -1; |
| /* Set default cache operations. */ |
| private->attrib.operation = DASD_NORMAL_CACHE; |
| private->attrib.nr_cyl = 0; |
| |
| /* Read Configuration Data */ |
| rc = dasd_eckd_read_conf(device); |
| if (rc) |
| goto out_err1; |
| |
| /* set some default values */ |
| device->default_expires = DASD_EXPIRES; |
| device->default_retries = DASD_RETRIES; |
| device->path_thrhld = DASD_ECKD_PATH_THRHLD; |
| device->path_interval = DASD_ECKD_PATH_INTERVAL; |
| |
| if (private->conf.gneq) { |
| value = 1; |
| for (i = 0; i < private->conf.gneq->timeout.value; i++) |
| value = 10 * value; |
| value = value * private->conf.gneq->timeout.number; |
| /* do not accept useless values */ |
| if (value != 0 && value <= DASD_EXPIRES_MAX) |
| device->default_expires = value; |
| } |
| |
| /* Read Device Characteristics */ |
| rc = dasd_generic_read_dev_chars(device, DASD_ECKD_MAGIC, |
| &private->rdc_data, 64); |
| if (rc) { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, |
| "Read device characteristic failed, rc=%d", rc); |
| goto out_err1; |
| } |
| |
| /* setup PPRC for device from devmap */ |
| rc = dasd_devmap_set_device_copy_relation(device->cdev, |
| dasd_eckd_pprc_enabled(device)); |
| if (rc) { |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, |
| "copy relation setup failed, rc=%d", rc); |
| goto out_err1; |
| } |
| |
| /* check if block device is needed and allocate in case */ |
| rc = dasd_eckd_alloc_block(device); |
| if (rc) |
| goto out_err1; |
| |
| /* register lcu with alias handling, enable PAV */ |
| rc = dasd_alias_make_device_known_to_lcu(device); |
| if (rc) |
| goto out_err2; |
| |
| 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; |
| |
| dasd_eckd_read_fc_security(device); |
| dasd_path_create_kobjects(device); |
| |
| /* Read Feature Codes */ |
| dasd_eckd_read_features(device); |
| |
| /* Read Volume Information */ |
| dasd_eckd_read_vol_info(device); |
| |
| /* Read Extent Pool Information */ |
| dasd_eckd_read_ext_pool_info(device); |
| |
| 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%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->rdc_data.trk_per_cyl, |
| private->rdc_data.sec_per_trk, |
| readonly ? ", read-only device" : ""); |
| return 0; |
| |
| out_err3: |
| dasd_alias_disconnect_device_from_lcu(device); |
| out_err2: |
| dasd_free_block(device->block); |
| device->block = NULL; |
| out_err1: |
| dasd_eckd_clear_conf_data(device); |
| dasd_path_remove_kobjects(device); |
| kfree(device->private); |
| device->private = NULL; |
| return rc; |
| } |
| |
| static void dasd_eckd_uncheck_device(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| |
| if (!private) |
| return; |
| |
| dasd_alias_disconnect_device_from_lcu(device); |
| private->conf.ned = NULL; |
| private->conf.sneq = NULL; |
| private->conf.vdsneq = NULL; |
| private->conf.gneq = NULL; |
| dasd_eckd_clear_conf_data(device); |
| dasd_path_remove_kobjects(device); |
| } |
| |
| static struct dasd_ccw_req * |
| dasd_eckd_analysis_ccw(struct dasd_device *device) |
| { |
| struct dasd_eckd_private *private = device->private; |
| struct eckd_count *count_data; |
| struct LO_eckd_data *LO_data; |
| struct dasd_ccw_req *cqr; |
| struct ccw1 *ccw; |
| int cplength, datasize; |
| int i; |
| |
| cplength = 8; |
| datasize = sizeof(struct DE_eckd_data) + 2*sizeof(struct LO_eckd_data); |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, device, |
| NULL); |
| if (IS_ERR(cqr)) |
| return cqr; |
| ccw = cqr->cpaddr; |
| /* Define extent for the first 2 tracks. */ |
| define_extent(ccw++, cqr->data, 0, 1, |
| DASD_ECKD_CCW_READ_COUNT, device, 0); |
| LO_data = cqr->data + sizeof(struct DE_eckd_data); |
| /* Locate record for the first 4 records on track 0. */ |
| ccw[-1].flags |= CCW_FLAG_CC; |
| locate_record(ccw++, LO_data++, 0, 0, 4, |
| DASD_ECKD_CCW_READ_COUNT, device, 0); |
| |
| count_data = private->count_area; |
| for (i = 0; i < 4; i++) { |
| ccw[-1].flags |= CCW_FLAG_CC; |
| ccw->cmd_code = DASD_ECKD_CCW_READ_COUNT; |
| ccw->flags = 0; |
| ccw->count = 8; |
| ccw->cda = (__u32)(addr_t) count_data; |
| ccw++; |
| count_data++; |
| } |
| |
| /* Locate record for the first record on track 1. */ |
| ccw[-1].flags |= CCW_FLAG_CC; |
| locate_record(ccw++, LO_data++, 1, 0, 1, |
| DASD_ECKD_CCW_READ_COUNT, device, 0); |
| /* Read count ccw. */ |
| ccw[-1].flags |= CCW_FLAG_CC; |
| ccw->cmd_code = DASD_ECKD_CCW_READ_COUNT; |
| ccw->flags = 0; |
| ccw->count = 8; |
| ccw->cda = (__u32)(addr_t) count_data; |
| |
| cqr->block = NULL; |
| cqr->startdev = device; |
| cqr->memdev = device; |
| cqr->retries = 255; |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| /* Set flags to suppress output for expected errors */ |
| set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); |
| |
| 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 |
| * the device to continue the startup sequence. This will call |
| * 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) |
| { |
| struct dasd_device *device = init_cqr->startdev; |
| struct dasd_eckd_private *private = device->private; |
| |
| 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) |
| { |
| struct dasd_ccw_req *init_cqr; |
| |
| 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) |
| { |
| struct dasd_device *device = block->base; |
| struct dasd_eckd_private *private = device->private; |
| struct eckd_count *count_area; |
| unsigned int sb, blk_per_trk; |
| int status, i; |
| struct dasd_ccw_req *init_cqr; |
| |
| status = private->init_cqr_status; |
| private->init_cqr_status = -1; |
| 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; |
| /* Check Track 0 for Compatible Disk Layout */ |
| 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].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; |
| } |
| } |
| if (i == 3) |
| count_area = &private->count_area[3]; |
| |
| if (private->uses_cdl == 0) { |
| 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[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) |
| count_area = &private->count_area[0]; |
| } else { |
| if (private->count_area[3].record == 1) |
| 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) |
| block->bp_block = count_area->dl; |
| } |
| if (block->bp_block == 0) { |
| dev_warn(&device->cdev->dev, |
| "The disk layout of the DASD is not supported\n"); |
| return -EMEDIUMTYPE; |
| } |
| block->s2b_shift = 0; /* bits to shift 512 to get a block */ |
| for (sb = 512; sb < block->bp_block; sb = sb << 1) |
| block->s2b_shift++; |
| |
| blk_per_trk = recs_per_track(&private->rdc_data, 0, block->bp_block); |
| |
| raw: |
| block->blocks = ((unsigned long) private->real_cyl * |
| private->rdc_data.trk_per_cyl * |
| blk_per_trk); |
| |
| dev_info(&device->cdev->dev, |
| "DASD with %u KB/block, %lu KB total size, %u KB/track, " |
| "%s\n", (block->bp_block >> 10), |
| (((unsigned long) private->real_cyl * |
| private->rdc_data.trk_per_cyl * |
| blk_per_trk * (block->bp_block >> 9)) >> 1), |
| ((blk_per_trk * block->bp_block) >> 10), |
| private->uses_cdl ? |
| "compatible disk layout" : "linux disk layout"); |
| |
| return 0; |
| } |
| |
| static int dasd_eckd_do_analysis(struct dasd_block *block) |
| { |
| struct dasd_eckd_private *private = block->base->private; |
| |
| if (private->init_cqr_status < 0) |
| return dasd_eckd_start_analysis(block); |
| else |
| return dasd_eckd_end_analysis(block); |
| } |
| |
| 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) |
| { |
| if (cancel_work_sync(&device->reload_device)) |
| dasd_put_device(device); |
| if (cancel_work_sync(&device->kick_validate)) |
| dasd_put_device(device); |
| |
| return 0; |
| }; |
| |
| static int dasd_eckd_basic_to_known(struct dasd_device *device) |
| { |
| return dasd_alias_remove_device(device); |
| }; |
| |
| static int |
| dasd_eckd_fill_geometry(struct dasd_block *block, struct hd_geometry *geo) |
| { |
| struct dasd_eckd_private *private = block->base->private; |
| |
| if (dasd_check_blocksize(block->bp_block) == 0) { |
| geo->sectors = recs_per_track(&private->rdc_data, |
| 0, block->bp_block); |
| } |
| geo->cylinders = private->rdc_data.no_cyl; |
| geo->heads = private->rdc_data.trk_per_cyl; |
| return 0; |
| } |
| |
| /* |
| * Build the TCW request for the format check |
| */ |
| static struct dasd_ccw_req * |
| dasd_eckd_build_check_tcw(struct dasd_device *base, struct format_data_t *fdata, |
| int enable_pav, struct eckd_count *fmt_buffer, |
| int rpt) |
| { |
| struct dasd_eckd_private *start_priv; |
| struct dasd_device *startdev = NULL; |
| struct tidaw *last_tidaw = NULL; |
| struct dasd_ccw_req *cqr; |
| struct itcw *itcw; |
| int itcw_size; |
| int count; |
| int rc; |
| int i; |
| |
| if (enable_pav) |
| startdev = dasd_alias_get_start_dev(base); |
| |
| if (!startdev) |
| startdev = base; |
| |
| start_priv = startdev->private; |
| |
| count = rpt * (fdata->stop_unit - fdata->start_unit + 1); |
| |
| /* |
| * we're adding 'count' amount of tidaw to the itcw. |
| * calculate the corresponding itcw_size |
| */ |
| itcw_size = itcw_calc_size(0, count, 0); |
| |
| cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev); |
| if (IS_ERR(cqr)) |
| return cqr; |
| |
| start_priv->count++; |
| |
| itcw = itcw_init(cqr->data, itcw_size, ITCW_OP_READ, 0, count, 0); |
| if (IS_ERR(itcw)) { |
| rc = -EINVAL; |
| goto out_err; |
| } |
| |
| cqr->cpaddr = itcw_get_tcw(itcw); |
| rc = prepare_itcw(itcw, fdata->start_unit, fdata->stop_unit, |
| DASD_ECKD_CCW_READ_COUNT_MT, base, startdev, 0, count, |
| sizeof(struct eckd_count), |
| count * sizeof(struct eckd_count), 0, rpt); |
| if (rc) |
| goto out_err; |
| |
| for (i = 0; i < count; i++) { |
| last_tidaw = itcw_add_tidaw(itcw, 0, fmt_buffer++, |
| sizeof(struct eckd_count)); |
| if (IS_ERR(last_tidaw)) { |
| rc = -EINVAL; |
| goto out_err; |
| } |
| } |
| |
| last_tidaw->flags |= TIDAW_FLAGS_LAST; |
| itcw_finalize(itcw); |
| |
| cqr->cpmode = 1; |
| cqr->startdev = startdev; |
| cqr->memdev = startdev; |
| cqr->basedev = base; |
| cqr->retries = startdev->default_retries; |
| cqr->expires = startdev->default_expires * HZ; |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| /* Set flags to suppress output for expected errors */ |
| set_bit(DASD_CQR_SUPPRESS_FP, &cqr->flags); |
| set_bit(DASD_CQR_SUPPRESS_IL, &cqr->flags); |
| |
| return cqr; |
| |
| out_err: |
| dasd_sfree_request(cqr, startdev); |
| |
| return ERR_PTR(rc); |
| } |
| |
| /* |
| * Build the CCW request for the format check |
| */ |
| static struct dasd_ccw_req * |
| dasd_eckd_build_check(struct dasd_device *base, struct format_data_t *fdata, |
| int enable_pav, struct eckd_count *fmt_buffer, int rpt) |
| { |
| struct dasd_eckd_private *start_priv; |
| struct dasd_eckd_private *base_priv; |
| struct dasd_device *startdev = NULL; |
| struct dasd_ccw_req *cqr; |
| struct ccw1 *ccw; |
| void *data; |
| int cplength, datasize; |
| int use_prefix; |
| int count; |
| int i; |
| |
| if (enable_pav) |
| startdev = dasd_alias_get_start_dev(base); |
| |
| if (!startdev) |
| startdev = base; |
| |
| start_priv = startdev->private; |
| base_priv = base->private; |
| |
| count = rpt * (fdata->stop_unit - fdata->start_unit + 1); |
| |
| use_prefix = base_priv->features.feature[8] & 0x01; |
| |
| if (use_prefix) { |
| cplength = 1; |
| datasize = sizeof(struct PFX_eckd_data); |
| } else { |
| cplength = 2; |
| datasize = sizeof(struct DE_eckd_data) + |
| sizeof(struct LO_eckd_data); |
| } |
| cplength += count; |
| |
| cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, cplength, datasize, startdev); |
| if (IS_ERR(cqr)) |
| return cqr; |
| |
| start_priv->count++; |
| data = cqr->data; |
| ccw = cqr->cpaddr; |
| |
| if (use_prefix) { |
| prefix_LRE(ccw++, data, fdata->start_unit, fdata->stop_unit, |
| DASD_ECKD_CCW_READ_COUNT, base, startdev, 1, 0, |
| count, 0, 0); |
| } else { |
| define_extent(ccw++, data, fdata->start_unit, fdata->stop_unit, |
| DASD_ECKD_CCW_READ_COUNT, startdev, 0); |
| |
| data += sizeof(struct DE_eckd_data); |
| ccw[-1].flags |= CCW_FLAG_CC; |
| |
| locate_record(ccw++, data, fdata->start_unit, 0, count, |
| DASD_ECKD_CCW_READ_COUNT, base, 0); |
| } |
| |
| for (i = 0; i < count; i++) { |
| ccw[-1].flags |= CCW_FLAG_CC; |
| ccw->cmd_code = DASD_ECKD_CCW_READ_COUNT; |
| ccw->flags = CCW_FLAG_SLI; |
| ccw->count = 8; |
| ccw->cda = (__u32)(addr_t) fmt_buffer; |
| ccw++; |
| fmt_buffer++; |
| } |
| |
| cqr->startdev = startdev; |
| cqr->memdev = startdev; |
| cqr->basedev = base; |
| cqr->retries = DASD_RETRIES; |
| cqr->expires = startdev->default_expires * HZ; |
| cqr->buildclk = get_tod_clock(); |
| cqr->status = DASD_CQR_FILLED; |
| /* Set flags to suppress output for expected errors */ |
| set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); |
| |
| return cqr; |
| } |
| |
| static struct dasd_ccw_req * |
| dasd_eckd_build_format(struct dasd_device *base, struct dasd_device *startdev, |
| struct format_data_t *fdata, int enable_pav) |
| { |
| struct dasd_eckd_private *base_priv; |
| struct dasd_eckd_private *start_priv; |
| struct dasd_ccw_req *fcp; |
| struct eckd_count *ect; |
| struct ch_t address; |
| struct ccw1 *ccw; |
| void *data; |
| int rpt; |
| int cplength, datasize; |
| int i, j; |
| int intensity = 0; |
| int r0_perm; |
| int nr_tracks; |
| int use_prefix; |
| |
| if (enable_pav) |
| startdev = dasd_alias_get_start_dev(base); |
| |
| if (!startdev) |
| startdev = base; |
| |
| start_priv = startdev->private; |
| base_priv = 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: |
| * Bit 0: write record zero |
| * Bit 1: write home address, currently not supported |
| * Bit 2: invalidate tracks |
| * Bit 3: use OS/390 compatible disk layout (cdl) |
| * Bit 4: do not allow storage subsystem to modify record zero |
| * Only some bit combinations do make sense. |
| */ |
| if (fdata->intensity & 0x10) { |
| r0_perm = 0; |
| intensity = fdata->intensity & ~0x10; |
| } else { |
| 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*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 = 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; |
| 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(&startdev->cdev->dev, |
| "An I/O control call used incorrect flags 0x%x\n", |
| fdata->intensity); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| fcp = dasd_fmalloc_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. */ |
| 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, 0); |
| /* 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*nr_tracks, |
| DASD_ECKD_CCW_WRITE_CKD, base, |
| fdata->blksize); |
| data += sizeof(struct LO_eckd_data); |
| break; |
| case 0x01: /* Write record zero + format track. */ |
| 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, 0); |
| 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 * 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. */ |
| 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, 0); |
| 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, base, 8); |
| data += sizeof(struct LO_eckd_data); |
| break; |
| } |
| |
| 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 = 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++) { |
| 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) && |
| address.cyl == 0 && address.head == 0) { |
| if (i < 3) { |
| ect->kl = 4; |
| ect->dl = sizes_trk0[i] - 4; |
| } |
| } |
| if ((intensity & 0x08) && |
| address.cyl == 0 && address.head == 1) { |
| ect->kl = 44; |
| ect->dl = LABEL_SIZE - 44; |
| } |
| ccw[-1].flags |= CCW_FLAG_CC; |
| if (i != 0 || j == 0) |
| ccw->cmd_code
|