| // 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> | |
| */ | |
| #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/init.h> | |
| #include <linux/seq_file.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/utsname.h> | |
| #include <linux/io.h> | |
| #include <linux/overflow.h> | |
| #include <asm/css_chars.h> | |
| #include <asm/machine.h> | |
| #include <asm/debug.h> | |
| #include <asm/idals.h> | |
| #include <asm/ebcdic.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" | |
| /* | |
| * 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_DESCRIPTION("S/390 DASD ECKD Disks device driver"); | |
| MODULE_LICENSE("GPL"); | |
| /* | |
| * Default full-track write bias applied to every ESE volume at online time; | |
| * individual volumes can be re-tuned afterwards through their per-device | |
| * full_track_bias sysfs attribute. 0 disables full-track writes, 100 always | |
| * uses them, 50 (the default) enables the adaptive heuristic. Values above | |
| * DASD_FT_BIAS_MAX are capped when applied. | |
| */ | |
| static unsigned int full_track_bias = DASD_FT_BIAS_DEFAULT; | |
| module_param(full_track_bias, uint, 0644); | |
| MODULE_PARM_DESC(full_track_bias, | |
| "Default ESE full-track write bias 0..100 (0=off, 1..99=adaptive, 100=always)"); | |
| 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 *); | |
| static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_writefulltrack(struct dasd_device *, | |
| struct dasd_block *, | |
| struct request *, | |
| sector_t, sector_t, | |
| sector_t, sector_t, | |
| unsigned int, unsigned int, | |
| unsigned int, unsigned int, | |
| struct dasd_ccw_req *); | |
| /* 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 >> DASD_EAV_CYL_HI_SHIFT; | |
| geo->head <<= DASD_EAV_HEAD_HI_SHIFT; | |
| geo->head |= head; | |
| } | |
| static void set_chr_t(void *addr, __u32 cyl, __u8 head, __u8 record) | |
| { | |
| struct chr_t *geo = addr; | |
| geo->cyl = (__u16)cyl; | |
| geo->head = cyl >> DASD_EAV_CYL_HI_SHIFT; | |
| geo->head <<= DASD_EAV_HEAD_HI_SHIFT; | |
| geo->head |= head; | |
| geo->record = record; | |
| } | |
| /* | |
| * 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 = virt_to_dma32(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 = virt_to_dma32(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 = virt_to_dma32(pfxdata); | |
| memset(pfxdata, 0, sizeof(*pfxdata) + 2); | |
| } else { | |
| ccw->count = sizeof(*pfxdata); | |
| ccw->cda = virt_to_dma32(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; | |
| /* cached copies - conf.ned may be freed under us by the reload worker */ | |
| pfxdata->base_address = READ_ONCE(basepriv->ned_ua); | |
| pfxdata->base_lss = READ_ONCE(basepriv->ned_lss); | |
| 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 = virt_to_dma32(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); | |
| /* cache LSS and unit address for the lockless CCW-build path */ | |
| WRITE_ONCE(private->ned_lss, private->conf.ned->ID); | |
| WRITE_ONCE(private->ned_ua, private->conf.ned->unit_addr); | |
| 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 = virt_to_dma32(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 = dma32_to_virt(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); | |
| snprintf(print_uid, DASD_UID_STRLEN, "%s.%s.%04x.%02x%s%s", | |
| uid.vendor, uid.serial, uid.ssid, uid.real_unit_addr, | |
| uid.vduit[0] ? "." : "", uid.vduit); | |
| } | |
| static int dasd_eckd_check_cabling(struct dasd_device *device, | |
| void *conf_data, __u8 lpm) | |
| { | |
| char print_path_uid[DASD_UID_STRLEN], print_device_uid[DASD_UID_STRLEN]; | |
| struct dasd_eckd_private *private = device->private; | |
| 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; | |
| char print_uid[DASD_UID_STRLEN]; | |
| struct dasd_conf path_conf; | |
| unsigned long flags; | |
| 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) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| 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) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| struct pe_handler_work_data *data; | |
| data = kzalloc_obj(*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) | |
| return; | |
| 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 = virt_to_dma32(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 = virt_to_dma32(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) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| 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 = READ_ONCE(private->ned_lss); | |
| prssdp->volume = READ_ONCE(private->ned_ua); | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->count = sizeof(*prssdp); | |
| ccw->flags |= CCW_FLAG_CC; | |
| ccw->cda = virt_to_dma32(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 = virt_to_dma32(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; | |
| } | |
| /* Hardware/volume ESE capability, from the Volume Storage Query. */ | |
| static int dasd_eckd_ese_capable(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| if (!private) | |
| return 0; | |
| return private->vsq.vol_info.ese; | |
| } | |
| /* | |
| * Whether the volume is to be handled as ESE (thin). This reflects the state | |
| * of the data, not the hardware: a volume copied off ESE storage onto other | |
| * hardware still needs ESE handling. The on-disk format label is authoritative | |
| * when present; without it (e.g. a volume formatted by an older driver) fall | |
| * back to the hardware ESE field. | |
| * | |
| * Only the F_ESE flag gates this. An ESE volume is thin regardless of whether | |
| * it was quick- or full-formatted (tracks are allocated on write, and discard | |
| * re-thins a full one). | |
| */ | |
| static int dasd_eckd_is_ese(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| /* sysfs may read this during set_online before private is allocated */ | |
| if (!private) | |
| return 0; | |
| if (private->ese_label_valid) | |
| return !!(private->ese_label.flags & DASD_ESE_LABEL_F_ESE); | |
| return dasd_eckd_ese_capable(device); | |
| } | |
| /* | |
| * Whether the volume is formatted on demand (thin), as opposed to fully | |
| * formatted. This is the format mode, not the hardware ESE capability. When a | |
| * label is present it is authoritative (F_QUICK). Without a label the mode is | |
| * unknown, but an ESE volume is still handled on demand (NRF triggers the | |
| * format), so fall back to the ESE state to stay consistent with the driver's | |
| * behavior on older, label-less volumes. | |
| */ | |
| static int dasd_eckd_on_demand_format(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| /* sysfs may read this during set_online before private is allocated */ | |
| if (!private) | |
| return 0; | |
| if (private->ese_label_valid) | |
| return !!(private->ese_label.flags & DASD_ESE_LABEL_F_QUICK); | |
| return dasd_eckd_is_ese(device); | |
| } | |
| static int dasd_eckd_ext_pool_id(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| if (!private) | |
| return 0; | |
| 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; | |
| if (!private) | |
| return 0; | |
| 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; | |
| if (!private) | |
| return 0; | |
| 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; | |
| if (!private) | |
| return 0; | |
| 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_obj(*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 = virt_to_dma32(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 = virt_to_dma32(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; | |
| if (!private) | |
| return 0; | |
| 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; | |
| if (!private) | |
| return 0; | |
| 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; | |
| if (!private) | |
| return 0; | |
| 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; | |
| if (!private) | |
| return 0; | |
| 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 = virt_to_dma32(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; | |
| } | |
| /* | |
| * Valid 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; | |
| } | |
| /* | |
| * Read the on-disk format label from track 0, record 4. On a formatted volume | |
| * R4 holds the label as its record data; on an unformatted (fresh ESE) or | |
| * label-less volume the read returns No Record Found, which is expected and | |
| * leaves the cache invalid so is_ese() falls back to the hardware field. | |
| */ | |
| static void dasd_eckd_read_format_label(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct dasd_format_label *label; | |
| struct DE_eckd_data *dedata; | |
| struct LO_eckd_data *lodata; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| private->ese_label_valid = false; | |
| /* The label lives on the base volume; aliases have none of their own. */ | |
| if (private->uid.type == UA_BASE_PAV_ALIAS || | |
| private->uid.type == UA_HYPER_PAV_ALIAS) | |
| return; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 3 /* DE + LO + READ */, | |
| sizeof(*dedata) + sizeof(*lodata) + | |
| sizeof(*label), device, NULL); | |
| if (IS_ERR(cqr)) | |
| return; | |
| dedata = cqr->data; | |
| lodata = (struct LO_eckd_data *)(dedata + 1); | |
| label = (struct dasd_format_label *)(lodata + 1); | |
| ccw = cqr->cpaddr; | |
| define_extent(ccw++, dedata, 0, 0, DASD_ECKD_CCW_READ, device, 0); | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| locate_record(ccw++, lodata, 0, 4, 1, DASD_ECKD_CCW_READ, device, | |
| sizeof(*label)); | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| ccw->cmd_code = DASD_ECKD_CCW_READ; | |
| ccw->count = sizeof(*label); | |
| ccw->flags = CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(label); | |
| 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; | |
| /* R4 may be absent (unformatted) or larger than the label. */ | |
| set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); | |
| set_bit(DASD_CQR_SUPPRESS_IL, &cqr->flags); | |
| if (!dasd_sleep_on(cqr) && | |
| label->magic == DASD_ESE_LABEL_MAGIC && | |
| label->version == DASD_ESE_LABEL_VERSION) { | |
| private->ese_label = *label; | |
| private->ese_label_valid = true; | |
| } | |
| dasd_sfree_request(cqr, device); | |
| } | |
| /* | |
| * 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_obj(*private, GFP_KERNEL | GFP_DMA); | |
| if (!private) { | |
| dev_warn(&device->cdev->dev, | |
| "Allocating memory for private DASD data " | |
| "failed\n"); | |
| return -ENOMEM; | |
| } | |
| device->private = private; | |
| } else { | |
| memset(private, 0, sizeof(*private)); | |
| } | |
| /* 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; | |
| device->aq_timeouts = DASD_RETRIES_MAX; | |
| 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); | |
| /* | |
| * Advertise discard through the device feature so the block layer sets | |
| * up discard limits. Discard releases allocated space, so require a thin | |
| * (ESE) volume whose storage reports support for the space-release | |
| * function. Raw-track access bypasses the normal block CCW path (discard | |
| * would reach the raw builder, which has no record data), so exclude it. | |
| */ | |
| if (dasd_eckd_ese_capable(device) && | |
| (private->features.feature[56] & 0x01) && | |
| !(device->features & DASD_FEATURE_USERAW)) | |
| device->features |= DASD_FEATURE_DISCARD; | |
| /* Read the on-disk format label for ESE detection */ | |
| dasd_eckd_read_format_label(device); | |
| /* | |
| * is_ese() now reflects the real ESE state (vsq + on-disk label), so | |
| * the adaptive heuristic can be derived correctly for this device. | |
| */ | |
| device->ft_bias = min_t(unsigned int, full_track_bias, DASD_FT_BIAS_MAX); | |
| dasd_ft_bias_apply(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 = virt_to_dma32(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 = virt_to_dma32(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); | |
| set_bit(DASD_CQR_SUPPRESS_IT, &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; | |
| const char *ese_str, *fmt_str; | |
| 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 nothing 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); | |
| /* | |
| * Report the ESE hardware capability and the format mode. The mode | |
| * comes from dasd_eckd_on_demand_format() (the on-disk label, or the | |
| * ESE state when no label is present), matching the on_demand_formatting | |
| * sysfs attribute. | |
| */ | |
| ese_str = dasd_eckd_ese_capable(device) ? ", ESE" : ""; | |
| fmt_str = ""; | |
| if (dasd_eckd_on_demand_format(device)) | |
| fmt_str = ", on-demand format"; | |
| else if (dasd_eckd_ese_capable(device)) | |
| fmt_str = ", full format"; | |
| dev_info(&device->cdev->dev, | |
| "DASD with %u KB/block, %lu KB total size, %u KB/track, %s%s%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", | |
| ese_str, fmt_str); | |
| 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_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 = virt_to_dma32(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; | |
| } | |
| /* Fill the format label into a R4 record buffer, zero-padded to blksize. */ | |
| static void dasd_eckd_fill_format_label(struct dasd_device *device, void *data, | |
| unsigned int blksize) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct dasd_format_label *label = data; | |
| memset(label, 0, blksize); | |
| label->magic = DASD_ESE_LABEL_MAGIC; | |
| label->version = DASD_ESE_LABEL_VERSION; | |
| /* | |
| * F_ESE records the hardware capability at format time, not is_ese(): | |
| * is_ese() is derived from the label, so using it here would let the | |
| * flag flip on repeated quick/full reformats. | |
| */ | |
| if (dasd_eckd_ese_capable(device)) | |
| label->flags |= DASD_ESE_LABEL_F_ESE; | |
| if (private->ese_format_quick) | |
| label->flags |= DASD_ESE_LABEL_F_QUICK; | |
| else | |
| label->flags |= DASD_ESE_LABEL_F_FULL; | |
| label->blksize = blksize; | |
| label->format_tod = get_tod_clock(); | |
| strscpy(label->kernel_version, init_utsname()->release, | |
| sizeof(label->kernel_version)); | |
| /* | |
| * Populate the cache directly from the bytes just computed instead of | |
| * synchronously reading them back from disk after the write lands. | |
| */ | |
| private->ese_label = *label; | |
| private->ese_label_valid = true; | |
| } | |
| 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; | |
| int write_label; | |
| 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; | |
| /* | |
| * Stamp the format label into R4 of the very first track. Only for CDL | |
| * (R4 is the first non-special record there), only when this request | |
| * covers track 0, only for the record-writing format intensities (not | |
| * track invalidation), and only if the track actually has an R4. | |
| */ | |
| write_label = (intensity & 0x08) && !((intensity & ~0x08) & 0x04) && | |
| fdata->start_unit == 0 && rpt > 3; | |
| 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); | |
| } | |
| /* room for the label data that R4 carries in addition to its count */ | |
| if (write_label) | |
| datasize += fdata->blksize; | |
| 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 = virt_to_dma32(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 = virt_to_dma32(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 = | |
| DASD_ECKD_CCW_WRITE_CKD; | |
| else | |
| ccw->cmd_code = | |
| DASD_ECKD_CCW_WRITE_CKD_MT; | |
| ccw->flags = CCW_FLAG_SLI; | |
| if (write_label && address.cyl == 0 && | |
| address.head == 0 && i == 3) { | |
| /* | |
| * R4 carries the label as its record | |
| * data; it follows ect contiguously so | |
| * the CCW transfers count + data. | |
| */ | |
| dasd_eckd_fill_format_label(base, | |
| data, | |
| fdata->blksize); | |
| data += fdata->blksize; | |
| ccw->count = 8 + fdata->blksize; | |
| } else { | |
| ccw->count = 8; | |
| } | |
| ccw->cda = virt_to_dma32(ect); | |
| ccw++; | |
| } | |
| } | |
| } | |
| fcp->startdev = startdev; | |
| fcp->memdev = startdev; | |
| fcp->basedev = base; | |
| fcp->retries = 256; | |
| fcp->expires = startdev->default_expires * HZ; | |
| fcp->buildclk = get_tod_clock(); | |
| fcp->status = DASD_CQR_FILLED; | |
| return fcp; | |
| } | |
| /* | |
| * Wrapper function to build a CCW request depending on input data | |
| */ | |
| static struct dasd_ccw_req * | |
| dasd_eckd_format_build_ccw_req(struct dasd_device *base, | |
| struct format_data_t *fdata, int enable_pav, | |
| int tpm, struct eckd_count *fmt_buffer, int rpt) | |
| { | |
| struct dasd_ccw_req *ccw_req; | |
| if (!fmt_buffer) { | |
| ccw_req = dasd_eckd_build_format(base, NULL, fdata, enable_pav); | |
| } else { | |
| if (tpm) | |
| ccw_req = dasd_eckd_build_check_tcw(base, fdata, | |
| enable_pav, | |
| fmt_buffer, rpt); | |
| else | |
| ccw_req = dasd_eckd_build_check(base, fdata, enable_pav, | |
| fmt_buffer, rpt); | |
| } | |
| return ccw_req; | |
| } | |
| /* | |
| * Sanity checks on format_data | |
| */ | |
| static int dasd_eckd_format_sanity_checks(struct dasd_device *base, | |
| struct format_data_t *fdata) | |
| { | |
| struct dasd_eckd_private *private = base->private; | |
| 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; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * This function will process format_data originally coming from an IOCTL | |
| */ | |
| static int dasd_eckd_format_process_data(struct dasd_device *base, | |
| struct format_data_t *fdata, | |
| int enable_pav, int tpm, | |
| struct eckd_count *fmt_buffer, int rpt, | |
| struct irb *irb) | |
| { | |
| struct dasd_eckd_private *private = base->private; | |
| struct dasd_ccw_req *cqr, *n; | |
| struct list_head format_queue; | |
| struct dasd_device *device; | |
| char *sense = NULL; | |
| int old_start, old_stop, format_step; | |
| int step, retry; | |
| int rc; | |
| rc = dasd_eckd_format_sanity_checks(base, fdata); | |
| if (rc) | |
| return rc; | |
| INIT_LIST_HEAD(&format_queue); | |
| old_start = fdata->start_unit; | |
| old_stop = fdata->stop_unit; | |
| if (!tpm && fmt_buffer != NULL) { | |
| /* Command Mode / Format Check */ | |
| format_step = 1; | |
| } else if (tpm && fmt_buffer != NULL) { | |
| /* Transport Mode / Format Check */ | |
| format_step = DASD_CQR_MAX_CCW / rpt; | |
| } else { | |
| /* Normal Formatting */ | |
| format_step = DASD_CQR_MAX_CCW / | |
| recs_per_track(&private->rdc_data, 0, fdata->blksize); | |
| } | |
| do { | |
| retry = 0; | |
| 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_format_build_ccw_req(base, fdata, | |
| enable_pav, tpm, | |
| fmt_buffer, rpt); | |
| if (IS_ERR(cqr)) { | |
| rc = PTR_ERR(cqr); | |
| if (rc == -ENOMEM) { | |
| if (list_empty(&format_queue)) | |
| goto out; | |
| /* | |
| * not enough memory available, start | |
| * requests retry after first requests | |
| * were finished | |
| */ | |
| retry = 1; | |
| break; | |
| } | |
| goto out_err; | |
| } | |
| list_add_tail(&cqr->blocklist, &format_queue); | |
| if (fmt_buffer) { | |
| step = fdata->stop_unit - fdata->start_unit + 1; | |
| fmt_buffer += rpt * step; | |
| } | |
| fdata->start_unit = fdata->stop_unit + 1; | |
| fdata->stop_unit = old_stop; | |
| } | |
| rc = dasd_sleep_on_queue(&format_queue); | |
| out_err: | |
| list_for_each_entry_safe(cqr, n, &format_queue, blocklist) { | |
| device = cqr->startdev; | |
| private = device->private; | |
| if (cqr->status == DASD_CQR_FAILED) { | |
| /* | |
| * Only get sense data if called by format | |
| * check | |
| */ | |
| if (fmt_buffer && irb) { | |
| sense = dasd_get_sense(&cqr->irb); | |
| memcpy(irb, &cqr->irb, sizeof(*irb)); | |
| } | |
| rc = -EIO; | |
| } | |
| list_del_init(&cqr->blocklist); | |
| dasd_ffree_request(cqr, device); | |
| private->count--; | |
| } | |
| if (rc && rc != -EIO) | |
| goto out; | |
| if (rc == -EIO) { | |
| /* | |
| * In case fewer than the expected records are on the | |
| * track, we will most likely get a 'No Record Found' | |
| * error (in command mode) or a 'File Protected' error | |
| * (in transport mode). Those particular cases shouldn't | |
| * pass the -EIO to the IOCTL, therefore reset the rc | |
| * and continue. | |
| */ | |
| if (sense && | |
| (sense[1] & SNS1_NO_REC_FOUND || | |
| sense[1] & SNS1_FILE_PROTECTED)) | |
| retry = 1; | |
| else | |
| goto out; | |
| } | |
| } while (retry); | |
| out: | |
| fdata->start_unit = old_start; | |
| fdata->stop_unit = old_stop; | |
| return rc; | |
| } | |
| static int dasd_eckd_format_device(struct dasd_device *base, | |
| struct format_data_t *fdata, int enable_pav) | |
| { | |
| struct dasd_eckd_private *private = base->private; | |
| int rc; | |
| rc = dasd_eckd_format_process_data(base, fdata, enable_pav, 0, NULL, | |
| 0, NULL); | |
| if (fdata->start_unit != 0) | |
| return rc; | |
| if (rc) { | |
| /* | |
| * The format failed, so the label cached speculatively during | |
| * CCW build may not match the disk; drop it so is_ese() falls | |
| * back to the hardware field until the next successful format | |
| * or bring-up. | |
| */ | |
| private->ese_label_valid = false; | |
| return rc; | |
| } | |
| /* | |
| * The quick-format indicator was consumed by the label stamped into | |
| * track 0; clear it so a later format that is not preceded by a full | |
| * space release is recorded as a full format. | |
| */ | |
| private->ese_format_quick = 0; | |
| /* | |
| * A full format returns the device to a fully sparse state and has just | |
| * committed a fresh label; restart the heuristic from ft1 on the now | |
| * current is_ese state, without an offline cycle. | |
| */ | |
| dasd_ft_bias_apply(base); | |
| return rc; | |
| } | |
| static bool test_and_set_format_track(sector_t start, sector_t end, | |
| struct dasd_ccw_req *cqr, | |
| struct dasd_block *block, | |
| struct dasd_device *device, | |
| struct dasd_format_entry **entry) | |
| { | |
| struct dasd_format_entry *to_format, *format; | |
| unsigned long flags; | |
| bool rc = false; | |
| int i = 0; | |
| /* marked as a collision by dasd_return_cqr_cb last round: retry */ | |
| if (cqr && READ_ONCE(cqr->collision)) { | |
| WRITE_ONCE(cqr->collision, false); | |
| return true; | |
| } | |
| spin_lock_irqsave(&block->format_lock, flags); | |
| while (i < DASD_NR_FORMAT_ENTRIES && | |
| READ_ONCE(device->format_entry[i].cqr)) | |
| i++; | |
| if (i >= DASD_NR_FORMAT_ENTRIES) { | |
| rc = true; | |
| goto out; | |
| } | |
| list_for_each_entry(format, &block->format_list, list) { | |
| if (!(end < format->start_trk || format->end_trk < start)) { | |
| rc = true; | |
| goto out; | |
| } | |
| } | |
| to_format = &device->format_entry[i]; | |
| to_format->start_trk = start; | |
| to_format->end_trk = end; | |
| to_format->cqr = cqr; | |
| list_add_tail(&to_format->list, &block->format_list); | |
| *entry = to_format; | |
| out: | |
| spin_unlock_irqrestore(&block->format_lock, flags); | |
| return rc; | |
| } | |
| static void clear_format_track(struct dasd_format_entry *format, | |
| struct dasd_block *block) | |
| { | |
| unsigned long flags; | |
| spin_lock_irqsave(&block->format_lock, flags); | |
| list_del_init(&format->list); | |
| format->cqr = NULL; | |
| spin_unlock_irqrestore(&block->format_lock, flags); | |
| } | |
| /* | |
| * Adaptive ft_bias heuristic, called once per IO from dasd_eckd_build_cp(). | |
| * Probes the device formatting state by briefly switching to ft0 and measuring | |
| * the NRF rate; parameters are derived from ft_bias. | |
| */ | |
| static void dasd_ese_heuristic_tick(struct dasd_device *basedev) | |
| { | |
| int ios, nrf, rate; | |
| if (atomic_inc_return(&basedev->ese_io_cnt) < (int)basedev->ese_probe_interval) | |
| return; | |
| /* | |
| * One wins the race to evaluate, the rest see ios == 0 after the | |
| * xchg and return early, preventing redundant state transitions. | |
| */ | |
| ios = atomic_xchg(&basedev->ese_io_cnt, 0); | |
| if (ios <= 0) | |
| return; | |
| switch (basedev->ese_probe_state) { | |
| case DASD_ESE_HEU_FT1_ACTIVE: | |
| /* Start ft0 probe window, reset NRF counter for clean measurement */ | |
| basedev->fulltrack = 0; | |
| basedev->ese_probe_state = DASD_ESE_HEU_PROBING; | |
| basedev->ese_probe_interval = basedev->ese_heu_probe_window; | |
| atomic_set(&basedev->ese_nrf_window, 0); | |
| break; | |
| case DASD_ESE_HEU_PROBING: | |
| case DASD_ESE_HEU_FT0_STABLE: | |
| nrf = atomic_xchg(&basedev->ese_nrf_window, 0); | |
| rate = (int)((u64)nrf * 1000 / ios); | |
| if (rate > (int)basedev->ese_heu_nrf_high) { | |
| /* NRF rate high: device still sparse, ft1 is better */ | |
| basedev->fulltrack = 1; | |
| basedev->ese_probe_state = DASD_ESE_HEU_FT1_ACTIVE; | |
| basedev->ese_probe_interval = basedev->ese_heu_start_interval; | |
| } else if (basedev->ese_probe_state == DASD_ESE_HEU_PROBING) { | |
| /* | |
| * NRF rate low: device mostly formatted, ft0 is faster. | |
| * Re-probe frequently at first, then back off below. | |
| */ | |
| basedev->fulltrack = 0; | |
| basedev->ese_probe_state = DASD_ESE_HEU_FT0_STABLE; | |
| basedev->ese_probe_interval = basedev->ese_heu_probe_window; | |
| } else { | |
| /* | |
| * Still stable in ft0: re-assert plain-write mode so a | |
| * fulltrack value left behind by a racing sysfs write | |
| * self-corrects, and back off the re-probe interval | |
| * (double it, capped at max_interval) so a long-lived | |
| * formatted device is not probed more often than needed. | |
| */ | |
| basedev->fulltrack = 0; | |
| basedev->ese_probe_interval = | |
| min(basedev->ese_probe_interval * 2, | |
| basedev->ese_heu_max_interval); | |
| } | |
| break; | |
| } | |
| } | |
| static void dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr, | |
| struct irb *irb) | |
| { | |
| struct dasd_format_entry *format = NULL; | |
| unsigned int first_offs, last_offs; | |
| struct dasd_eckd_private *private; | |
| struct dasd_ccw_req *base_cqr; | |
| sector_t first_rec, last_rec; | |
| sector_t first_trk, last_trk; | |
| unsigned int proc_bytes = 0; | |
| struct dasd_ccw_req *fcqr; | |
| unsigned int recs_per_trk; | |
| struct dasd_device *base; | |
| struct dasd_block *block; | |
| unsigned int blksize; | |
| struct request *req; | |
| sector_t curr_trk; | |
| unsigned int diff; | |
| int rc; | |
| req = dasd_get_callback_data(cqr); | |
| block = cqr->block; | |
| base = block->base; | |
| private = base->private; | |
| if (dasd_ese_adaptive(base)) | |
| atomic_inc(&base->ese_nrf_window); | |
| blksize = block->bp_block; | |
| recs_per_trk = recs_per_track(&private->rdc_data, 0, blksize); | |
| /* Calculate record id of first and last block. */ | |
| first_rec = blk_rq_pos(req) >> block->s2b_shift; | |
| first_trk = first_rec; | |
| first_offs = sector_div(first_trk, recs_per_trk); | |
| last_rec = (blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift; | |
| last_trk = last_rec; | |
| last_offs = sector_div(last_trk, recs_per_trk); | |
| /* | |
| * detect if some data has already been processed and the unformatted track is | |
| * within the request. | |
| * If so, finish the request first with the already processed bytes and let the | |
| * blocklayer only redrive unformatted part. | |
| * With this we ensure that there is no overlap of existing data with unformatted | |
| * zero blocks | |
| */ | |
| rc = dasd_eckd_track_from_irb(irb, base, &curr_trk); | |
| if (rc) { | |
| /* sense data could not be parsed - this will not resolve by retrying */ | |
| cqr->status = DASD_CQR_ERROR; | |
| goto out; | |
| } | |
| if (curr_trk >= (sector_t)private->real_cyl * private->rdc_data.trk_per_cyl) { | |
| DBF_DEV_EVENT(DBF_WARNING, startdev, | |
| "ESE error track %llu exceeds device geometry\n", | |
| curr_trk); | |
| cqr->status = DASD_CQR_ERROR; | |
| goto out; | |
| } | |
| if (curr_trk < first_trk || curr_trk > last_trk) { | |
| DBF_DEV_EVENT(DBF_WARNING, startdev, | |
| "ESE error track %llu not within range %llu - %llu\n", | |
| curr_trk, first_trk, last_trk); | |
| cqr->status = DASD_CQR_ERROR; | |
| goto out; | |
| } | |
| if (curr_trk != first_trk) { | |
| proc_bytes = ((curr_trk - first_trk) * recs_per_trk - first_offs) * blksize; | |
| cqr->proc_bytes = proc_bytes; | |
| cqr->status = DASD_CQR_SUCCESS; | |
| cqr->stopclk = get_tod_clock(); | |
| goto out; | |
| } | |
| /* | |
| * If there are multiple tracks to be format-written, we can not write | |
| * the partial last track since we do not know if it is already formatted | |
| * or not so skip the partial last track for now. Return the partial | |
| * completion to blocklayer and let it redo the remainder | |
| */ | |
| if (first_trk != last_trk && last_offs + 1 < recs_per_trk) { | |
| diff = last_offs + 1; | |
| last_rec = last_rec - diff; | |
| last_trk = last_rec; | |
| last_offs = sector_div(last_trk, recs_per_trk); | |
| proc_bytes = (last_rec - first_rec + 1) * blksize; | |
| } | |
| if (first_offs > 0 || last_offs + 1 < recs_per_trk) { | |
| /* test if tracks are already in formatting by another thread */ | |
| if (test_and_set_format_track(first_trk, last_trk, cqr, | |
| cqr->block, cqr->startdev, &format)) { | |
| /* this is no real error so do not count down retries */ | |
| cqr->retries++; | |
| goto out_retry; | |
| } | |
| } | |
| fcqr = dasd_eckd_build_cp_tpm_writefulltrack(startdev, block, req, | |
| first_rec, last_rec, | |
| first_trk, last_trk, | |
| first_offs, last_offs, | |
| recs_per_trk, blksize, cqr); | |
| if (IS_ERR(fcqr)) { | |
| if (format) | |
| clear_format_track(format, cqr->block); | |
| if (PTR_ERR(fcqr) == -EINVAL) { | |
| /* permanent build failure - fail instead of retrying */ | |
| cqr->status = DASD_CQR_ERROR; | |
| goto out; | |
| } | |
| /* | |
| * Transient conditions - the XRC clock is not in sync (-EAGAIN) | |
| * or the format request pool is momentarily exhausted under load | |
| * (-ENOMEM). Retry the origin without counting down its retries. | |
| */ | |
| if (PTR_ERR(fcqr) == -EAGAIN || PTR_ERR(fcqr) == -ENOMEM) | |
| cqr->retries++; | |
| goto out_retry; | |
| } | |
| if (format) { | |
| /* occupancy marker; the free-slot scan reads it with READ_ONCE */ | |
| WRITE_ONCE(format->cqr, fcqr); | |
| fcqr->format = format; | |
| } | |
| /* | |
| * cqr may be an ERP request; dq and the owning request are only set on | |
| * the base request at the end of the ERP chain, so copy from there. | |
| */ | |
| base_cqr = cqr; | |
| while (base_cqr->refers) | |
| base_cqr = base_cqr->refers; | |
| fcqr->dq = base_cqr->dq; | |
| fcqr->callback_data = base_cqr->callback_data; | |
| if (proc_bytes) | |
| fcqr->proc_bytes = proc_bytes; | |
| fcqr->status = DASD_CQR_FILLED; | |
| ((struct dasd_eckd_private *)fcqr->memdev->private)->count++; | |
| /* | |
| * stage under ese_lock; dasd_block_tasklet splices it into ccw_queue. | |
| * Direct enqueue here would invert queue_lock / ccwdev_lock. | |
| */ | |
| spin_lock(&block->ese_lock); | |
| list_add(&fcqr->blocklist, &block->ese_staging); | |
| spin_unlock(&block->ese_lock); | |
| /* mark origin CQR as aborted; ccwdev_lock is held by the IRQ handler */ | |
| cqr->status = DASD_CQR_ABORT; | |
| goto out; | |
| out_retry: | |
| /* | |
| * If we can't format now, let the request go | |
| * one extra round. Maybe we can format later. | |
| * re-queue at the end to let potential format collision finish first | |
| */ | |
| list_move_tail(&cqr->devlist, &cqr->startdev->ccw_queue); | |
| cqr->status = DASD_CQR_QUEUED; | |
| out: | |
| dasd_device_clear_timer(startdev); | |
| dasd_schedule_block_bh(block); | |
| dasd_schedule_device_bh(startdev); | |
| return; | |
| } | |
| /* | |
| * When data is read from an unformatted area of an ESE volume, this function | |
| * returns zeroed data and thereby mimics a read of zero data. | |
| * | |
| * The first unformatted track is the one that got the NRF error, the address is | |
| * encoded in the sense data. | |
| * | |
| * All tracks before have returned valid data and should not be touched. | |
| * All tracks after the unformatted track might be formatted or not. This is | |
| * currently not known, remember the processed data and return the remainder of | |
| * the request to the blocklayer in __dasd_cleanup_cqr(). | |
| */ | |
| static int dasd_eckd_ese_read(struct dasd_ccw_req *cqr, struct irb *irb) | |
| { | |
| struct dasd_eckd_private *private; | |
| sector_t first_trk, last_trk; | |
| sector_t first_blk, last_blk; | |
| unsigned int blksize, off; | |
| unsigned int recs_per_trk; | |
| struct dasd_device *base; | |
| struct req_iterator iter; | |
| struct dasd_block *block; | |
| unsigned int skip_block; | |
| unsigned int blk_count; | |
| struct request *req; | |
| struct bio_vec bv; | |
| sector_t curr_trk; | |
| sector_t end_blk; | |
| char *dst; | |
| int rc; | |
| req = (struct request *) cqr->callback_data; | |
| base = cqr->block->base; | |
| blksize = base->block->bp_block; | |
| block = cqr->block; | |
| private = base->private; | |
| skip_block = 0; | |
| blk_count = 0; | |
| recs_per_trk = recs_per_track(&private->rdc_data, 0, blksize); | |
| first_trk = first_blk = blk_rq_pos(req) >> block->s2b_shift; | |
| sector_div(first_trk, recs_per_trk); | |
| last_trk = last_blk = | |
| (blk_rq_pos(req) + blk_rq_sectors(req) - 1) >> block->s2b_shift; | |
| sector_div(last_trk, recs_per_trk); | |
| rc = dasd_eckd_track_from_irb(irb, base, &curr_trk); | |
| if (rc) | |
| return rc; | |
| /* sanity check if the current track from sense data is valid */ | |
| if (curr_trk < first_trk || curr_trk > last_trk) { | |
| DBF_DEV_EVENT(DBF_WARNING, base, | |
| "ESE error track %llu not within range %llu - %llu\n", | |
| curr_trk, first_trk, last_trk); | |
| return -EINVAL; | |
| } | |
| /* | |
| * if not the first track got the NRF error we have to skip over valid | |
| * blocks | |
| */ | |
| if (curr_trk != first_trk) | |
| skip_block = curr_trk * recs_per_trk - first_blk; | |
| /* we have no information beyond the current track */ | |
| end_blk = (curr_trk + 1) * recs_per_trk; | |
| rq_for_each_segment(bv, req, iter) { | |
| dst = bvec_virt(&bv); | |
| for (off = 0; off < bv.bv_len; off += blksize) { | |
| if (first_blk + blk_count >= end_blk) { | |
| cqr->proc_bytes = blk_count * blksize; | |
| return 0; | |
| } | |
| if (dst && !skip_block) | |
| memset(dst, 0, blksize); | |
| else | |
| skip_block--; | |
| dst += blksize; | |
| blk_count++; | |
| } | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Helper function to count consecutive records of a single track. | |
| */ | |
| static int dasd_eckd_count_records(struct eckd_count *fmt_buffer, int start, | |
| int max) | |
| { | |
| int head; | |
| int i; | |
| head = fmt_buffer[start].head; | |
| /* | |
| * There are 3 conditions where we stop counting: | |
| * - if data reoccurs (same head and record may reoccur), which may | |
| * happen due to the way DASD_ECKD_CCW_READ_COUNT works | |
| * - when the head changes, because we're iterating over several tracks | |
| * then (DASD_ECKD_CCW_READ_COUNT_MT) | |
| * - when we've reached the end of sensible data in the buffer (the | |
| * record will be 0 then) | |
| */ | |
| for (i = start; i < max; i++) { | |
| if (i > start) { | |
| if ((fmt_buffer[i].head == head && | |
| fmt_buffer[i].record == 1) || | |
| fmt_buffer[i].head != head || | |
| fmt_buffer[i].record == 0) | |
| break; | |
| } | |
| } | |
| return i - start; | |
| } | |
| /* | |
| * Evaluate a given range of tracks. Data like number of records, blocksize, | |
| * record ids, and key length are compared with expected data. | |
| * | |
| * If a mismatch occurs, the corresponding error bit is set, as well as | |
| * additional information, depending on the error. | |
| */ | |
| static void dasd_eckd_format_evaluate_tracks(struct eckd_count *fmt_buffer, | |
| struct format_check_t *cdata, | |
| int rpt_max, int rpt_exp, | |
| int trk_per_cyl, int tpm) | |
| { | |
| struct ch_t geo; | |
| int max_entries; | |
| int count = 0; | |
| int trkcount; | |
| int blksize; | |
| int pos = 0; | |
| int i, j; | |
| int kl; | |
| trkcount = cdata->expect.stop_unit - cdata->expect.start_unit + 1; | |
| max_entries = trkcount * rpt_max; | |
| for (i = cdata->expect.start_unit; i <= cdata->expect.stop_unit; i++) { | |
| /* Calculate the correct next starting position in the buffer */ | |
| if (tpm) { | |
| while (fmt_buffer[pos].record == 0 && | |
| fmt_buffer[pos].dl == 0) { | |
| if (pos++ > max_entries) | |
| break; | |
| } | |
| } else { | |
| if (i != cdata->expect.start_unit) | |
| pos += rpt_max - count; | |
| } | |
| /* Calculate the expected geo values for the current track */ | |
| set_ch_t(&geo, i / trk_per_cyl, i % trk_per_cyl); | |
| /* Count and check number of records */ | |
| count = dasd_eckd_count_records(fmt_buffer, pos, pos + rpt_max); | |
| if (count < rpt_exp) { | |
| cdata->result = DASD_FMT_ERR_TOO_FEW_RECORDS; | |
| break; | |
| } | |
| if (count > rpt_exp) { | |
| cdata->result = DASD_FMT_ERR_TOO_MANY_RECORDS; | |
| break; | |
| } | |
| for (j = 0; j < count; j++, pos++) { | |
| blksize = cdata->expect.blksize; | |
| kl = 0; | |
| /* | |
| * Set special values when checking CDL formatted | |
| * devices. | |
| */ | |
| if ((cdata->expect.intensity & 0x08) && | |
| geo.cyl == 0 && geo.head == 0) { | |
| if (j < 3) { | |
| blksize = sizes_trk0[j] - 4; | |
| kl = 4; | |
| } | |
| } | |
| if ((cdata->expect.intensity & 0x08) && | |
| geo.cyl == 0 && geo.head == 1) { | |
| blksize = LABEL_SIZE - 44; | |
| kl = 44; | |
| } | |
| /* Check blocksize */ | |
| if (fmt_buffer[pos].dl != blksize) { | |
| cdata->result = DASD_FMT_ERR_BLKSIZE; | |
| goto out; | |
| } | |
| /* Check if key length is 0 */ | |
| if (fmt_buffer[pos].kl != kl) { | |
| cdata->result = DASD_FMT_ERR_KEY_LENGTH; | |
| goto out; | |
| } | |
| /* Check if record_id is correct */ | |
| if (fmt_buffer[pos].cyl != geo.cyl || | |
| fmt_buffer[pos].head != geo.head || | |
| fmt_buffer[pos].record != (j + 1)) { | |
| cdata->result = DASD_FMT_ERR_RECORD_ID; | |
| goto out; | |
| } | |
| } | |
| } | |
| out: | |
| /* | |
| * In case of no errors, we need to decrease by one | |
| * to get the correct positions. | |
| */ | |
| if (!cdata->result) { | |
| i--; | |
| pos--; | |
| } | |
| cdata->unit = i; | |
| cdata->num_records = count; | |
| cdata->rec = fmt_buffer[pos].record; | |
| cdata->blksize = fmt_buffer[pos].dl; | |
| cdata->key_length = fmt_buffer[pos].kl; | |
| } | |
| /* | |
| * Check the format of a range of tracks of a DASD. | |
| */ | |
| static int dasd_eckd_check_device_format(struct dasd_device *base, | |
| struct format_check_t *cdata, | |
| int enable_pav) | |
| { | |
| struct dasd_eckd_private *private = base->private; | |
| struct eckd_count *fmt_buffer; | |
| size_t fmt_buffer_size; | |
| unsigned int trkcount; | |
| int rpt_max, rpt_exp; | |
| struct irb irb; | |
| int trk_per_cyl; | |
| int tpm = 0; | |
| int rc; | |
| trk_per_cyl = private->rdc_data.trk_per_cyl; | |
| /* Get maximum and expected amount of records per track */ | |
| rpt_max = recs_per_track(&private->rdc_data, 0, 512) + 1; | |
| rpt_exp = recs_per_track(&private->rdc_data, 0, cdata->expect.blksize); | |
| trkcount = cdata->expect.stop_unit - cdata->expect.start_unit + 1; | |
| if (check_mul_overflow(trkcount, rpt_max, &fmt_buffer_size) || | |
| check_mul_overflow(fmt_buffer_size, sizeof(struct eckd_count), &fmt_buffer_size)) | |
| return -EINVAL; | |
| fmt_buffer = kzalloc(fmt_buffer_size, GFP_KERNEL | GFP_DMA); | |
| if (!fmt_buffer) | |
| return -ENOMEM; | |
| /* | |
| * A certain FICON feature subset is needed to operate in transport | |
| * mode. Additionally, the support for transport mode is implicitly | |
| * checked by comparing the buffer size with fcx_max_data. As long as | |
| * the buffer size is smaller we can operate in transport mode and | |
| * process multiple tracks. If not, only one track at once is being | |
| * processed using command mode. | |
| */ | |
| if ((private->features.feature[40] & 0x04) && | |
| fmt_buffer_size <= private->fcx_max_data) | |
| tpm = 1; | |
| rc = dasd_eckd_format_process_data(base, &cdata->expect, enable_pav, | |
| tpm, fmt_buffer, rpt_max, &irb); | |
| if (rc && rc != -EIO) | |
| goto out; | |
| if (rc == -EIO) { | |
| /* | |
| * If our first attempt with transport mode enabled comes back | |
| * with an incorrect length error, we're going to retry the | |
| * check with command mode. | |
| */ | |
| if (tpm && scsw_cstat(&irb.scsw) == 0x40) { | |
| tpm = 0; | |
| rc = dasd_eckd_format_process_data(base, &cdata->expect, | |
| enable_pav, tpm, | |
| fmt_buffer, rpt_max, | |
| &irb); | |
| if (rc) | |
| goto out; | |
| } else { | |
| goto out; | |
| } | |
| } | |
| dasd_eckd_format_evaluate_tracks(fmt_buffer, cdata, rpt_max, rpt_exp, | |
| trk_per_cyl, tpm); | |
| out: | |
| kfree(fmt_buffer); | |
| 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 = dasd_path_get_opm(cqr->block->base); | |
| } | |
| }; | |
| static dasd_erp_fn_t | |
| dasd_eckd_erp_action(struct dasd_ccw_req * cqr) | |
| { | |
| struct dasd_device *device = (struct dasd_device *) cqr->startdev; | |
| struct ccw_device *cdev = device->cdev; | |
| switch (cdev->id.cu_type) { | |
| case 0x3990: | |
| case 0x2105: | |
| case 0x2107: | |
| case 0x1750: | |
| return dasd_3990_erp_action; | |
| case 0x9343: | |
| case 0x3880: | |
| default: | |
| return dasd_default_erp_action; | |
| } | |
| } | |
| static dasd_erp_fn_t | |
| dasd_eckd_erp_postaction(struct dasd_ccw_req * cqr) | |
| { | |
| return dasd_default_erp_postaction; | |
| } | |
| 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 = 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)) { | |
| /* 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 ((sense[27] & DASD_SENSE_BIT_0) && (sense[7] == 0x0D) && | |
| (scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK)) { | |
| if (test_and_set_bit(DASD_FLAG_SUC, &device->flags)) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "eckd suc: device already notified"); | |
| return; | |
| } | |
| sense = dasd_get_sense(irb); | |
| if (!sense) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "eckd suc: no reason code available"); | |
| clear_bit(DASD_FLAG_SUC, &device->flags); | |
| return; | |
| } | |
| private->suc_reason = sense[8]; | |
| DBF_DEV_EVENT(DBF_NOTICE, device, "%s %x", | |
| "eckd handle summary unit check: reason", | |
| private->suc_reason); | |
| dasd_get_device(device); | |
| if (!schedule_work(&device->suc_work)) | |
| dasd_put_device(device); | |
| return; | |
| } | |
| /* service information message SIM */ | |
| if (!cqr && !(sense[27] & DASD_SENSE_BIT_0) && | |
| ((sense[6] & DASD_SIM_SENSE) == DASD_SIM_SENSE)) { | |
| dasd_3990_erp_handle_sim(device, sense); | |
| return; | |
| } | |
| /* 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"); | |
| } | |
| } | |
| static int dasd_eckd_ras_sanity_checks(struct dasd_device *device, | |
| unsigned int first_trk, | |
| unsigned int last_trk) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| unsigned int trks_per_vol; | |
| int rc = 0; | |
| trks_per_vol = private->real_cyl * private->rdc_data.trk_per_cyl; | |
| if (first_trk >= trks_per_vol) { | |
| dev_warn(&device->cdev->dev, | |
| "Start track number %u used in the space release command is too big\n", | |
| first_trk); | |
| rc = -EINVAL; | |
| } else if (last_trk >= trks_per_vol) { | |
| dev_warn(&device->cdev->dev, | |
| "Stop track number %u used in the space release command is too big\n", | |
| last_trk); | |
| rc = -EINVAL; | |
| } else if (first_trk > last_trk) { | |
| dev_warn(&device->cdev->dev, | |
| "Start track %u used in the space release command exceeds the end track\n", | |
| first_trk); | |
| rc = -EINVAL; | |
| } | |
| return rc; | |
| } | |
| /* | |
| * Number of extents the track range [from, to] spans. Extent n covers tracks | |
| * [n * trks_per_ext, (n + 1) * trks_per_ext - 1], so the range touches the | |
| * extents from (from / trks_per_ext) to (to / trks_per_ext) inclusive. | |
| */ | |
| static int count_exts(unsigned int from, unsigned int to, int trks_per_ext) | |
| { | |
| return to / trks_per_ext - from / trks_per_ext + 1; | |
| } | |
| static int dasd_in_copy_relation(struct dasd_device *device) | |
| { | |
| struct dasd_pprc_data_sc4 *temp; | |
| int rc; | |
| if (!dasd_eckd_pprc_enabled(device)) | |
| return 0; | |
| temp = kzalloc_obj(*temp); | |
| if (!temp) | |
| return -ENOMEM; | |
| rc = dasd_eckd_query_pprc_status(device, temp); | |
| if (!rc) | |
| rc = temp->dev_info[0].state; | |
| kfree(temp); | |
| return rc; | |
| } | |
| /* | |
| * Release allocated space for a given range or an entire volume. | |
| */ | |
| static struct dasd_ccw_req * | |
| dasd_eckd_dso_ras(struct dasd_device *device, struct dasd_block *block, | |
| struct request *req, unsigned int first_trk, | |
| unsigned int last_trk, int by_extent) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct dasd_dso_ras_ext_range *ras_range; | |
| struct dasd_rssd_features *features; | |
| struct dasd_dso_ras_data *ras_data; | |
| u16 heads, beg_head, end_head; | |
| int cur_to_trk, cur_from_trk; | |
| struct dasd_ccw_req *cqr; | |
| u32 beg_cyl, end_cyl; | |
| int copy_relation; | |
| struct ccw1 *ccw; | |
| int trks_per_ext; | |
| size_t ras_size; | |
| size_t size; | |
| int nr_exts; | |
| void *rq; | |
| int i; | |
| if (dasd_eckd_ras_sanity_checks(device, first_trk, last_trk)) | |
| return ERR_PTR(-EINVAL); | |
| /* | |
| * The block-layer discard path (req != NULL) runs in atomic context, so | |
| * it must not issue the sleeping copy-relation (PPRC) query. It also | |
| * leaves guarantee_init off - discard does not promise zeroing anyway. | |
| */ | |
| copy_relation = 0; | |
| if (!req) { | |
| copy_relation = dasd_in_copy_relation(device); | |
| if (copy_relation < 0) | |
| return ERR_PTR(copy_relation); | |
| } | |
| rq = req ? blk_mq_rq_to_pdu(req) : NULL; | |
| features = &private->features; | |
| trks_per_ext = dasd_eckd_ext_size(device) * private->rdc_data.trk_per_cyl; | |
| nr_exts = 0; | |
| if (by_extent) | |
| nr_exts = count_exts(first_trk, last_trk, trks_per_ext); | |
| ras_size = sizeof(*ras_data); | |
| size = ras_size + (nr_exts * sizeof(*ras_range)); | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, size, device, rq); | |
| if (IS_ERR(cqr)) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate RAS request"); | |
| return cqr; | |
| } | |
| ras_data = cqr->data; | |
| memset(ras_data, 0, size); | |
| ras_data->order = DSO_ORDER_RAS; | |
| ras_data->flags.vol_type = 0; /* CKD volume */ | |
| /* Release specified extents or entire volume */ | |
| ras_data->op_flags.by_extent = by_extent; | |
| /* | |
| * This bit guarantees initialisation of tracks within an extent that is | |
| * not fully specified, but is only supported with a certain feature | |
| * subset and for devices not in a copy relation. | |
| */ | |
| if (!req && features->feature[56] & 0x01 && !copy_relation) | |
| ras_data->op_flags.guarantee_init = 1; | |
| /* cached copies - conf.ned may be freed under us by the reload worker */ | |
| ras_data->lss = READ_ONCE(private->ned_lss); | |
| ras_data->dev_addr = READ_ONCE(private->ned_ua); | |
| ras_data->nr_exts = nr_exts; | |
| if (by_extent) { | |
| heads = private->rdc_data.trk_per_cyl; | |
| cur_from_trk = first_trk; | |
| cur_to_trk = first_trk + trks_per_ext - | |
| (first_trk % trks_per_ext) - 1; | |
| if (cur_to_trk > last_trk) | |
| cur_to_trk = last_trk; | |
| ras_range = (struct dasd_dso_ras_ext_range *)(cqr->data + ras_size); | |
| for (i = 0; i < nr_exts; i++) { | |
| beg_cyl = cur_from_trk / heads; | |
| beg_head = cur_from_trk % heads; | |
| end_cyl = cur_to_trk / heads; | |
| end_head = cur_to_trk % heads; | |
| set_ch_t(&ras_range->beg_ext, beg_cyl, beg_head); | |
| set_ch_t(&ras_range->end_ext, end_cyl, end_head); | |
| cur_from_trk = cur_to_trk + 1; | |
| cur_to_trk = cur_from_trk + trks_per_ext - 1; | |
| if (cur_to_trk > last_trk) | |
| cur_to_trk = last_trk; | |
| ras_range++; | |
| } | |
| } | |
| ccw = cqr->cpaddr; | |
| ccw->cda = virt_to_dma32(cqr->data); | |
| ccw->cmd_code = DASD_ECKD_CCW_DSO; | |
| ccw->count = size; | |
| cqr->startdev = device; | |
| cqr->memdev = device; | |
| cqr->block = block; | |
| cqr->retries = 256; | |
| cqr->expires = device->default_expires * HZ; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| return cqr; | |
| } | |
| static int dasd_eckd_release_space_full(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private; | |
| struct dasd_ccw_req *cqr; | |
| int rc; | |
| cqr = dasd_eckd_dso_ras(device, NULL, NULL, 0, 0, 0); | |
| if (IS_ERR(cqr)) | |
| return PTR_ERR(cqr); | |
| rc = dasd_sleep_on_interruptible(cqr); | |
| if (!rc) { | |
| /* | |
| * Releasing all space (RAS) wipes every track and the device is | |
| * fully sparse again, so restart the heuristic from ft1. | |
| */ | |
| dasd_ft_bias_apply(device); | |
| /* | |
| * A full release is what makes a subsequent format a quick | |
| * (thin) one; remember it so the format label records that. | |
| */ | |
| private = device->private; | |
| private->ese_format_quick = 1; | |
| } | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| static int dasd_eckd_release_space_trks(struct dasd_device *device, | |
| unsigned int from, unsigned int to) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct dasd_block *block = device->block; | |
| struct dasd_ccw_req *cqr, *n; | |
| struct list_head ras_queue; | |
| unsigned int device_exts; | |
| int trks_per_ext; | |
| int stop, step; | |
| int cur_pos; | |
| int rc = 0; | |
| int retry; | |
| INIT_LIST_HEAD(&ras_queue); | |
| if (dasd_eckd_ext_size(device) == 0) | |
| return -EINVAL; | |
| device_exts = private->real_cyl / dasd_eckd_ext_size(device); | |
| trks_per_ext = dasd_eckd_ext_size(device) * private->rdc_data.trk_per_cyl; | |
| /* Make sure device limits are not exceeded */ | |
| step = trks_per_ext * min(device_exts, DASD_ECKD_RAS_EXTS_MAX); | |
| cur_pos = from; | |
| do { | |
| retry = 0; | |
| while (cur_pos < to) { | |
| stop = cur_pos + step - | |
| ((cur_pos + step) % trks_per_ext) - 1; | |
| if (stop > to) | |
| stop = to; | |
| cqr = dasd_eckd_dso_ras(device, NULL, NULL, cur_pos, stop, 1); | |
| if (IS_ERR(cqr)) { | |
| rc = PTR_ERR(cqr); | |
| if (rc == -ENOMEM) { | |
| if (list_empty(&ras_queue)) | |
| goto out; | |
| retry = 1; | |
| break; | |
| } | |
| goto err_out; | |
| } | |
| spin_lock_irq(&block->queue_lock); | |
| list_add_tail(&cqr->blocklist, &ras_queue); | |
| spin_unlock_irq(&block->queue_lock); | |
| cur_pos = stop + 1; | |
| } | |
| rc = dasd_sleep_on_queue_interruptible(&ras_queue); | |
| err_out: | |
| list_for_each_entry_safe(cqr, n, &ras_queue, blocklist) { | |
| device = cqr->startdev; | |
| private = device->private; | |
| spin_lock_irq(&block->queue_lock); | |
| list_del_init(&cqr->blocklist); | |
| spin_unlock_irq(&block->queue_lock); | |
| dasd_sfree_request(cqr, device); | |
| private->count--; | |
| } | |
| } while (retry); | |
| out: | |
| return rc; | |
| } | |
| static int dasd_eckd_release_space(struct dasd_device *device, | |
| struct format_data_t *rdata) | |
| { | |
| /* | |
| * Space release (and thus a quick format) requires real ESE hardware. | |
| * is_ese() may be true from a copied label on non-ESE hardware, so gate | |
| * on the hardware capability, not on is_ese(). | |
| */ | |
| if (!dasd_eckd_ese_capable(device)) | |
| return -EOPNOTSUPP; | |
| if (rdata->intensity & DASD_FMT_INT_ESE_FULL) | |
| return dasd_eckd_release_space_full(device); | |
| else if (rdata->intensity == 0) | |
| return dasd_eckd_release_space_trks(device, rdata->start_unit, | |
| rdata->stop_unit); | |
| else | |
| return -EINVAL; | |
| } | |
| static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single( | |
| struct dasd_device *startdev, | |
| struct dasd_block *block, | |
| struct request *req, | |
| sector_t first_rec, | |
| sector_t last_rec, | |
| sector_t first_trk, | |
| sector_t last_trk, | |
| unsigned int first_offs, | |
| unsigned int last_offs, | |
| unsigned int blk_per_trk, | |
| unsigned int blksize) | |
| { | |
| struct dasd_eckd_private *private; | |
| dma64_t *idaws; | |
| struct LO_eckd_data *LO_data; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| struct req_iterator iter; | |
| struct bio_vec bv; | |
| char *dst; | |
| unsigned int off; | |
| int count, cidaw, cplength, datasize; | |
| sector_t recid; | |
| unsigned char cmd, rcmd; | |
| int use_prefix; | |
| struct dasd_device *basedev; | |
| basedev = block->base; | |
| private = basedev->private; | |
| if (rq_data_dir(req) == READ) | |
| cmd = DASD_ECKD_CCW_READ_MT; | |
| else if (rq_data_dir(req) == WRITE) | |
| cmd = DASD_ECKD_CCW_WRITE_MT; | |
| else | |
| return ERR_PTR(-EINVAL); | |
| /* Check struct bio and count the number of blocks for the request. */ | |
| count = 0; | |
| cidaw = 0; | |
| rq_for_each_segment(bv, req, iter) { | |
| if (bv.bv_len & (blksize - 1)) | |
| /* Eckd can only do full blocks. */ | |
| return ERR_PTR(-EINVAL); | |
| count += bv.bv_len >> (block->s2b_shift + 9); | |
| if (idal_is_needed (page_address(bv.bv_page), bv.bv_len)) | |
| cidaw += bv.bv_len >> (block->s2b_shift + 9); | |
| } | |
| /* Paranoia. */ | |
| if (count != last_rec - first_rec + 1) | |
| return ERR_PTR(-EINVAL); | |
| /* use the prefix command if available */ | |
| use_prefix = private->features.feature[8] & 0x01; | |
| if (use_prefix) { | |
| /* 1x prefix + number of blocks */ | |
| cplength = 2 + count; | |
| /* 1x prefix + cidaws*sizeof(long) */ | |
| datasize = sizeof(struct PFX_eckd_data) + | |
| sizeof(struct LO_eckd_data) + | |
| cidaw * sizeof(unsigned long); | |
| } else { | |
| /* 1x define extent + 1x locate record + number of blocks */ | |
| cplength = 2 + count; | |
| /* 1x define extent + 1x locate record + cidaws*sizeof(long) */ | |
| datasize = sizeof(struct DE_eckd_data) + | |
| sizeof(struct LO_eckd_data) + | |
| cidaw * sizeof(unsigned long); | |
| } | |
| /* Find out the number of additional locate record ccws for cdl. */ | |
| if (private->uses_cdl && first_rec < 2*blk_per_trk) { | |
| if (last_rec >= 2*blk_per_trk) | |
| count = 2*blk_per_trk - first_rec; | |
| cplength += count; | |
| datasize += count*sizeof(struct LO_eckd_data); | |
| } | |
| /* Allocate the ccw request. */ | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, | |
| startdev, blk_mq_rq_to_pdu(req)); | |
| if (IS_ERR(cqr)) | |
| return cqr; | |
| ccw = cqr->cpaddr; | |
| /* First ccw is define extent or prefix. */ | |
| if (use_prefix) { | |
| if (prefix(ccw++, cqr->data, first_trk, | |
| last_trk, cmd, basedev, startdev) == -EAGAIN) { | |
| /* Clock not in sync and XRC is enabled. | |
| * Try again later. | |
| */ | |
| dasd_sfree_request(cqr, startdev); | |
| return ERR_PTR(-EAGAIN); | |
| } | |
| idaws = (dma64_t *)(cqr->data + sizeof(struct PFX_eckd_data)); | |
| } else { | |
| if (define_extent(ccw++, cqr->data, first_trk, | |
| last_trk, cmd, basedev, 0) == -EAGAIN) { | |
| /* Clock not in sync and XRC is enabled. | |
| * Try again later. | |
| */ | |
| dasd_sfree_request(cqr, startdev); | |
| return ERR_PTR(-EAGAIN); | |
| } | |
| idaws = (dma64_t *)(cqr->data + sizeof(struct DE_eckd_data)); | |
| } | |
| /* Build locate_record+read/write/ccws. */ | |
| LO_data = (struct LO_eckd_data *) (idaws + cidaw); | |
| recid = first_rec; | |
| if (private->uses_cdl == 0 || recid > 2*blk_per_trk) { | |
| /* Only standard blocks so there is just one locate record. */ | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| locate_record(ccw++, LO_data++, first_trk, first_offs + 1, | |
| last_rec - recid + 1, cmd, basedev, blksize); | |
| } | |
| rq_for_each_segment(bv, req, iter) { | |
| dst = bvec_virt(&bv); | |
| if (dasd_page_cache) { | |
| char *copy = kmem_cache_alloc(dasd_page_cache, | |
| GFP_DMA | __GFP_NOWARN); | |
| if (copy && rq_data_dir(req) == WRITE) | |
| memcpy(copy + bv.bv_offset, dst, bv.bv_len); | |
| if (copy) | |
| dst = copy + bv.bv_offset; | |
| } | |
| for (off = 0; off < bv.bv_len; off += blksize) { | |
| sector_t trkid = recid; | |
| unsigned int recoffs = sector_div(trkid, blk_per_trk); | |
| rcmd = cmd; | |
| count = blksize; | |
| /* Locate record for cdl special block ? */ | |
| if (private->uses_cdl && recid < 2*blk_per_trk) { | |
| if (dasd_eckd_cdl_special(blk_per_trk, recid)){ | |
| rcmd |= 0x8; | |
| count = dasd_eckd_cdl_reclen(recid); | |
| if (count < blksize && | |
| rq_data_dir(req) == READ) | |
| memset(dst + count, 0xe5, | |
| blksize - count); | |
| } | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| locate_record(ccw++, LO_data++, | |
| trkid, recoffs + 1, | |
| 1, rcmd, basedev, count); | |
| } | |
| /* Locate record for standard blocks ? */ | |
| if (private->uses_cdl && recid == 2*blk_per_trk) { | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| locate_record(ccw++, LO_data++, | |
| trkid, recoffs + 1, | |
| last_rec - recid + 1, | |
| cmd, basedev, count); | |
| } | |
| /* Read/write ccw. */ | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| ccw->cmd_code = rcmd; | |
| ccw->count = count; | |
| if (idal_is_needed(dst, blksize)) { | |
| ccw->cda = virt_to_dma32(idaws); | |
| ccw->flags = CCW_FLAG_IDA; | |
| idaws = idal_create_words(idaws, dst, blksize); | |
| } else { | |
| ccw->cda = virt_to_dma32(dst); | |
| ccw->flags = 0; | |
| } | |
| ccw++; | |
| dst += blksize; | |
| recid++; | |
| } | |
| } | |
| if (blk_noretry_request(req) || | |
| block->base->features & DASD_FEATURE_FAILFAST) | |
| set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); | |
| cqr->startdev = startdev; | |
| cqr->memdev = startdev; | |
| cqr->block = block; | |
| cqr->expires = startdev->default_expires * HZ; /* default 5 minutes */ | |
| cqr->lpm = dasd_path_get_ppm(startdev); | |
| cqr->retries = startdev->default_retries; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| /* Set flags to suppress output for expected errors */ | |
| if (dasd_eckd_is_ese(basedev)) { | |
| set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); | |
| } | |
| return cqr; | |
| } | |
| static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_track( | |
| struct dasd_device *startdev, | |
| struct dasd_block *block, | |
| struct request *req, | |
| sector_t first_rec, | |
| sector_t last_rec, | |
| sector_t first_trk, | |
| sector_t last_trk, | |
| unsigned int first_offs, | |
| unsigned int last_offs, | |
| unsigned int blk_per_trk, | |
| unsigned int blksize) | |
| { | |
| dma64_t *idaws; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| struct req_iterator iter; | |
| struct bio_vec bv; | |
| char *dst, *idaw_dst; | |
| unsigned int cidaw, cplength, datasize; | |
| unsigned int tlf; | |
| sector_t recid; | |
| unsigned char cmd; | |
| struct dasd_device *basedev; | |
| unsigned int trkcount, count, count_to_trk_end; | |
| unsigned int idaw_len, seg_len, part_len, len_to_track_end; | |
| unsigned char new_track, end_idaw; | |
| sector_t trkid; | |
| unsigned int recoffs; | |
| basedev = block->base; | |
| if (rq_data_dir(req) == READ) | |
| cmd = DASD_ECKD_CCW_READ_TRACK_DATA; | |
| else if (rq_data_dir(req) == WRITE) | |
| cmd = DASD_ECKD_CCW_WRITE_TRACK_DATA; | |
| else | |
| return ERR_PTR(-EINVAL); | |
| /* Track based I/O needs IDAWs for each page, and not just for | |
| * 64 bit addresses. We need additional idals for pages | |
| * that get filled from two tracks, so we use the number | |
| * of records as upper limit. | |
| */ | |
| cidaw = last_rec - first_rec + 1; | |
| trkcount = last_trk - first_trk + 1; | |
| /* 1x prefix + one read/write ccw per track */ | |
| cplength = 1 + trkcount; | |
| datasize = sizeof(struct PFX_eckd_data) + cidaw * sizeof(unsigned long); | |
| /* Allocate the ccw request. */ | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, datasize, | |
| startdev, blk_mq_rq_to_pdu(req)); | |
| if (IS_ERR(cqr)) | |
| return cqr; | |
| ccw = cqr->cpaddr; | |
| /* transfer length factor: how many bytes to read from the last track */ | |
| if (first_trk == last_trk) | |
| tlf = last_offs - first_offs + 1; | |
| else | |
| tlf = last_offs + 1; | |
| tlf *= blksize; | |
| if (prefix_LRE(ccw++, cqr->data, first_trk, | |
| last_trk, cmd, basedev, startdev, | |
| 1 /* format */, first_offs + 1, | |
| trkcount, blksize, | |
| tlf) == -EAGAIN) { | |
| /* Clock not in sync and XRC is enabled. | |
| * Try again later. | |
| */ | |
| dasd_sfree_request(cqr, startdev); | |
| return ERR_PTR(-EAGAIN); | |
| } | |
| /* | |
| * The translation of request into ccw programs must meet the | |
| * following conditions: | |
| * - all idaws but the first and the last must address full pages | |
| * (or 2K blocks on 31-bit) | |
| * - the scope of a ccw and it's idal ends with the track boundaries | |
| */ | |
| idaws = (dma64_t *)(cqr->data + sizeof(struct PFX_eckd_data)); | |
| recid = first_rec; | |
| new_track = 1; | |
| end_idaw = 0; | |
| len_to_track_end = 0; | |
| idaw_dst = NULL; | |
| idaw_len = 0; | |
| rq_for_each_segment(bv, req, iter) { | |
| dst = bvec_virt(&bv); | |
| seg_len = bv.bv_len; | |
| while (seg_len) { | |
| if (new_track) { | |
| trkid = recid; | |
| recoffs = sector_div(trkid, blk_per_trk); | |
| count_to_trk_end = blk_per_trk - recoffs; | |
| count = min((last_rec - recid + 1), | |
| (sector_t)count_to_trk_end); | |
| len_to_track_end = count * blksize; | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| ccw->cmd_code = cmd; | |
| ccw->count = len_to_track_end; | |
| ccw->cda = virt_to_dma32(idaws); | |
| ccw->flags = CCW_FLAG_IDA; | |
| 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, 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) { | |
| if ((unsigned long)(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); | |
| } | |
| part_len = min(seg_len, len_to_track_end); | |
| seg_len -= part_len; | |
| dst += part_len; | |
| idaw_len += part_len; | |
| len_to_track_end -= part_len; | |
| /* collected memory area ends on an IDA_BLOCK border, | |
| * -> create an idaw | |
| * idal_create_words will handle cases where idaw_len | |
| * is larger then IDA_BLOCK_SIZE | |
| */ | |
| if (!((unsigned long)(idaw_dst + idaw_len) & (IDA_BLOCK_SIZE - 1))) | |
| end_idaw = 1; | |
| /* We also need to end the idaw at track end */ | |
| if (!len_to_track_end) { | |
| new_track = 1; | |
| end_idaw = 1; | |
| } | |
| if (end_idaw) { | |
| idaws = idal_create_words(idaws, idaw_dst, | |
| idaw_len); | |
| idaw_dst = NULL; | |
| idaw_len = 0; | |
| end_idaw = 0; | |
| } | |
| } | |
| } | |
| 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; /* default 5 minutes */ | |
| cqr->lpm = dasd_path_get_ppm(startdev); | |
| cqr->retries = startdev->default_retries; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| /* Set flags to suppress output for expected errors */ | |
| if (dasd_eckd_is_ese(basedev)) | |
| set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); | |
| return cqr; | |
| } | |
| static int prepare_itcw(struct itcw *itcw, | |
| unsigned int trk, unsigned int totrk, int cmd, | |
| struct dasd_device *basedev, | |
| struct dasd_device *startdev, | |
| unsigned int rec_on_trk, int count, | |
| unsigned int blksize, | |
| unsigned int total_data_size, | |
| unsigned int tlf, | |
| unsigned int blk_per_trk) | |
| { | |
| u8 pfxbuf[sizeof(struct PFX_eckd_data) + 2] __aligned(8); | |
| struct PFX_eckd_data *pfxdata = (struct PFX_eckd_data *)pfxbuf; | |
| struct dasd_eckd_private *basepriv, *startpriv; | |
| struct DE_eckd_data *dedata; | |
| struct LRE_eckd_data *lredata; | |
| struct dcw *dcw; | |
| int pfxsize; | |
| u32 begcyl, endcyl; | |
| u16 heads, beghead, endhead; | |
| u8 pfx_cmd; | |
| int rc = 0; | |
| int sector = 0; | |
| int dn, d; | |
| pfxsize = sizeof(struct PFX_eckd_data); | |
| /* prefix + LRE extended data */ | |
| if (cmd == DASD_ECKD_CCW_WRITE_FULL_TRACK) | |
| pfxsize += 2; | |
| memset(pfxbuf, 0, pfxsize); | |
| /* setup prefix data */ | |
| basepriv = basedev->private; | |
| startpriv = startdev->private; | |
| dedata = &pfxdata->define_extent; | |
| lredata = &pfxdata->locate_record; | |
| pfxdata->format = 1; /* PFX with LRE */ | |
| /* cached copies - conf.ned may be freed under us by the reload worker */ | |
| pfxdata->base_address = READ_ONCE(basepriv->ned_ua); | |
| pfxdata->base_lss = READ_ONCE(basepriv->ned_lss); | |
| 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; | |
| } | |
| switch (cmd) { | |
| case DASD_ECKD_CCW_READ_TRACK_DATA: | |
| dedata->mask.perm = 0x1; | |
| dedata->attributes.operation = basepriv->attrib.operation; | |
| dedata->blk_size = blksize; | |
| dedata->ga_extended |= 0x42; | |
| lredata->operation.orientation = 0x0; | |
| lredata->operation.operation = 0x0C; | |
| lredata->auxiliary.check_bytes = 0x01; | |
| pfx_cmd = DASD_ECKD_CCW_PFX_READ; | |
| break; | |
| case DASD_ECKD_CCW_WRITE_TRACK_DATA: | |
| dedata->mask.perm = 0x02; | |
| dedata->attributes.operation = basepriv->attrib.operation; | |
| dedata->blk_size = blksize; | |
| rc = set_timestamp(NULL, dedata, basedev); | |
| dedata->ga_extended |= 0x42; | |
| lredata->operation.orientation = 0x0; | |
| lredata->operation.operation = 0x3F; | |
| lredata->extended_operation = 0x23; | |
| lredata->auxiliary.check_bytes = 0x2; | |
| /* | |
| * If XRC is supported the System Time Stamp is set. 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' */ | |
| pfx_cmd = DASD_ECKD_CCW_PFX; | |
| break; | |
| case DASD_ECKD_CCW_WRITE_FULL_TRACK: | |
| dedata->mask.perm = 0x3; | |
| dedata->mask.auth = 0x00; | |
| dedata->attributes.operation = basepriv->attrib.operation; | |
| dedata->blk_size = blksize; | |
| dedata->ga_extended |= 0x42; | |
| rc = set_timestamp(NULL, dedata, basedev); | |
| lredata->operation.orientation = 0x0; | |
| lredata->operation.operation = 0x3F; | |
| lredata->extended_operation = 0x11; | |
| lredata->auxiliary.check_bytes = 0x2; | |
| lredata->extended_parameter_length = 0x02; | |
| if (count > 8) { | |
| lredata->extended_parameter[0] = 0xFF; | |
| lredata->extended_parameter[1] = 0xFF; | |
| lredata->extended_parameter[1] <<= (16 - count); | |
| } else { | |
| lredata->extended_parameter[0] = 0xFF; | |
| lredata->extended_parameter[0] <<= (8 - count); | |
| lredata->extended_parameter[1] = 0x00; | |
| } | |
| sector = 0xFF; | |
| /* | |
| * If XRC is supported the System Time Stamp is set. 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' */ | |
| pfx_cmd = DASD_ECKD_CCW_PFX; | |
| break; | |
| case DASD_ECKD_CCW_READ_COUNT_MT: | |
| dedata->mask.perm = 0x1; | |
| dedata->attributes.operation = DASD_BYPASS_CACHE; | |
| dedata->ga_extended |= 0x42; | |
| dedata->blk_size = blksize; | |
| lredata->operation.orientation = 0x2; | |
| lredata->operation.operation = 0x16; | |
| lredata->auxiliary.check_bytes = 0x01; | |
| pfx_cmd = DASD_ECKD_CCW_PFX_READ; | |
| break; | |
| default: | |
| DBF_DEV_EVENT(DBF_ERR, basedev, | |
| "prepare itcw, unknown opcode 0x%x", cmd); | |
| BUG(); | |
| break; | |
| } | |
| if (rc) | |
| return rc; | |
| dedata->attributes.mode = 0x3; /* ECKD */ | |
| heads = basepriv->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 (dedata->attributes.operation == DASD_SEQ_PRESTAGE || | |
| dedata->attributes.operation == DASD_SEQ_ACCESS) { | |
| if (endcyl + basepriv->attrib.nr_cyl < basepriv->real_cyl) | |
| endcyl += basepriv->attrib.nr_cyl; | |
| else | |
| endcyl = (basepriv->real_cyl - 1); | |
| } | |
| set_ch_t(&dedata->beg_ext, begcyl, beghead); | |
| set_ch_t(&dedata->end_ext, endcyl, endhead); | |
| dedata->ep_format = 0x20; /* records per track is valid */ | |
| dedata->ep_rec_per_track = blk_per_trk; | |
| if (rec_on_trk) { | |
| switch (basepriv->rdc_data.dev_type) { | |
| case 0x3390: | |
| dn = ceil_quot(blksize + 6, 232); | |
| d = 9 + ceil_quot(blksize + 6 * (dn + 1), 34); | |
| sector = (49 + (rec_on_trk - 1) * (10 + d)) / 8; | |
| break; | |
| case 0x3380: | |
| d = 7 + ceil_quot(blksize + 12, 32); | |
| sector = (39 + (rec_on_trk - 1) * (8 + d)) / 7; | |
| break; | |
| } | |
| } | |
| if (cmd == DASD_ECKD_CCW_READ_COUNT_MT) { | |
| lredata->auxiliary.length_valid = 0; | |
| lredata->auxiliary.length_scope = 0; | |
| lredata->sector = 0xff; | |
| } else { | |
| lredata->auxiliary.length_valid = 1; | |
| lredata->auxiliary.length_scope = 1; | |
| lredata->sector = sector; | |
| } | |
| lredata->auxiliary.imbedded_ccw_valid = 1; | |
| lredata->length = tlf; | |
| lredata->imbedded_ccw = cmd; | |
| lredata->count = count; | |
| set_ch_t(&lredata->seek_addr, begcyl, beghead); | |
| lredata->search_arg.cyl = lredata->seek_addr.cyl; | |
| lredata->search_arg.head = lredata->seek_addr.head; | |
| lredata->search_arg.record = rec_on_trk; | |
| dcw = itcw_add_dcw(itcw, pfx_cmd, 0, | |
| pfxdata, pfxsize, total_data_size); | |
| return PTR_ERR_OR_ZERO(dcw); | |
| } | |
| static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( | |
| struct dasd_device *startdev, | |
| struct dasd_block *block, | |
| struct request *req, | |
| sector_t first_rec, | |
| sector_t last_rec, | |
| sector_t first_trk, | |
| sector_t last_trk, | |
| unsigned int first_offs, | |
| unsigned int last_offs, | |
| unsigned int blk_per_trk, | |
| unsigned int blksize) | |
| { | |
| struct dasd_ccw_req *cqr; | |
| struct req_iterator iter; | |
| struct bio_vec bv; | |
| char *dst; | |
| unsigned int trkcount, ctidaw; | |
| unsigned char cmd; | |
| struct dasd_device *basedev; | |
| unsigned int tlf; | |
| struct itcw *itcw; | |
| 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; | |
| if (rq_data_dir(req) == READ) { | |
| cmd = DASD_ECKD_CCW_READ_TRACK_DATA; | |
| itcw_op = ITCW_OP_READ; | |
| } else if (rq_data_dir(req) == WRITE) { | |
| cmd = DASD_ECKD_CCW_WRITE_TRACK_DATA; | |
| itcw_op = ITCW_OP_WRITE; | |
| } else | |
| return ERR_PTR(-EINVAL); | |
| /* 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_MAGIC, 0, itcw_size, startdev, | |
| blk_mq_rq_to_pdu(req)); | |
| if (IS_ERR(cqr)) | |
| return cqr; | |
| /* transfer length factor: how many bytes to read from the last track */ | |
| if (first_trk == last_trk) | |
| tlf = last_offs - first_offs + 1; | |
| else | |
| tlf = last_offs + 1; | |
| 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, | |
| trkcount, blksize, | |
| (last_rec - first_rec + 1) * blksize, | |
| tlf, blk_per_trk) == -EAGAIN) { | |
| /* Clock not in sync and XRC is enabled. | |
| * Try again later. | |
| */ | |
| 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 seg_boundary_mask | |
| * to page boundaries and 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. | |
| */ | |
| if (rq_data_dir(req) == WRITE) { | |
| new_track = 1; | |
| recid = first_rec; | |
| rq_for_each_segment(bv, req, iter) { | |
| dst = bvec_virt(&bv); | |
| 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 = bvec_virt(&bv); | |
| last_tidaw = itcw_add_tidaw(itcw, 0x00, | |
| dst, bv.bv_len); | |
| if (IS_ERR(last_tidaw)) { | |
| ret = -EINVAL; | |
| goto out_error; | |
| } | |
| } | |
| } | |
| 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 = startdev->default_expires * HZ; /* default 5 minutes */ | |
| cqr->lpm = dasd_path_get_ppm(startdev); | |
| cqr->retries = startdev->default_retries; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| /* Set flags to suppress output for expected errors */ | |
| if (dasd_eckd_is_ese(basedev)) { | |
| set_bit(DASD_CQR_SUPPRESS_NRF, &cqr->flags); | |
| set_bit(DASD_CQR_SUPPRESS_IT, &cqr->flags); | |
| } | |
| return cqr; | |
| out_error: | |
| dasd_sfree_request(cqr, startdev); | |
| return ERR_PTR(ret); | |
| } | |
| static __always_inline bool crosses_page(const void *addr, size_t len) | |
| { | |
| return len && (offset_in_page(addr) + len > PAGE_SIZE); | |
| } | |
| static __always_inline void *reserve_nocross(char **p, size_t *space, size_t len) | |
| { | |
| size_t pad = crosses_page(*p, len) ? PAGE_SIZE - offset_in_page(*p) : 0; | |
| void *ret; | |
| if (*space < pad + len) | |
| return NULL; /* out of space */ | |
| *p += pad; | |
| *space -= pad; | |
| ret = *p; | |
| *p += len; | |
| *space -= len; | |
| return ret; | |
| } | |
| /* | |
| * Helpers for dasd_eckd_build_cp_tpm_writefulltrack(): append the TIDAWs for | |
| * one track-image element (R0 header, a count + data record, or the trailing | |
| * pseudo track end count) to the itcw. Return the last TIDAW, or NULL on failure. | |
| */ | |
| static struct tidaw *add_track_r0(struct itcw *itcw, char **fill, | |
| size_t *fillsize, u32 cyl, u16 head) | |
| { | |
| struct tidaw *tidaw; | |
| struct eckd_r0 *r0; | |
| r0 = reserve_nocross(fill, fillsize, sizeof(*r0)); | |
| if (WARN_ON_ONCE(!r0)) | |
| return NULL; | |
| set_chr_t(r0, cyl, head, 0); | |
| r0->count.dl = 8; | |
| tidaw = itcw_add_tidaw(itcw, 0, r0, sizeof(*r0)); | |
| return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw; | |
| } | |
| static struct tidaw *add_track_record(struct itcw *itcw, char **fill, | |
| size_t *fillsize, u32 cyl, u16 head, | |
| u8 rec, void *data, u32 dl) | |
| { | |
| struct eckd_count *count; | |
| struct tidaw *tidaw; | |
| count = reserve_nocross(fill, fillsize, sizeof(*count)); | |
| if (WARN_ON_ONCE(!count)) | |
| return NULL; | |
| set_chr_t(count, cyl, head, rec); | |
| count->dl = dl; | |
| tidaw = itcw_add_tidaw(itcw, 0, count, sizeof(*count)); | |
| if (IS_ERR_OR_NULL(tidaw)) | |
| return NULL; | |
| tidaw = itcw_add_tidaw(itcw, 0, data, dl); | |
| return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw; | |
| } | |
| static struct tidaw *add_track_end(struct itcw *itcw, char **fill, | |
| size_t *fillsize) | |
| { | |
| struct eckd_count *count; | |
| struct tidaw *tidaw; | |
| count = reserve_nocross(fill, fillsize, sizeof(*count)); | |
| if (WARN_ON_ONCE(!count)) | |
| return NULL; | |
| count->cyl = 0xffff; | |
| count->head = 0xffff; | |
| count->dl = 0xffff; | |
| count->record = 0xff; | |
| count->kl = 0xff; | |
| tidaw = itcw_add_tidaw(itcw, TIDAW_FLAGS_INSERT_CBC, count, sizeof(*count)); | |
| return IS_ERR_OR_NULL(tidaw) ? NULL : tidaw; | |
| } | |
| static struct dasd_ccw_req * | |
| dasd_eckd_build_cp_tpm_writefulltrack(struct dasd_device *startdev, | |
| struct dasd_block *block, | |
| struct request *req, | |
| sector_t first_rec, | |
| sector_t last_rec, | |
| sector_t first_trk, | |
| sector_t last_trk, | |
| unsigned int first_offs, | |
| unsigned int last_offs, | |
| unsigned int blk_per_trk, | |
| unsigned int blksize, | |
| struct dasd_ccw_req *ocqr) | |
| { | |
| struct dasd_eckd_private *private = block->base->private; | |
| unsigned int seg_len, part_len, len_to_track_end; | |
| unsigned int count, count_to_trk_end, offs; | |
| unsigned int trkcount, ctidaw, tlf; | |
| int itcw_op, rec_count, datasize; | |
| struct tidaw *last_tidaw = NULL; | |
| sector_t recid, trkid, curr_trk; | |
| unsigned char cmd, new_track; | |
| struct dasd_device *basedev; | |
| size_t itcw_size, fillsize; | |
| struct dasd_ccw_req *cqr; | |
| struct req_iterator iter; | |
| char *dst, *filldata; | |
| unsigned long flags; | |
| struct itcw *itcw; | |
| struct bio_vec bv; | |
| int ret = -EINVAL; | |
| void *nullrecord; | |
| u16 heads, head; | |
| u32 cyl; | |
| u8 rec; | |
| basedev = block->base; | |
| cmd = DASD_ECKD_CCW_WRITE_FULL_TRACK; | |
| itcw_op = ITCW_OP_WRITE; | |
| /* | |
| * 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. | |
| * Per track we emit one R0 tidaw, two tidaws per record (count field | |
| * plus data - a record never crosses a track or page boundary, as | |
| * part_len is clamped to both blksize and the track end), and one track | |
| * end tidaw: 2 * blk_per_trk + 2. | |
| * Round the +2 up to blk_per_trk-independent headroom via 2 * (blk_per_trk + 2). | |
| */ | |
| trkcount = last_trk - first_trk + 1; | |
| ctidaw = trkcount * 2 * (blk_per_trk + 2); | |
| /* | |
| * build_cp (ocqr == NULL): the request owns its CCW program - block in | |
| * the pdu, ITCW in ccw_chunks. ese_format (ocqr != NULL): the failing | |
| * origin still owns its pdu, so take the replacement from ese_chunks. | |
| */ | |
| itcw_size = itcw_calc_size(0, ctidaw, 0); | |
| if (ocqr) | |
| cqr = dasd_fmalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev); | |
| else | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 0, itcw_size, startdev, | |
| blk_mq_rq_to_pdu(req)); | |
| if (IS_ERR(cqr)) | |
| return cqr; | |
| fillsize = trkcount * (sizeof(struct eckd_r0) + | |
| (sizeof(struct eckd_count) * (blk_per_trk + 2))); | |
| /* | |
| * reserve_nocross() pads elements away from page boundaries and draws | |
| * that padding from fillsize; budget one element per page the buffer | |
| * may span so it never runs short. | |
| */ | |
| fillsize += (fillsize / PAGE_SIZE + 1) * sizeof(struct eckd_r0); | |
| spin_lock_irqsave(&startdev->mem_lock, flags); | |
| filldata = dasd_alloc_chunk(&startdev->fill_chunks, fillsize); | |
| spin_unlock_irqrestore(&startdev->mem_lock, flags); | |
| if (!filldata) { | |
| ret = -ENOMEM; | |
| goto out_error; | |
| } | |
| memset(filldata, 0, fillsize); | |
| cqr->filldata = filldata; | |
| nullrecord = startdev->nulldata; | |
| /* count + data for each record, plus r0 and the pseudo count */ | |
| tlf = blk_per_trk * (blksize + sizeof(struct eckd_count)); | |
| tlf += sizeof(struct eckd_r0) + sizeof(struct eckd_count); | |
| 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); | |
| datasize = trkcount * tlf; | |
| if (prepare_itcw(itcw, first_trk, last_trk, | |
| cmd, basedev, startdev, | |
| 0, | |
| trkcount, blksize, | |
| datasize, | |
| tlf, | |
| blk_per_trk) == -EAGAIN) { | |
| /* Clock not in sync and XRC is enabled. | |
| * Try again later. | |
| */ | |
| ret = -EAGAIN; | |
| goto out_error; | |
| } | |
| heads = private->rdc_data.trk_per_cyl; | |
| /* | |
| * A tidaw can address 4k of memory, but must not cross page boundaries | |
| * We can let the block layer handle this by setting seg_boundary_mask | |
| * to page boundaries and max_segment_size to page size when setting up | |
| * the request queue. | |
| */ | |
| curr_trk = first_trk; | |
| recid = first_rec; | |
| trkid = recid; | |
| offs = sector_div(trkid, blk_per_trk); | |
| count = blk_per_trk; | |
| len_to_track_end = count * blksize; | |
| recid += count - first_offs; | |
| new_track = 0; | |
| /* the R0 header of the first track */ | |
| cyl = curr_trk / heads; | |
| head = curr_trk % heads; | |
| last_tidaw = add_track_r0(itcw, &filldata, &fillsize, cyl, head); | |
| if (!last_tidaw) | |
| goto out_error; | |
| /* empty records before the first data record */ | |
| for (int i = 1; i <= first_offs; i++) { | |
| len_to_track_end -= blksize; | |
| last_tidaw = add_track_record(itcw, &filldata, &fillsize, | |
| cyl, head, i, nullrecord, blksize); | |
| if (!last_tidaw) | |
| goto out_error; | |
| } | |
| /* process data records */ | |
| rec = first_offs + 1; | |
| rec_count = 0; | |
| rq_for_each_segment(bv, req, iter) { | |
| dst = bvec_virt(&bv); | |
| 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); | |
| /* | |
| * Size to the physical track end: a short last | |
| * track is padded in out_skip, so the track-end | |
| * marker must not be emitted early here. | |
| */ | |
| len_to_track_end = count_to_trk_end * blksize; | |
| recid += count; | |
| new_track = 0; | |
| /* the R0 header of the next track */ | |
| cyl = curr_trk / heads; | |
| head = curr_trk % heads; | |
| last_tidaw = add_track_r0(itcw, &filldata, | |
| &fillsize, cyl, head); | |
| if (!last_tidaw) | |
| goto out_error; | |
| rec = 1; | |
| } | |
| /* | |
| * One count + data record per block: a bvec segment can | |
| * be up to a page, so clamp to blksize - otherwise the | |
| * count field would describe one oversized record instead | |
| * of several blksize ones for sub-page block sizes. | |
| */ | |
| part_len = min(seg_len, len_to_track_end); | |
| part_len = min(part_len, blksize); | |
| seg_len -= part_len; | |
| len_to_track_end -= part_len; | |
| /* | |
| * This block ends the track; the next one starts a new | |
| * track. The track-end marker emitted below carries the | |
| * CBC flag. | |
| */ | |
| if (!len_to_track_end) | |
| new_track = 1; | |
| last_tidaw = add_track_record(itcw, &filldata, &fillsize, | |
| cyl, head, rec, dst, part_len); | |
| if (!last_tidaw) | |
| goto out_error; | |
| if (new_track) { | |
| /* add track end marker */ | |
| last_tidaw = add_track_end(itcw, &filldata, | |
| &fillsize); | |
| if (!last_tidaw) | |
| goto out_error; | |
| curr_trk++; | |
| } | |
| rec++; | |
| dst += part_len; | |
| rec_count++; | |
| if (rec_count >= (last_rec - first_rec + 1)) | |
| goto out_skip; | |
| } | |
| } | |
| out_skip: | |
| new_track = 0; | |
| /* empty records after the last data record */ | |
| for (int i = last_offs + 2; i <= blk_per_trk; i++) { | |
| len_to_track_end -= blksize; | |
| last_tidaw = add_track_record(itcw, &filldata, &fillsize, | |
| cyl, head, i, nullrecord, blksize); | |
| if (!last_tidaw) | |
| goto out_error; | |
| new_track = 1; | |
| } | |
| /* add track end marker */ | |
| if (new_track) { | |
| last_tidaw = add_track_end(itcw, &filldata, &fillsize); | |
| if (!last_tidaw) | |
| goto out_error; | |
| } | |
| 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 = startdev->default_expires * HZ; /* default 5 minutes */ | |
| cqr->lpm = dasd_path_get_ppm(startdev); | |
| cqr->retries = startdev->default_retries; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| return cqr; | |
| out_error: | |
| /* dasd_sfree_request frees from the right pool via cqr->mem_chunk */ | |
| dasd_sfree_request(cqr, startdev); | |
| return ERR_PTR(ret); | |
| } | |
| static struct dasd_ccw_req * | |
| dasd_eckd_build_cp_discard(struct dasd_device *device, struct dasd_block *block, | |
| struct request *req, sector_t first_trk, | |
| sector_t last_trk, unsigned int first_offs, | |
| unsigned int last_offs, unsigned int blk_per_trk) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| sector_t first_ext_trk, last_ext_end, last_ext_trk; | |
| unsigned int trks_per_ext; | |
| trks_per_ext = dasd_eckd_ext_size(device) * private->rdc_data.trk_per_cyl; | |
| if (!trks_per_ext) | |
| return ERR_PTR(-EOPNOTSUPP); | |
| /* | |
| * A discard range is rarely track-aligned: fstrim is FS-block granular | |
| * and discard_granularity is only a hint. If it starts or ends mid-track, | |
| * that boundary track still holds live records outside the range, so drop | |
| * it from the whole-track span first. Otherwise a partial boundary track | |
| * that happens to sit on an extent boundary would be released together | |
| * with its live records resulting in silent data loss | |
| */ | |
| if (first_offs) /* partial first track */ | |
| first_trk++; | |
| if (last_offs != blk_per_trk - 1) { /* partial last track */ | |
| if (!last_trk) | |
| return ERR_PTR(-EOPNOTSUPP); | |
| last_trk--; | |
| } | |
| if (first_trk > last_trk) | |
| return ERR_PTR(-EOPNOTSUPP); /* no whole track fully covered */ | |
| /* | |
| * RAS releases whole extents. Only release extents that lie entirely | |
| * within the (now whole-track) discard range by rounding inward to extent | |
| * boundaries - an extent shared with a live allocation must never be | |
| * released. If no whole extent is covered there is nothing to release | |
| * safely (e.g. a sub-extent discard, unavoidable with large extents), so | |
| * reject the request rather than release too much. | |
| */ | |
| first_ext_trk = roundup(first_trk, trks_per_ext); | |
| /* one past the last whole extent inside the range (exclusive) */ | |
| last_ext_end = rounddown(last_trk + 1, trks_per_ext); | |
| if (first_ext_trk >= last_ext_end) | |
| return ERR_PTR(-EOPNOTSUPP); | |
| /* inclusive last track; the guard above keeps this from underflowing */ | |
| last_ext_trk = last_ext_end - 1; | |
| return dasd_eckd_dso_ras(device, block, req, first_ext_trk, | |
| last_ext_trk, 1); | |
| } | |
| static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, | |
| struct dasd_block *block, | |
| struct request *req) | |
| { | |
| int cmdrtd, cmdwtd; | |
| int use_prefix; | |
| int fcx_multitrack; | |
| struct dasd_eckd_private *private; | |
| 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; | |
| if (dasd_ese_adaptive(basedev)) | |
| dasd_ese_heuristic_tick(basedev); | |
| else | |
| /* re-assert the endpoint mode: a stale heuristic write cannot stick */ | |
| basedev->fulltrack = (basedev->ft_bias >= DASD_FT_BIAS_MAX) ? 1 : 0; | |
| private = basedev->private; | |
| /* 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 = blk_rq_pos(req) >> block->s2b_shift; | |
| first_offs = sector_div(first_trk, blk_per_trk); | |
| last_rec = last_trk = | |
| (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); | |
| if (req_op(req) == REQ_OP_DISCARD) | |
| return dasd_eckd_build_cp_discard(startdev, block, req, | |
| first_trk, last_trk, | |
| first_offs, last_offs, | |
| blk_per_trk); | |
| fcx_multitrack = private->features.feature[40] & 0x20; | |
| data_size = blk_rq_bytes(req); | |
| if (data_size % blksize || data_size == 0) | |
| 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; | |
| cmdwtd = private->features.feature[12] & 0x40; | |
| use_prefix = private->features.feature[8] & 0x01; | |
| cqr = NULL; | |
| if (cdlspecial || dasd_page_cache) { | |
| /* do nothing, just fall through to the cmd mode single case */ | |
| } else if ((data_size <= private->fcx_max_data) | |
| && (fcx_multitrack || (first_trk == last_trk))) { | |
| if (!first_offs && (last_offs + 1 == blk_per_trk) && | |
| rq_data_dir(req) == WRITE && basedev->fulltrack) { | |
| cqr = dasd_eckd_build_cp_tpm_writefulltrack(startdev, block, req, | |
| first_rec, last_rec, | |
| first_trk, last_trk, | |
| first_offs, last_offs, | |
| blk_per_trk, blksize, NULL); | |
| } else { | |
| 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) && | |
| (PTR_ERR(cqr) != -ENOMEM)) | |
| cqr = NULL; | |
| } else if (use_prefix && | |
| (((rq_data_dir(req) == READ) && cmdrtd) || | |
| ((rq_data_dir(req) == WRITE) && cmdwtd))) { | |
| cqr = dasd_eckd_build_cp_cmd_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) && | |
| (PTR_ERR(cqr) != -ENOMEM)) | |
| cqr = NULL; | |
| } | |
| if (!cqr) | |
| cqr = dasd_eckd_build_cp_cmd_single(startdev, block, req, | |
| first_rec, last_rec, | |
| first_trk, last_trk, | |
| first_offs, last_offs, | |
| blk_per_trk, blksize); | |
| if (!IS_ERR(cqr)) { | |
| cqr->start_trk = first_trk; | |
| cqr->end_trk = last_trk; | |
| } | |
| return cqr; | |
| } | |
| static struct dasd_ccw_req *dasd_eckd_build_cp_raw(struct dasd_device *startdev, | |
| struct dasd_block *block, | |
| struct request *req) | |
| { | |
| sector_t start_padding_sectors, end_sector_offset, end_padding_sectors; | |
| unsigned int seg_len, len_to_track_end; | |
| unsigned int cidaw, cplength, datasize; | |
| sector_t first_trk, last_trk, sectors; | |
| struct dasd_eckd_private *base_priv; | |
| struct dasd_device *basedev; | |
| struct req_iterator iter; | |
| struct dasd_ccw_req *cqr; | |
| unsigned int trkcount; | |
| unsigned int size; | |
| unsigned char cmd; | |
| struct bio_vec bv; | |
| struct ccw1 *ccw; | |
| dma64_t *idaws; | |
| int use_prefix; | |
| void *data; | |
| char *dst; | |
| /* | |
| * raw track access needs to be multiple 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 (%llu,%llu) req %p", | |
| start_padding_sectors, end_padding_sectors, req); | |
| return ERR_PTR(-EINVAL); | |
| } | |
| 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; | |
| 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 | |
| return ERR_PTR(-EINVAL); | |
| /* | |
| * Raw track based I/O needs IDAWs for each page, | |
| * and not just for 64 bit addresses. | |
| */ | |
| cidaw = trkcount * DASD_RAW_BLOCK_PER_TRACK; | |
| /* | |
| * struct PFX_eckd_data and struct LRE_eckd_data can have up to 2 bytes | |
| * of extended parameter. This is needed for write full track. | |
| */ | |
| base_priv = basedev->private; | |
| use_prefix = base_priv->features.feature[8] & 0x01; | |
| if (use_prefix) { | |
| cplength = 1 + trkcount; | |
| size = sizeof(struct PFX_eckd_data) + 2; | |
| } else { | |
| cplength = 2 + trkcount; | |
| size = sizeof(struct DE_eckd_data) + | |
| sizeof(struct LRE_eckd_data) + 2; | |
| } | |
| size = ALIGN(size, 8); | |
| datasize = size + cidaw * sizeof(unsigned long); | |
| /* Allocate the ccw request. */ | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, cplength, | |
| datasize, startdev, blk_mq_rq_to_pdu(req)); | |
| if (IS_ERR(cqr)) | |
| return cqr; | |
| ccw = cqr->cpaddr; | |
| data = cqr->data; | |
| if (use_prefix) { | |
| prefix_LRE(ccw++, data, first_trk, last_trk, cmd, basedev, | |
| startdev, 1, 0, trkcount, 0, 0); | |
| } else { | |
| define_extent(ccw++, data, first_trk, last_trk, cmd, basedev, 0); | |
| ccw[-1].flags |= CCW_FLAG_CC; | |
| data += sizeof(struct DE_eckd_data); | |
| locate_record_ext(ccw++, data, first_trk, 0, | |
| trkcount, cmd, basedev, 0, 0); | |
| } | |
| idaws = (dma64_t *)(cqr->data + size); | |
| 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 = virt_to_dma32(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 = bvec_virt(&bv); | |
| 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 = virt_to_dma32(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 = dasd_path_get_ppm(startdev); | |
| cqr->retries = startdev->default_retries; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| 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; | |
| char *dst, *cda; | |
| unsigned int blksize, blk_per_trk, off; | |
| sector_t recid; | |
| int status; | |
| /* | |
| * A format-aborted request finished nothing - its replacement | |
| * completes the block request - so report ABORTED instead of DONE, | |
| * but still release its bounce buffers like any other request. | |
| */ | |
| if (cqr->status == DASD_CQR_ABORTED) | |
| status = DASD_CQR_ABORTED; | |
| else | |
| status = cqr->status == DASD_CQR_DONE; | |
| /* transport mode has no dasd_page_cache bounce buffers to release */ | |
| if (!dasd_page_cache || cqr->cpmode) | |
| goto out; | |
| private = cqr->block->base->private; | |
| blksize = cqr->block->bp_block; | |
| blk_per_trk = recs_per_track(&private->rdc_data, 0, blksize); | |
| 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 = bvec_virt(&bv); | |
| for (off = 0; off < bv.bv_len; off += blksize) { | |
| /* Skip locate record. */ | |
| if (private->uses_cdl && recid <= 2*blk_per_trk) | |
| ccw++; | |
| if (dst) { | |
| if (ccw->flags & CCW_FLAG_IDA) | |
| cda = dma64_to_virt(*((dma64_t *)dma32_to_virt(ccw->cda))); | |
| else | |
| cda = dma32_to_virt(ccw->cda); | |
| if (dst != cda) { | |
| if (rq_data_dir(req) == READ) | |
| memcpy(dst, cda, bv.bv_len); | |
| kmem_cache_free(dasd_page_cache, | |
| (void *)((addr_t)cda & PAGE_MASK)); | |
| } | |
| dst = NULL; | |
| } | |
| ccw++; | |
| recid++; | |
| } | |
| } | |
| out: | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return status; | |
| } | |
| /* | |
| * Modify ccw/tcw in cqr so it can be started on a base device. | |
| * | |
| * Note that this is not enough to restart the cqr! | |
| * Either reset cqr->startdev as well (summary unit check handling) | |
| * or restart via separate cqr (as in ERP handling). | |
| */ | |
| void dasd_eckd_reset_ccw_to_base_io(struct dasd_ccw_req *cqr) | |
| { | |
| struct ccw1 *ccw; | |
| struct PFX_eckd_data *pfxdata; | |
| struct tcw *tcw; | |
| struct tccb *tccb; | |
| struct dcw *dcw; | |
| if (cqr->cpmode == 1) { | |
| tcw = cqr->cpaddr; | |
| tccb = tcw_get_tccb(tcw); | |
| dcw = (struct dcw *)&tccb->tca[0]; | |
| pfxdata = (struct PFX_eckd_data *)&dcw->cd[0]; | |
| pfxdata->validity.verify_base = 0; | |
| pfxdata->validity.hyper_pav = 0; | |
| } else { | |
| ccw = cqr->cpaddr; | |
| pfxdata = cqr->data; | |
| if (ccw->cmd_code == DASD_ECKD_CCW_PFX) { | |
| pfxdata->validity.verify_base = 0; | |
| pfxdata->validity.hyper_pav = 0; | |
| } | |
| } | |
| } | |
| #define DASD_ECKD_CHANQ_MAX_SIZE 4 | |
| static struct dasd_ccw_req *dasd_eckd_build_alias_cp(struct dasd_device *base, | |
| struct dasd_block *block, | |
| struct request *req) | |
| { | |
| struct dasd_eckd_private *private; | |
| struct dasd_device *startdev = NULL; | |
| unsigned long flags; | |
| struct dasd_ccw_req *cqr; | |
| /* Discard requests (space release) can only run on the base device. */ | |
| if (req_op(req) != REQ_OP_DISCARD) | |
| startdev = dasd_alias_get_start_dev(base); | |
| if (!startdev) | |
| startdev = base; | |
| private = startdev->private; | |
| if (private->count >= DASD_ECKD_CHANQ_MAX_SIZE) | |
| return ERR_PTR(-EBUSY); | |
| spin_lock_irqsave(get_ccwdev_lock(startdev->cdev), flags); | |
| private->count++; | |
| if ((base->features & DASD_FEATURE_USERAW)) | |
| cqr = dasd_eckd_build_cp_raw(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); | |
| return cqr; | |
| } | |
| static int dasd_eckd_free_alias_cp(struct dasd_ccw_req *cqr, | |
| struct request *req) | |
| { | |
| struct dasd_eckd_private *private; | |
| unsigned long flags; | |
| spin_lock_irqsave(get_ccwdev_lock(cqr->memdev->cdev), flags); | |
| private = cqr->memdev->private; | |
| private->count--; | |
| spin_unlock_irqrestore(get_ccwdev_lock(cqr->memdev->cdev), flags); | |
| if (cqr->format) | |
| clear_format_track(cqr->format, cqr->block); | |
| return dasd_eckd_free_cp(cqr, req); | |
| } | |
| static int | |
| dasd_eckd_fill_info(struct dasd_device * device, | |
| struct dasd_information2_t * info) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| info->label_block = 2; | |
| info->FBA_layout = private->uses_cdl ? 0 : 1; | |
| info->format = private->uses_cdl ? DASD_FORMAT_CDL : DASD_FORMAT_LDL; | |
| info->characteristics_size = sizeof(private->rdc_data); | |
| memcpy(info->characteristics, &private->rdc_data, | |
| sizeof(private->rdc_data)); | |
| info->confdata_size = min_t(unsigned long, private->conf.len, | |
| sizeof(info->configuration_data)); | |
| memcpy(info->configuration_data, private->conf.data, | |
| info->confdata_size); | |
| return 0; | |
| } | |
| /* | |
| * SECTION: ioctl functions for eckd devices. | |
| */ | |
| /* | |
| * Release device ioctl. | |
| * Buils a channel programm to releases a prior reserved | |
| * (see dasd_eckd_reserve) device. | |
| */ | |
| static int | |
| dasd_eckd_release(struct dasd_device *device) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| struct dasd_ccw_req *cqr; | |
| int rc; | |
| struct ccw1 *ccw; | |
| int useglobal; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EACCES; | |
| useglobal = 0; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 32, device, NULL); | |
| 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_RELEASE; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->count = 32; | |
| ccw->cda = virt_to_dma32(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); | |
| cqr->retries = 2; /* set retry counter to enable basic ERP */ | |
| cqr->expires = 2 * HZ; | |
| 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); | |
| if (useglobal) | |
| mutex_unlock(&dasd_reserve_mutex); | |
| else | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| /* | |
| * Reserve device ioctl. | |
| * Options are set to 'synchronous wait for interrupt' and | |
| * 'timeout the request'. This leads to a terminate IO if | |
| * the interrupt is outstanding for a certain time. | |
| */ | |
| static int | |
| dasd_eckd_reserve(struct dasd_device *device) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| struct dasd_ccw_req *cqr; | |
| int rc; | |
| struct ccw1 *ccw; | |
| int useglobal; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EACCES; | |
| useglobal = 0; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 32, device, NULL); | |
| 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_RESERVE; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->count = 32; | |
| ccw->cda = virt_to_dma32(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); | |
| cqr->retries = 2; /* set retry counter to enable basic ERP */ | |
| cqr->expires = 2 * HZ; | |
| 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); | |
| if (useglobal) | |
| mutex_unlock(&dasd_reserve_mutex); | |
| else | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| /* | |
| * Steal lock ioctl - unconditional reserve device. | |
| * Buils a channel programm to break a device's reservation. | |
| * (unconditional reserve) | |
| */ | |
| static int | |
| dasd_eckd_steal_lock(struct dasd_device *device) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| struct dasd_ccw_req *cqr; | |
| int rc; | |
| struct ccw1 *ccw; | |
| int useglobal; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EACCES; | |
| useglobal = 0; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 32, device, NULL); | |
| 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_SLCK; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->count = 32; | |
| ccw->cda = virt_to_dma32(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); | |
| cqr->retries = 2; /* set retry counter to enable basic ERP */ | |
| cqr->expires = 2 * HZ; | |
| 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); | |
| 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) | |
| __context_unsafe(/* Conditional locking */) | |
| { | |
| 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, | |
| NULL); | |
| 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 = virt_to_dma32(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; | |
| } | |
| /* | |
| * Read performance statistics | |
| */ | |
| static int | |
| dasd_eckd_performance(struct dasd_device *device, void __user *argp) | |
| { | |
| struct dasd_psf_prssd_data *prssdp; | |
| struct dasd_rssd_perf_stats_t *stats; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| int rc; | |
| 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, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "Could not allocate initialization request"); | |
| return PTR_ERR(cqr); | |
| } | |
| 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 */ | |
| prssdp = (struct dasd_psf_prssd_data *) cqr->data; | |
| memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data)); | |
| prssdp->order = PSF_ORDER_PRSSD; | |
| prssdp->suborder = 0x01; /* Performance Statistics */ | |
| prssdp->varies[1] = 0x01; /* Perf Statistics for the Subsystem */ | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->count = sizeof(struct dasd_psf_prssd_data); | |
| ccw->flags |= CCW_FLAG_CC; | |
| ccw->cda = virt_to_dma32(prssdp); | |
| /* Read Subsystem Data - Performance Statistics */ | |
| stats = (struct dasd_rssd_perf_stats_t *) (prssdp + 1); | |
| memset(stats, 0, sizeof(struct dasd_rssd_perf_stats_t)); | |
| ccw++; | |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; | |
| ccw->count = sizeof(struct dasd_rssd_perf_stats_t); | |
| ccw->cda = virt_to_dma32(stats); | |
| 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; | |
| stats = (struct dasd_rssd_perf_stats_t *) (prssdp + 1); | |
| if (copy_to_user(argp, stats, | |
| sizeof(struct dasd_rssd_perf_stats_t))) | |
| rc = -EFAULT; | |
| } | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| /* | |
| * Get attributes (cache operations) | |
| * Returnes the cache attributes used in Define Extend (DE). | |
| */ | |
| static int | |
| dasd_eckd_get_attrib(struct dasd_device *device, void __user *argp) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct attrib_data_t attrib = private->attrib; | |
| int rc; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EACCES; | |
| if (!argp) | |
| return -EINVAL; | |
| rc = 0; | |
| if (copy_to_user(argp, (long *) &attrib, | |
| sizeof(struct attrib_data_t))) | |
| rc = -EFAULT; | |
| return rc; | |
| } | |
| /* | |
| * Set attributes (cache operations) | |
| * Stores the attributes for cache operation to be used in Define Extend (DE). | |
| */ | |
| static int | |
| dasd_eckd_set_attrib(struct dasd_device *device, void __user *argp) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct attrib_data_t attrib; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EACCES; | |
| if (!argp) | |
| return -EINVAL; | |
| if (copy_from_user(&attrib, argp, sizeof(struct attrib_data_t))) | |
| return -EFAULT; | |
| private->attrib = attrib; | |
| dev_info(&device->cdev->dev, | |
| "The DASD cache mode was set to %x (%i cylinder prestage)\n", | |
| private->attrib.operation, private->attrib.nr_cyl); | |
| return 0; | |
| } | |
| /* | |
| * Issue syscall I/O to EMC Symmetrix array. | |
| * CCWs are PSF and RSSD | |
| */ | |
| static int dasd_symm_io(struct dasd_device *device, void __user *argp) | |
| { | |
| struct dasd_symmio_parms usrparm; | |
| 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; | |
| /* at least 2 bytes are accessed and should be allocated */ | |
| if (usrparm.psf_data_len < 2) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, | |
| "Symmetrix ioctl invalid data length %d", | |
| usrparm.psf_data_len); | |
| rc = -EINVAL; | |
| goto out; | |
| } | |
| /* 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); | |
| if (!psf_data || !rssd_result) { | |
| rc = -ENOMEM; | |
| goto out_free; | |
| } | |
| /* get syscall header from user space */ | |
| rc = -EFAULT; | |
| if (copy_from_user(psf_data, | |
| (void __user *)(unsigned long) usrparm.psf_data, | |
| usrparm.psf_data_len)) | |
| goto out_free; | |
| psf0 = psf_data[0]; | |
| psf1 = psf_data[1]; | |
| /* setup CCWs for PSF + RSSD */ | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 2, 0, device, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "Could not allocate initialization request"); | |
| rc = PTR_ERR(cqr); | |
| goto out_free; | |
| } | |
| cqr->startdev = device; | |
| cqr->memdev = device; | |
| cqr->retries = 3; | |
| cqr->expires = 10 * HZ; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| /* Build the ccws */ | |
| ccw = cqr->cpaddr; | |
| /* PSF ccw */ | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->count = usrparm.psf_data_len; | |
| ccw->flags |= CCW_FLAG_CC; | |
| ccw->cda = virt_to_dma32(psf_data); | |
| ccw++; | |
| /* RSSD ccw */ | |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; | |
| ccw->count = usrparm.rssd_result_len; | |
| ccw->flags = CCW_FLAG_SLI ; | |
| ccw->cda = virt_to_dma32(rssd_result); | |
| rc = dasd_sleep_on(cqr); | |
| if (rc) | |
| goto out_sfree; | |
| rc = -EFAULT; | |
| if (copy_to_user((void __user *)(unsigned long) usrparm.rssd_result, | |
| rssd_result, usrparm.rssd_result_len)) | |
| goto out_sfree; | |
| rc = 0; | |
| out_sfree: | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| out_free: | |
| kfree(rssd_result); | |
| kfree(psf_data); | |
| out: | |
| DBF_DEV_EVENT(DBF_WARNING, device, | |
| "Symmetrix ioctl (0x%02x 0x%02x): rc=%d", | |
| (int) psf0, (int) psf1, rc); | |
| return rc; | |
| } | |
| static int | |
| dasd_eckd_ioctl(struct dasd_block *block, unsigned int cmd, void __user *argp) | |
| { | |
| struct dasd_device *device = block->base; | |
| switch (cmd) { | |
| case BIODASDGATTR: | |
| return dasd_eckd_get_attrib(device, argp); | |
| case BIODASDSATTR: | |
| return dasd_eckd_set_attrib(device, argp); | |
| case BIODASDPSRD: | |
| return dasd_eckd_performance(device, argp); | |
| case BIODASDRLSE: | |
| return dasd_eckd_release(device); | |
| case BIODASDRSRV: | |
| 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 -ENOTTY; | |
| } | |
| } | |
| /* | |
| * Dump the range of CCWs into 'page' buffer | |
| * and return number of printed chars. | |
| */ | |
| static void | |
| dasd_eckd_dump_ccw_range(struct dasd_device *device, struct ccw1 *from, | |
| struct ccw1 *to, char *page) | |
| { | |
| int len, count; | |
| char *datap; | |
| len = 0; | |
| while (from <= to) { | |
| len += sprintf(page + len, "CCW %px: %08X %08X DAT:", | |
| from, ((int *) from)[0], ((int *) from)[1]); | |
| /* get pointer to data (consider IDALs) */ | |
| if (from->flags & CCW_FLAG_IDA) | |
| datap = dma64_to_virt(*((dma64_t *)dma32_to_virt(from->cda))); | |
| else | |
| datap = dma32_to_virt(from->cda); | |
| /* dump data (max 128 bytes) */ | |
| for (count = 0; count < from->count && count < 128; count++) { | |
| if (count % 32 == 0) | |
| len += sprintf(page + len, "\n"); | |
| if (count % 8 == 0) | |
| len += sprintf(page + len, " "); | |
| if (count % 4 == 0) | |
| len += sprintf(page + len, " "); | |
| len += sprintf(page + len, "%02x", datap[count]); | |
| } | |
| len += sprintf(page + len, "\n"); | |
| from++; | |
| } | |
| if (len > 0) | |
| dev_err(&device->cdev->dev, "%s", page); | |
| } | |
| static void | |
| dasd_eckd_dump_sense_dbf(struct dasd_device *device, struct irb *irb, | |
| char *reason) | |
| { | |
| u64 *sense; | |
| u64 *stat; | |
| sense = (u64 *) dasd_get_sense(irb); | |
| stat = (u64 *) &irb->scsw; | |
| if (sense) { | |
| 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: %016llx %08x : %s", | |
| reason, *stat, *((u32 *) (stat + 1)), | |
| "NO VALID SENSE"); | |
| } | |
| } | |
| /* | |
| * Print sense data and related channel program. | |
| * Parts are printed because printk buffer is only 1024 bytes. | |
| */ | |
| static void dasd_eckd_dump_sense_ccw(struct dasd_device *device, | |
| struct dasd_ccw_req *req, struct irb *irb) | |
| { | |
| struct ccw1 *first, *last, *fail, *from, *to; | |
| struct device *dev; | |
| int len, sl, sct; | |
| char *page; | |
| dev = &device->cdev->dev; | |
| page = kzalloc(PAGE_SIZE, GFP_ATOMIC); | |
| if (page == NULL) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "No memory to dump sense data\n"); | |
| return; | |
| } | |
| /* dump the sense data */ | |
| len = sprintf(page, "I/O status report:\n"); | |
| len += sprintf(page + len, | |
| "in req: %px 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, "Failing CCW: %px\n", | |
| dma32_to_virt(irb->scsw.cmd.cpa)); | |
| if (irb->esw.esw0.erw.cons) { | |
| for (sl = 0; sl < 4; sl++) { | |
| len += sprintf(page + len, "Sense(hex) %2d-%2d:", | |
| (8 * sl), ((8 * sl) + 7)); | |
| for (sct = 0; sct < 8; sct++) { | |
| len += sprintf(page + len, " %02x", | |
| irb->ecw[8 * sl + sct]); | |
| } | |
| len += sprintf(page + len, "\n"); | |
| } | |
| if (irb->ecw[27] & DASD_SENSE_BIT_0) { | |
| /* 24 Byte Sense Data */ | |
| sprintf(page + len, | |
| "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, | |
| "32 Byte: Format: %x Exception class %x\n", | |
| irb->ecw[6] & 0x0f, irb->ecw[22] >> 4); | |
| } | |
| } else { | |
| sprintf(page + len, "SORRY - NO VALID SENSE AVAILABLE\n"); | |
| } | |
| dev_err(dev, "%s", page); | |
| if (req) { | |
| /* req == NULL for unsolicited interrupts */ | |
| /* dump the Channel Program (max 140 Bytes per line) */ | |
| /* Count CCW and print first CCWs (maximum 7) */ | |
| first = req->cpaddr; | |
| for (last = first; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++); | |
| to = min(first + 6, last); | |
| dev_err(dev, "Related CP in req: %px\n", req); | |
| dasd_eckd_dump_ccw_range(device, first, to, page); | |
| /* print failing CCW area (maximum 4) */ | |
| /* scsw->cda is either valid or zero */ | |
| from = ++to; | |
| fail = dma32_to_virt(irb->scsw.cmd.cpa); /* failing CCW */ | |
| if (from < fail - 2) { | |
| from = fail - 2; /* there is a gap - print header */ | |
| dev_err(dev, "......\n"); | |
| } | |
| to = min(fail + 1, last); | |
| dasd_eckd_dump_ccw_range(device, from, to, page + len); | |
| /* print last CCWs (maximum 2) */ | |
| len = 0; | |
| from = max(from, ++to); | |
| if (from < last - 1) { | |
| from = last - 1; /* there is a gap - print header */ | |
| dev_err(dev, "......\n"); | |
| } | |
| dasd_eckd_dump_ccw_range(device, from, last, page + len); | |
| } | |
| kfree(page); | |
| } | |
| /* | |
| * Print sense data from a tcw. | |
| */ | |
| static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, | |
| struct dasd_ccw_req *req, struct irb *irb) | |
| { | |
| char *page; | |
| int len, sl, sct, residual; | |
| struct tsb *tsb; | |
| u8 *sense, *rcq; | |
| page = kzalloc(PAGE_SIZE, GFP_ATOMIC); | |
| if (page == NULL) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, " %s", | |
| "No memory to dump sense data"); | |
| return; | |
| } | |
| /* dump the sense data */ | |
| len = sprintf(page, "I/O status report:\n"); | |
| len += sprintf(page + len, | |
| "in req: %px 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.ifob << 7) | irb->scsw.tm.sesq, | |
| req ? req->intrc : 0); | |
| len += sprintf(page + len, "Failing TCW: %px\n", | |
| dma32_to_virt(irb->scsw.tm.tcw)); | |
| tsb = NULL; | |
| sense = NULL; | |
| if (irb->scsw.tm.tcw && (irb->scsw.tm.fcxs & 0x01)) | |
| tsb = tcw_get_tsb(dma32_to_virt(irb->scsw.tm.tcw)); | |
| if (tsb) { | |
| len += sprintf(page + len, "tsb->length %d\n", tsb->length); | |
| len += sprintf(page + len, "tsb->flags %x\n", tsb->flags); | |
| len += sprintf(page + len, "tsb->dcw_offset %d\n", tsb->dcw_offset); | |
| len += sprintf(page + len, "tsb->count %d\n", tsb->count); | |
| residual = tsb->count - 28; | |
| len += sprintf(page + len, "residual %d\n", residual); | |
| switch (tsb->flags & 0x07) { | |
| case 1: /* tsa_iostat */ | |
| len += sprintf(page + len, "tsb->tsa.iostat.dev_time %d\n", | |
| tsb->tsa.iostat.dev_time); | |
| len += sprintf(page + len, "tsb->tsa.iostat.def_time %d\n", | |
| tsb->tsa.iostat.def_time); | |
| len += sprintf(page + len, "tsb->tsa.iostat.queue_time %d\n", | |
| tsb->tsa.iostat.queue_time); | |
| len += sprintf(page + len, "tsb->tsa.iostat.dev_busy_time %d\n", | |
| tsb->tsa.iostat.dev_busy_time); | |
| len += sprintf(page + len, "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, "tsb->tsa.ddpc.rc %d\n", | |
| tsb->tsa.ddpc.rc); | |
| for (sl = 0; sl < 2; sl++) { | |
| len += sprintf(page + len, | |
| "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", | |
| rcq[8 * sl + sct]); | |
| } | |
| len += sprintf(page + len, "\n"); | |
| } | |
| sense = tsb->tsa.ddpc.sense; | |
| break; | |
| case 3: /* tsa_intrg */ | |
| len += sprintf(page + len, | |
| "tsb->tsa.intrg.: not supported yet\n"); | |
| break; | |
| } | |
| if (sense) { | |
| for (sl = 0; sl < 4; sl++) { | |
| len += sprintf(page + len, | |
| "Sense(hex) %2d-%2d:", | |
| (8 * sl), ((8 * sl) + 7)); | |
| for (sct = 0; sct < 8; sct++) { | |
| len += sprintf(page + len, " %02x", | |
| sense[8 * sl + sct]); | |
| } | |
| len += sprintf(page + len, "\n"); | |
| } | |
| if (sense[27] & DASD_SENSE_BIT_0) { | |
| /* 24 Byte Sense Data */ | |
| sprintf(page + len, | |
| "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, | |
| "32 Byte: Format: %x Exception class %x\n", | |
| sense[6] & 0x0f, sense[22] >> 4); | |
| } | |
| } else { | |
| sprintf(page + len, "SORRY - NO VALID SENSE AVAILABLE\n"); | |
| } | |
| } else { | |
| sprintf(page + len, "SORRY - NO TSB DATA AVAILABLE\n"); | |
| } | |
| dev_err(&device->cdev->dev, "%s", page); | |
| kfree(page); | |
| } | |
| static void dasd_eckd_dump_sense(struct dasd_device *device, | |
| struct dasd_ccw_req *req, struct irb *irb) | |
| { | |
| u8 *sense = dasd_get_sense(irb); | |
| /* | |
| * In some cases certain errors might be expected and | |
| * log messages shouldn't be written then. | |
| * Check if the according suppress bit is set. | |
| */ | |
| if (sense && (sense[1] & SNS1_INV_TRACK_FORMAT) && | |
| !(sense[2] & SNS2_ENV_DATA_PRESENT) && | |
| test_bit(DASD_CQR_SUPPRESS_IT, &req->flags)) | |
| return; | |
| if (sense && sense[0] & SNS0_CMD_REJECT && | |
| test_bit(DASD_CQR_SUPPRESS_CR, &req->flags)) | |
| return; | |
| if (sense && sense[1] & SNS1_NO_REC_FOUND && | |
| test_bit(DASD_CQR_SUPPRESS_NRF, &req->flags)) | |
| return; | |
| if (scsw_cstat(&irb->scsw) == 0x40 && | |
| test_bit(DASD_CQR_SUPPRESS_IL, &req->flags)) | |
| return; | |
| 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_reload_device(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| char print_uid[DASD_UID_STRLEN]; | |
| int rc, old_base; | |
| struct dasd_uid uid; | |
| unsigned long flags; | |
| /* | |
| * remove device from alias handling to prevent new requests | |
| * from being scheduled on the wrong alias device | |
| */ | |
| dasd_alias_remove_device(device); | |
| 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; | |
| dasd_eckd_read_fc_security(device); | |
| 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) { | |
| dasd_eckd_get_uid_string(&private->conf, print_uid); | |
| 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 int dasd_eckd_read_message_buffer(struct dasd_device *device, | |
| struct dasd_rssd_messages *messages, | |
| __u8 lpum) | |
| { | |
| struct dasd_rssd_messages *message_buf; | |
| struct dasd_psf_prssd_data *prssdp; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| int rc; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */, | |
| (sizeof(struct dasd_psf_prssd_data) + | |
| sizeof(struct dasd_rssd_messages)), | |
| device, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate read message buffer request"); | |
| return PTR_ERR(cqr); | |
| } | |
| cqr->lpm = lpum; | |
| retry: | |
| cqr->startdev = device; | |
| cqr->memdev = device; | |
| cqr->block = NULL; | |
| cqr->expires = 10 * HZ; | |
| set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags); | |
| /* dasd_sleep_on_immediatly does not do complex error | |
| * recovery so clear erp flag and set retry counter to | |
| * do basic erp */ | |
| clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); | |
| cqr->retries = 256; | |
| /* Prepare for Read Subsystem Data */ | |
| prssdp = (struct dasd_psf_prssd_data *) cqr->data; | |
| memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data)); | |
| prssdp->order = PSF_ORDER_PRSSD; | |
| prssdp->suborder = 0x03; /* Message Buffer */ | |
| /* all other bytes of prssdp must be zero */ | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->count = sizeof(struct dasd_psf_prssd_data); | |
| ccw->flags |= CCW_FLAG_CC; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(prssdp); | |
| /* Read Subsystem Data - message buffer */ | |
| message_buf = (struct dasd_rssd_messages *) (prssdp + 1); | |
| memset(message_buf, 0, sizeof(struct dasd_rssd_messages)); | |
| ccw++; | |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; | |
| ccw->count = sizeof(struct dasd_rssd_messages); | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(message_buf); | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| rc = dasd_sleep_on_immediatly(cqr); | |
| if (rc == 0) { | |
| prssdp = (struct dasd_psf_prssd_data *) cqr->data; | |
| message_buf = (struct dasd_rssd_messages *) | |
| (prssdp + 1); | |
| memcpy(messages, message_buf, | |
| sizeof(struct dasd_rssd_messages)); | |
| } else if (cqr->lpm) { | |
| /* | |
| * on z/VM we might not be able to do I/O on the requested path | |
| * but instead we get the required information on any path | |
| * so retry with open path mask | |
| */ | |
| cqr->lpm = 0; | |
| goto retry; | |
| } else | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, | |
| "Reading messages failed with rc=%d\n" | |
| , rc); | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| static int dasd_eckd_query_host_access(struct dasd_device *device, | |
| struct dasd_psf_query_host_access *data) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct dasd_psf_query_host_access *host_access; | |
| struct dasd_psf_prssd_data *prssdp; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| int rc; | |
| if (!private) | |
| return -ENODEV; | |
| /* not available for HYPER PAV alias devices */ | |
| if (!device->block && private->lcu && private->lcu->pav == HYPER_PAV) | |
| return -EOPNOTSUPP; | |
| /* may not be supported by the storage server */ | |
| if (!(private->features.feature[14] & 0x80)) | |
| return -EOPNOTSUPP; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */, | |
| sizeof(struct dasd_psf_prssd_data) + 1, | |
| device, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate read message buffer request"); | |
| return PTR_ERR(cqr); | |
| } | |
| host_access = kzalloc_obj(*host_access, GFP_KERNEL | GFP_DMA); | |
| if (!host_access) { | |
| dasd_sfree_request(cqr, device); | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate host_access buffer"); | |
| return -ENOMEM; | |
| } | |
| 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 = PSF_SUBORDER_QHA; /* query host access */ | |
| /* LSS and Volume that will be queried */ | |
| prssdp->lss = READ_ONCE(private->ned_lss); | |
| prssdp->volume = READ_ONCE(private->ned_ua); | |
| /* all other bytes of prssdp must be zero */ | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->count = sizeof(struct dasd_psf_prssd_data); | |
| ccw->flags |= CCW_FLAG_CC; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(prssdp); | |
| /* Read Subsystem Data - query host access */ | |
| ccw++; | |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; | |
| ccw->count = sizeof(struct dasd_psf_query_host_access); | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(host_access); | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| /* the command might not be supported, suppress error message */ | |
| __set_bit(DASD_CQR_SUPPRESS_CR, &cqr->flags); | |
| rc = dasd_sleep_on_interruptible(cqr); | |
| if (rc == 0) { | |
| *data = *host_access; | |
| } else { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, | |
| "Reading host access data failed with rc=%d\n", | |
| rc); | |
| rc = -EOPNOTSUPP; | |
| } | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| kfree(host_access); | |
| return rc; | |
| } | |
| /* | |
| * return number of grouped devices | |
| */ | |
| static int dasd_eckd_host_access_count(struct dasd_device *device) | |
| { | |
| struct dasd_psf_query_host_access *access; | |
| struct dasd_ckd_path_group_entry *entry; | |
| struct dasd_ckd_host_information *info; | |
| int count = 0; | |
| int rc, i; | |
| access = kzalloc_obj(*access, GFP_NOIO); | |
| if (!access) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate access buffer"); | |
| return -ENOMEM; | |
| } | |
| rc = dasd_eckd_query_host_access(device, access); | |
| if (rc) { | |
| kfree(access); | |
| return rc; | |
| } | |
| info = (struct dasd_ckd_host_information *) | |
| access->host_access_information; | |
| for (i = 0; i < info->entry_count; i++) { | |
| entry = (struct dasd_ckd_path_group_entry *) | |
| (info->entry + i * info->entry_size); | |
| if (entry->status_flags & DASD_ECKD_PG_GROUPED) | |
| count++; | |
| } | |
| kfree(access); | |
| return count; | |
| } | |
| /* | |
| * write host access information to a sequential file | |
| */ | |
| static int dasd_hosts_print(struct dasd_device *device, struct seq_file *m) | |
| { | |
| struct dasd_psf_query_host_access *access; | |
| struct dasd_ckd_path_group_entry *entry; | |
| struct dasd_ckd_host_information *info; | |
| char sysplex[9] = ""; | |
| int rc, i; | |
| access = kzalloc_obj(*access, GFP_NOIO); | |
| if (!access) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate access buffer"); | |
| return -ENOMEM; | |
| } | |
| rc = dasd_eckd_query_host_access(device, access); | |
| if (rc) { | |
| kfree(access); | |
| return rc; | |
| } | |
| info = (struct dasd_ckd_host_information *) | |
| access->host_access_information; | |
| for (i = 0; i < info->entry_count; i++) { | |
| entry = (struct dasd_ckd_path_group_entry *) | |
| (info->entry + i * info->entry_size); | |
| /* PGID */ | |
| seq_printf(m, "pgid %*phN\n", 11, entry->pgid); | |
| /* FLAGS */ | |
| seq_printf(m, "status_flags %02x\n", entry->status_flags); | |
| /* SYSPLEX NAME */ | |
| memcpy(&sysplex, &entry->sysplex_name, sizeof(sysplex) - 1); | |
| EBCASC(sysplex, sizeof(sysplex)); | |
| seq_printf(m, "sysplex_name %8s\n", sysplex); | |
| /* SUPPORTED CYLINDER */ | |
| seq_printf(m, "supported_cylinder %d\n", entry->cylinder); | |
| /* TIMESTAMP */ | |
| seq_printf(m, "timestamp %lu\n", (unsigned long) | |
| entry->timestamp); | |
| } | |
| kfree(access); | |
| return 0; | |
| } | |
| static struct dasd_device | |
| *copy_relation_find_device(struct dasd_copy_relation *copy, | |
| char *busid) | |
| { | |
| int i; | |
| for (i = 0; i < DASD_CP_ENTRIES; i++) { | |
| if (copy->entry[i].configured && | |
| strncmp(copy->entry[i].busid, busid, DASD_BUS_ID_SIZE) == 0) | |
| return copy->entry[i].device; | |
| } | |
| return NULL; | |
| } | |
| /* | |
| * set the new active/primary device | |
| */ | |
| static void copy_pair_set_active(struct dasd_copy_relation *copy, char *new_busid, | |
| char *old_busid) | |
| { | |
| int i; | |
| for (i = 0; i < DASD_CP_ENTRIES; i++) { | |
| if (copy->entry[i].configured && | |
| strncmp(copy->entry[i].busid, new_busid, | |
| DASD_BUS_ID_SIZE) == 0) { | |
| copy->active = ©->entry[i]; | |
| copy->entry[i].primary = true; | |
| } else if (copy->entry[i].configured && | |
| strncmp(copy->entry[i].busid, old_busid, | |
| DASD_BUS_ID_SIZE) == 0) { | |
| copy->entry[i].primary = false; | |
| } | |
| } | |
| } | |
| /* | |
| * The function will swap the role of a given copy pair. | |
| * During the swap operation the relation of the blockdevice is disconnected | |
| * from the old primary and connected to the new. | |
| * | |
| * IO is paused on the block queue before swap and may be resumed afterwards. | |
| */ | |
| static int dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid, | |
| char *sec_busid) | |
| { | |
| struct dasd_eckd_private *prim_priv, *sec_priv; | |
| struct dasd_device *primary, *secondary; | |
| struct dasd_copy_relation *copy; | |
| struct dasd_block *block; | |
| struct gendisk *gdp; | |
| int rc; | |
| copy = device->copy; | |
| if (!copy) | |
| return DASD_COPYPAIRSWAP_INVALID; | |
| primary = copy->active->device; | |
| if (!primary) | |
| return DASD_COPYPAIRSWAP_INVALID; | |
| /* double check if swap has correct primary */ | |
| if (strncmp(dev_name(&primary->cdev->dev), prim_busid, DASD_BUS_ID_SIZE) != 0) | |
| return DASD_COPYPAIRSWAP_PRIMARY; | |
| secondary = copy_relation_find_device(copy, sec_busid); | |
| if (!secondary) | |
| return DASD_COPYPAIRSWAP_SECONDARY; | |
| prim_priv = primary->private; | |
| sec_priv = secondary->private; | |
| /* | |
| * usually the device should be quiesced for swap | |
| * for paranoia stop device and requeue requests again | |
| */ | |
| dasd_device_set_stop_bits(primary, DASD_STOPPED_PPRC); | |
| dasd_device_set_stop_bits(secondary, DASD_STOPPED_PPRC); | |
| dasd_generic_requeue_all_requests(primary); | |
| /* swap DASD internal device <> block assignment */ | |
| block = primary->block; | |
| primary->block = NULL; | |
| secondary->block = block; | |
| block->base = secondary; | |
| /* set new primary device in COPY relation */ | |
| copy_pair_set_active(copy, sec_busid, prim_busid); | |
| /* swap blocklayer device link */ | |
| gdp = block->gdp; | |
| dasd_add_link_to_gendisk(gdp, secondary); | |
| rc = device_move(disk_to_dev(gdp), &secondary->cdev->dev, DPM_ORDER_NONE); | |
| if (rc) { | |
| dev_err(&primary->cdev->dev, | |
| "copy_pair_swap: moving blockdevice parent %s->%s failed (%d)\n", | |
| dev_name(&primary->cdev->dev), | |
| dev_name(&secondary->cdev->dev), rc); | |
| } | |
| if (primary->stopped & DASD_STOPPED_QUIESCE) { | |
| dasd_device_set_stop_bits(secondary, DASD_STOPPED_QUIESCE); | |
| dasd_device_remove_stop_bits(primary, DASD_STOPPED_QUIESCE); | |
| } | |
| /* | |
| * The secondary device never got through format detection, but since it | |
| * is a copy of the primary device, the format is exactly the same; | |
| * therefore, the detected layout can simply be copied. | |
| */ | |
| sec_priv->uses_cdl = prim_priv->uses_cdl; | |
| /* re-enable device */ | |
| dasd_device_remove_stop_bits(primary, DASD_STOPPED_PPRC); | |
| dasd_device_remove_stop_bits(secondary, DASD_STOPPED_PPRC); | |
| dasd_schedule_device_bh(secondary); | |
| return DASD_COPYPAIRSWAP_SUCCESS; | |
| } | |
| /* | |
| * Perform Subsystem Function - Peer-to-Peer Remote Copy Extended Query | |
| */ | |
| static int dasd_eckd_query_pprc_status(struct dasd_device *device, | |
| struct dasd_pprc_data_sc4 *data) | |
| { | |
| struct dasd_pprc_data_sc4 *pprc_data; | |
| struct dasd_psf_prssd_data *prssdp; | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| int rc; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */, | |
| sizeof(*prssdp) + sizeof(*pprc_data) + 1, | |
| device, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate query PPRC status 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 = PSF_SUBORDER_PPRCEQ; | |
| prssdp->varies[0] = PPRCEQ_SCOPE_4; | |
| pprc_data = (struct dasd_pprc_data_sc4 *)(prssdp + 1); | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->count = sizeof(struct dasd_psf_prssd_data); | |
| ccw->flags |= CCW_FLAG_CC; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(prssdp); | |
| /* Read Subsystem Data - query host access */ | |
| ccw++; | |
| ccw->cmd_code = DASD_ECKD_CCW_RSSD; | |
| ccw->count = sizeof(*pprc_data); | |
| ccw->flags |= CCW_FLAG_SLI; | |
| ccw->cda = virt_to_dma32(pprc_data); | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| rc = dasd_sleep_on_interruptible(cqr); | |
| if (rc == 0) { | |
| *data = *pprc_data; | |
| } else { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, | |
| "PPRC Extended Query failed with rc=%d\n", | |
| rc); | |
| rc = -EOPNOTSUPP; | |
| } | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| /* | |
| * ECKD NOP - no operation | |
| */ | |
| static int dasd_eckd_nop(struct dasd_device *device) | |
| { | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| int rc; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, 1, device, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s", | |
| "Could not allocate NOP request"); | |
| return PTR_ERR(cqr); | |
| } | |
| cqr->startdev = device; | |
| cqr->memdev = device; | |
| cqr->block = NULL; | |
| cqr->retries = 1; | |
| cqr->expires = 10 * HZ; | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_NOP; | |
| ccw->flags |= CCW_FLAG_SLI; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| rc = dasd_sleep_on_interruptible(cqr); | |
| if (rc != 0) { | |
| DBF_EVENT_DEVID(DBF_WARNING, device->cdev, | |
| "NOP failed with rc=%d\n", rc); | |
| rc = -EOPNOTSUPP; | |
| } | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| static int dasd_eckd_device_ping(struct dasd_device *device) | |
| { | |
| return dasd_eckd_nop(device); | |
| } | |
| /* | |
| * Perform Subsystem Function - CUIR response | |
| */ | |
| static int | |
| dasd_eckd_psf_cuir_response(struct dasd_device *device, int response, | |
| __u32 message_id, __u8 lpum) | |
| { | |
| struct dasd_psf_cuir_response *psf_cuir; | |
| int pos = pathmask_to_pos(lpum); | |
| struct dasd_ccw_req *cqr; | |
| struct ccw1 *ccw; | |
| int rc; | |
| cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ , | |
| sizeof(struct dasd_psf_cuir_response), | |
| device, NULL); | |
| if (IS_ERR(cqr)) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "Could not allocate PSF-CUIR request"); | |
| return PTR_ERR(cqr); | |
| } | |
| psf_cuir = (struct dasd_psf_cuir_response *)cqr->data; | |
| psf_cuir->order = PSF_ORDER_CUIR_RESPONSE; | |
| psf_cuir->cc = response; | |
| psf_cuir->chpid = device->path[pos].chpid; | |
| psf_cuir->message_id = message_id; | |
| psf_cuir->cssid = device->path[pos].cssid; | |
| psf_cuir->ssid = device->path[pos].ssid; | |
| ccw = cqr->cpaddr; | |
| ccw->cmd_code = DASD_ECKD_CCW_PSF; | |
| ccw->cda = virt_to_dma32(psf_cuir); | |
| ccw->flags = CCW_FLAG_SLI; | |
| ccw->count = sizeof(struct dasd_psf_cuir_response); | |
| cqr->startdev = device; | |
| cqr->memdev = device; | |
| cqr->block = NULL; | |
| cqr->retries = 256; | |
| cqr->expires = 10*HZ; | |
| cqr->buildclk = get_tod_clock(); | |
| cqr->status = DASD_CQR_FILLED; | |
| set_bit(DASD_CQR_VERIFY_PATH, &cqr->flags); | |
| rc = dasd_sleep_on(cqr); | |
| dasd_sfree_request(cqr, cqr->memdev); | |
| return rc; | |
| } | |
| /* | |
| * return configuration data that is referenced by record selector | |
| * if a record selector is specified or per default return the | |
| * conf_data pointer for the path specified by lpum | |
| */ | |
| static struct dasd_conf_data *dasd_eckd_get_ref_conf(struct dasd_device *device, | |
| __u8 lpum, | |
| struct dasd_cuir_message *cuir) | |
| { | |
| struct dasd_conf_data *conf_data; | |
| int path, pos; | |
| if (cuir->record_selector == 0) | |
| goto out; | |
| for (path = 0x80, pos = 0; path; path >>= 1, pos++) { | |
| conf_data = device->path[pos].conf_data; | |
| if (conf_data->gneq.record_selector == | |
| cuir->record_selector) | |
| return conf_data; | |
| } | |
| out: | |
| return device->path[pathmask_to_pos(lpum)].conf_data; | |
| } | |
| /* | |
| * This function determines the scope of a reconfiguration request by | |
| * analysing the path and device selection data provided in the CUIR request. | |
| * Returns a path mask containing CUIR affected paths for the give device. | |
| * | |
| * If the CUIR request does not contain the required information return the | |
| * path mask of the path the attention message for the CUIR request was reveived | |
| * on. | |
| */ | |
| static int dasd_eckd_cuir_scope(struct dasd_device *device, __u8 lpum, | |
| struct dasd_cuir_message *cuir) | |
| { | |
| struct dasd_conf_data *ref_conf_data; | |
| unsigned long bitmask = 0, mask = 0; | |
| struct dasd_conf_data *conf_data; | |
| unsigned int pos, path; | |
| char *ref_gneq, *gneq; | |
| char *ref_ned, *ned; | |
| int tbcpm = 0; | |
| /* if CUIR request does not specify the scope use the path | |
| the attention message was presented on */ | |
| if (!cuir->ned_map || | |
| !(cuir->neq_map[0] | cuir->neq_map[1] | cuir->neq_map[2])) | |
| return lpum; | |
| /* get reference conf data */ | |
| ref_conf_data = dasd_eckd_get_ref_conf(device, lpum, cuir); | |
| /* reference ned is determined by ned_map field */ | |
| pos = 8 - ffs(cuir->ned_map); | |
| ref_ned = (char *)&ref_conf_data->neds[pos]; | |
| ref_gneq = (char *)&ref_conf_data->gneq; | |
| /* transfer 24 bit neq_map to mask */ | |
| mask = cuir->neq_map[2]; | |
| mask |= cuir->neq_map[1] << 8; | |
| mask |= cuir->neq_map[0] << 16; | |
| for (path = 0; path < 8; path++) { | |
| /* initialise data per path */ | |
| bitmask = mask; | |
| conf_data = device->path[path].conf_data; | |
| pos = 8 - ffs(cuir->ned_map); | |
| ned = (char *) &conf_data->neds[pos]; | |
| /* compare reference ned and per path ned */ | |
| if (memcmp(ref_ned, ned, sizeof(*ned)) != 0) | |
| continue; | |
| gneq = (char *)&conf_data->gneq; | |
| /* compare reference gneq and per_path gneq under | |
| 24 bit mask where mask bit 0 equals byte 7 of | |
| the gneq and mask bit 24 equals byte 31 */ | |
| while (bitmask) { | |
| pos = ffs(bitmask) - 1; | |
| if (memcmp(&ref_gneq[31 - pos], &gneq[31 - pos], 1) | |
| != 0) | |
| break; | |
| clear_bit(pos, &bitmask); | |
| } | |
| if (bitmask) | |
| continue; | |
| /* device and path match the reference values | |
| add path to CUIR scope */ | |
| tbcpm |= 0x80 >> path; | |
| } | |
| return tbcpm; | |
| } | |
| static void dasd_eckd_cuir_notify_user(struct dasd_device *device, | |
| unsigned long paths, int action) | |
| { | |
| int pos; | |
| while (paths) { | |
| /* get position of bit in mask */ | |
| pos = 8 - ffs(paths); | |
| /* get channel path descriptor from this position */ | |
| if (action == CUIR_QUIESCE) | |
| pr_warn("Service on the storage server caused path %x.%02x to go offline", | |
| device->path[pos].cssid, | |
| device->path[pos].chpid); | |
| else if (action == CUIR_RESUME) | |
| pr_info("Path %x.%02x is back online after service on the storage server", | |
| device->path[pos].cssid, | |
| device->path[pos].chpid); | |
| clear_bit(7 - pos, &paths); | |
| } | |
| } | |
| static int dasd_eckd_cuir_remove_path(struct dasd_device *device, __u8 lpum, | |
| struct dasd_cuir_message *cuir) | |
| { | |
| unsigned long tbcpm; | |
| tbcpm = dasd_eckd_cuir_scope(device, lpum, cuir); | |
| /* nothing to do if path is not in use */ | |
| if (!(dasd_path_get_opm(device) & tbcpm)) | |
| return 0; | |
| if (!(dasd_path_get_opm(device) & ~tbcpm)) { | |
| /* no path would be left if the CUIR action is taken | |
| return error */ | |
| return -EINVAL; | |
| } | |
| /* remove device from operational path mask */ | |
| dasd_path_remove_opm(device, tbcpm); | |
| dasd_path_add_cuirpm(device, tbcpm); | |
| return tbcpm; | |
| } | |
| /* | |
| * walk through all devices and build a path mask to quiesce them | |
| * return an error if the last path to a device would be removed | |
| * | |
| * if only part of the devices are quiesced and an error | |
| * occurs no onlining necessary, the storage server will | |
| * notify the already set offline devices again | |
| */ | |
| static int dasd_eckd_cuir_quiesce(struct dasd_device *device, __u8 lpum, | |
| struct dasd_cuir_message *cuir) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct alias_pav_group *pavgroup, *tempgroup; | |
| struct dasd_device *dev, *n; | |
| unsigned long paths = 0; | |
| unsigned long flags; | |
| int tbcpm; | |
| /* active devices */ | |
| list_for_each_entry_safe(dev, n, &private->lcu->active_devices, | |
| alias_list) { | |
| spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags); | |
| tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir); | |
| spin_unlock_irqrestore(get_ccwdev_lock(dev->cdev), flags); | |
| if (tbcpm < 0) | |
| goto out_err; | |
| paths |= tbcpm; | |
| } | |
| /* inactive devices */ | |
| list_for_each_entry_safe(dev, n, &private->lcu->inactive_devices, | |
| alias_list) { | |
| spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags); | |
| tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir); | |
| spin_unlock_irqrestore(get_ccwdev_lock(dev->cdev), flags); | |
| if (tbcpm < 0) | |
| goto out_err; | |
| paths |= tbcpm; | |
| } | |
| /* devices in PAV groups */ | |
| list_for_each_entry_safe(pavgroup, tempgroup, | |
| &private->lcu->grouplist, group) { | |
| list_for_each_entry_safe(dev, n, &pavgroup->baselist, | |
| alias_list) { | |
| spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags); | |
| tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir); | |
| spin_unlock_irqrestore( | |
| get_ccwdev_lock(dev->cdev), flags); | |
| if (tbcpm < 0) | |
| goto out_err; | |
| paths |= tbcpm; | |
| } | |
| list_for_each_entry_safe(dev, n, &pavgroup->aliaslist, | |
| alias_list) { | |
| spin_lock_irqsave(get_ccwdev_lock(dev->cdev), flags); | |
| tbcpm = dasd_eckd_cuir_remove_path(dev, lpum, cuir); | |
| spin_unlock_irqrestore( | |
| get_ccwdev_lock(dev->cdev), flags); | |
| if (tbcpm < 0) | |
| goto out_err; | |
| paths |= tbcpm; | |
| } | |
| } | |
| /* notify user about all paths affected by CUIR action */ | |
| dasd_eckd_cuir_notify_user(device, paths, CUIR_QUIESCE); | |
| return 0; | |
| out_err: | |
| return tbcpm; | |
| } | |
| static int dasd_eckd_cuir_resume(struct dasd_device *device, __u8 lpum, | |
| struct dasd_cuir_message *cuir) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct alias_pav_group *pavgroup, *tempgroup; | |
| struct dasd_device *dev, *n; | |
| unsigned long paths = 0; | |
| int tbcpm; | |
| /* | |
| * the path may have been added through a generic path event before | |
| * only trigger path verification if the path is not already in use | |
| */ | |
| list_for_each_entry_safe(dev, n, | |
| &private->lcu->active_devices, | |
| alias_list) { | |
| tbcpm = dasd_eckd_cuir_scope(dev, lpum, cuir); | |
| paths |= tbcpm; | |
| if (!(dasd_path_get_opm(dev) & tbcpm)) { | |
| dasd_path_add_tbvpm(dev, tbcpm); | |
| dasd_schedule_device_bh(dev); | |
| } | |
| } | |
| list_for_each_entry_safe(dev, n, | |
| &private->lcu->inactive_devices, | |
| alias_list) { | |
| tbcpm = dasd_eckd_cuir_scope(dev, lpum, cuir); | |
| paths |= tbcpm; | |
| if (!(dasd_path_get_opm(dev) & tbcpm)) { | |
| dasd_path_add_tbvpm(dev, tbcpm); | |
| dasd_schedule_device_bh(dev); | |
| } | |
| } | |
| /* devices in PAV groups */ | |
| list_for_each_entry_safe(pavgroup, tempgroup, | |
| &private->lcu->grouplist, | |
| group) { | |
| list_for_each_entry_safe(dev, n, | |
| &pavgroup->baselist, | |
| alias_list) { | |
| tbcpm = dasd_eckd_cuir_scope(dev, lpum, cuir); | |
| paths |= tbcpm; | |
| if (!(dasd_path_get_opm(dev) & tbcpm)) { | |
| dasd_path_add_tbvpm(dev, tbcpm); | |
| dasd_schedule_device_bh(dev); | |
| } | |
| } | |
| list_for_each_entry_safe(dev, n, | |
| &pavgroup->aliaslist, | |
| alias_list) { | |
| tbcpm = dasd_eckd_cuir_scope(dev, lpum, cuir); | |
| paths |= tbcpm; | |
| if (!(dasd_path_get_opm(dev) & tbcpm)) { | |
| dasd_path_add_tbvpm(dev, tbcpm); | |
| dasd_schedule_device_bh(dev); | |
| } | |
| } | |
| } | |
| /* notify user about all paths affected by CUIR action */ | |
| dasd_eckd_cuir_notify_user(device, paths, CUIR_RESUME); | |
| return 0; | |
| } | |
| static void dasd_eckd_handle_cuir(struct dasd_device *device, void *messages, | |
| __u8 lpum) | |
| { | |
| struct dasd_cuir_message *cuir = messages; | |
| int response; | |
| DBF_DEV_EVENT(DBF_WARNING, device, | |
| "CUIR request: %016llx %016llx %016llx %08x", | |
| ((u64 *)cuir)[0], ((u64 *)cuir)[1], ((u64 *)cuir)[2], | |
| ((u32 *)cuir)[3]); | |
| if (cuir->code == CUIR_QUIESCE) { | |
| /* quiesce */ | |
| if (dasd_eckd_cuir_quiesce(device, lpum, cuir)) | |
| response = PSF_CUIR_LAST_PATH; | |
| else | |
| response = PSF_CUIR_COMPLETED; | |
| } else if (cuir->code == CUIR_RESUME) { | |
| /* resume */ | |
| dasd_eckd_cuir_resume(device, lpum, cuir); | |
| response = PSF_CUIR_COMPLETED; | |
| } else | |
| response = PSF_CUIR_NOT_SUPPORTED; | |
| dasd_eckd_psf_cuir_response(device, response, | |
| cuir->message_id, lpum); | |
| DBF_DEV_EVENT(DBF_WARNING, device, | |
| "CUIR response: %d on message ID %08x", response, | |
| cuir->message_id); | |
| /* to make sure there is no attention left schedule work again */ | |
| device->discipline->check_attention(device, lpum); | |
| } | |
| static void dasd_eckd_oos_resume(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| struct alias_pav_group *pavgroup, *tempgroup; | |
| struct dasd_device *dev, *n; | |
| unsigned long flags; | |
| spin_lock_irqsave(&private->lcu->lock, flags); | |
| list_for_each_entry_safe(dev, n, &private->lcu->active_devices, | |
| alias_list) { | |
| if (dev->stopped & DASD_STOPPED_NOSPC) | |
| dasd_generic_space_avail(dev); | |
| } | |
| list_for_each_entry_safe(dev, n, &private->lcu->inactive_devices, | |
| alias_list) { | |
| if (dev->stopped & DASD_STOPPED_NOSPC) | |
| dasd_generic_space_avail(dev); | |
| } | |
| /* devices in PAV groups */ | |
| list_for_each_entry_safe(pavgroup, tempgroup, | |
| &private->lcu->grouplist, | |
| group) { | |
| list_for_each_entry_safe(dev, n, &pavgroup->baselist, | |
| alias_list) { | |
| if (dev->stopped & DASD_STOPPED_NOSPC) | |
| dasd_generic_space_avail(dev); | |
| } | |
| list_for_each_entry_safe(dev, n, &pavgroup->aliaslist, | |
| alias_list) { | |
| if (dev->stopped & DASD_STOPPED_NOSPC) | |
| dasd_generic_space_avail(dev); | |
| } | |
| } | |
| spin_unlock_irqrestore(&private->lcu->lock, flags); | |
| } | |
| static void dasd_eckd_handle_oos(struct dasd_device *device, void *messages, | |
| __u8 lpum) | |
| { | |
| struct dasd_oos_message *oos = messages; | |
| switch (oos->code) { | |
| case REPO_WARN: | |
| case POOL_WARN: | |
| dev_warn(&device->cdev->dev, | |
| "Extent pool usage has reached a critical value\n"); | |
| dasd_eckd_oos_resume(device); | |
| break; | |
| case REPO_EXHAUST: | |
| case POOL_EXHAUST: | |
| dev_warn(&device->cdev->dev, | |
| "Extent pool is exhausted\n"); | |
| break; | |
| case REPO_RELIEVE: | |
| case POOL_RELIEVE: | |
| dev_info(&device->cdev->dev, | |
| "Extent pool physical space constraint has been relieved\n"); | |
| break; | |
| } | |
| /* In any case, update related data */ | |
| dasd_eckd_read_ext_pool_info(device); | |
| /* to make sure there is no attention left schedule work again */ | |
| device->discipline->check_attention(device, lpum); | |
| } | |
| static void dasd_eckd_check_attention_work(struct work_struct *work) | |
| { | |
| struct check_attention_work_data *data; | |
| struct dasd_rssd_messages *messages; | |
| struct dasd_device *device; | |
| int rc; | |
| data = container_of(work, struct check_attention_work_data, worker); | |
| device = data->device; | |
| messages = kzalloc_obj(*messages); | |
| if (!messages) { | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "Could not allocate attention message buffer"); | |
| goto out; | |
| } | |
| rc = dasd_eckd_read_message_buffer(device, messages, data->lpum); | |
| if (rc) | |
| goto out; | |
| if (messages->length == ATTENTION_LENGTH_CUIR && | |
| messages->format == ATTENTION_FORMAT_CUIR) | |
| dasd_eckd_handle_cuir(device, messages, data->lpum); | |
| if (messages->length == ATTENTION_LENGTH_OOS && | |
| messages->format == ATTENTION_FORMAT_OOS) | |
| dasd_eckd_handle_oos(device, messages, data->lpum); | |
| out: | |
| dasd_put_device(device); | |
| kfree(messages); | |
| kfree(data); | |
| } | |
| static int dasd_eckd_check_attention(struct dasd_device *device, __u8 lpum) | |
| { | |
| struct check_attention_work_data *data; | |
| data = kzalloc_obj(*data, GFP_ATOMIC); | |
| if (!data) | |
| return -ENOMEM; | |
| INIT_WORK(&data->worker, dasd_eckd_check_attention_work); | |
| dasd_get_device(device); | |
| data->device = device; | |
| data->lpum = lpum; | |
| schedule_work(&data->worker); | |
| return 0; | |
| } | |
| static int dasd_eckd_disable_hpf_path(struct dasd_device *device, __u8 lpum) | |
| { | |
| if (~lpum & dasd_path_get_opm(device)) { | |
| dasd_path_add_nohpfpm(device, lpum); | |
| dasd_path_remove_opm(device, lpum); | |
| dev_err(&device->cdev->dev, | |
| "Channel path %02X lost HPF functionality and is disabled\n", | |
| lpum); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static void dasd_eckd_disable_hpf_device(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| dev_err(&device->cdev->dev, | |
| "High Performance FICON disabled\n"); | |
| private->fcx_max_data = 0; | |
| } | |
| static int dasd_eckd_hpf_enabled(struct dasd_device *device) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| if (!private) | |
| return 0; | |
| return private->fcx_max_data ? 1 : 0; | |
| } | |
| static void dasd_eckd_handle_hpf_error(struct dasd_device *device, | |
| struct irb *irb) | |
| { | |
| struct dasd_eckd_private *private = device->private; | |
| if (!private->fcx_max_data) { | |
| /* sanity check for no HPF, the error makes no sense */ | |
| DBF_DEV_EVENT(DBF_WARNING, device, "%s", | |
| "Trying to disable HPF for a non HPF device"); | |
| return; | |
| } | |
| if (irb->scsw.tm.sesq == SCSW_SESQ_DEV_NOFCX) { | |
| dasd_eckd_disable_hpf_device(device); | |
| } else if (irb->scsw.tm.sesq == SCSW_SESQ_PATH_NOFCX) { | |
| if (dasd_eckd_disable_hpf_path(device, irb->esw.esw1.lpum)) | |
| return; | |
| dasd_eckd_disable_hpf_device(device); | |
| dasd_path_set_tbvpm(device, | |
| dasd_path_get_hpfpm(device)); | |
| } | |
| /* | |
| * prevent that any new I/O ist started on the device and schedule a | |
| * requeue of existing requests | |
| */ | |
| dasd_device_set_stop_bits(device, DASD_STOPPED_NOT_ACC); | |
| dasd_schedule_requeue(device); | |
| } | |
| static unsigned int dasd_eckd_max_sectors(struct dasd_block *block) | |
| { | |
| if (block->base->features & DASD_FEATURE_USERAW) { | |
| /* | |
| * the max_blocks value for raw_track access is 256 | |
| * it is higher than the native ECKD value because we | |
| * only need one ccw per track | |
| * so the max_hw_sectors are | |
| * 2048 x 512B = 1024kB = 16 tracks | |
| */ | |
| return DASD_ECKD_MAX_BLOCKS_RAW << block->s2b_shift; | |
| } | |
| return DASD_ECKD_MAX_BLOCKS << block->s2b_shift; | |
| } | |
| /* | |
| * Discard on ECKD releases space through RAS, which works on whole extents. | |
| * Advertise extent granularity so the block layer only sends extent-aligned | |
| * discards (avoiding partially specified extents), and only for volumes on ESE | |
| * hardware. Non-ESE devices are left without discard limits. | |
| */ | |
| static void dasd_eckd_disc_limits(struct dasd_block *block, | |
| struct queue_limits *lim) | |
| { | |
| struct dasd_device *device = block->base; | |
| struct dasd_eckd_private *private = device->private; | |
| unsigned int logical_block_size = block->bp_block; | |
| unsigned int max_discard_sectors, max_bytes, ext_bytes; | |
| int recs_per_trk, trks_per_cyl, ext_limit, ext_size; | |
| if (!dasd_eckd_ese_capable(device) || dasd_eckd_ext_size(device) == 0) | |
| return; | |
| trks_per_cyl = private->rdc_data.trk_per_cyl; | |
| recs_per_trk = recs_per_track(&private->rdc_data, 0, logical_block_size); | |
| ext_size = dasd_eckd_ext_size(device); | |
| ext_limit = min(private->real_cyl / ext_size, DASD_ECKD_RAS_EXTS_MAX); | |
| ext_bytes = ext_size * trks_per_cyl * recs_per_trk * logical_block_size; | |
| if (!ext_bytes) /* malformed RDC data - leave discard unset */ | |
| return; | |
| max_bytes = UINT_MAX - (UINT_MAX % ext_bytes); | |
| if (max_bytes / ext_bytes > ext_limit) | |
| max_bytes = ext_bytes * ext_limit; | |
| max_discard_sectors = max_bytes / 512; | |
| lim->max_hw_discard_sectors = max_discard_sectors; | |
| /* | |
| * ext_bytes is the hardware extent size and is not a power of two, so | |
| * the block layer's power-of-two round_up()/round_down() alignment | |
| * helpers compute it only approximately. That is a hint, not a | |
| * correctness requirement: RAS safety is enforced in the CCW builder, | |
| * which rounds the range inward to whole extents and rejects a request | |
| * that covers no whole extent, so a misaligned range is never | |
| * over-released. At worst a few sub-extent discards are declined. | |
| */ | |
| lim->discard_granularity = ext_bytes; | |
| } | |
| static struct ccw_driver dasd_eckd_driver = { | |
| .driver = { | |
| .name = "dasd-eckd", | |
| .owner = THIS_MODULE, | |
| .dev_groups = dasd_dev_groups, | |
| }, | |
| .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, | |
| .uc_handler = dasd_generic_uc_handler, | |
| .int_class = IRQIO_DAS, | |
| }; | |
| static struct dasd_discipline dasd_eckd_discipline = { | |
| .owner = THIS_MODULE, | |
| .name = "ECKD", | |
| .ebcname = "ECKD", | |
| .disc_limits = dasd_eckd_disc_limits, | |
| .check_device = dasd_eckd_check_characteristics, | |
| .uncheck_device = dasd_eckd_uncheck_device, | |
| .do_analysis = dasd_eckd_do_analysis, | |
| .pe_handler = dasd_eckd_pe_handler, | |
| .basic_to_ready = dasd_eckd_basic_to_ready, | |
| .online_to_ready = dasd_eckd_online_to_ready, | |
| .basic_to_known = dasd_eckd_basic_to_known, | |
| .max_sectors = dasd_eckd_max_sectors, | |
| .fill_geometry = dasd_eckd_fill_geometry, | |
| .start_IO = dasd_start_IO, | |
| .term_IO = dasd_term_IO, | |
| .handle_terminated_request = dasd_eckd_handle_terminated_request, | |
| .format_device = dasd_eckd_format_device, | |
| .check_device_format = dasd_eckd_check_device_format, | |
| .erp_action = dasd_eckd_erp_action, | |
| .erp_postaction = dasd_eckd_erp_postaction, | |
| .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, | |
| .reload = dasd_eckd_reload_device, | |
| .get_uid = dasd_eckd_get_uid, | |
| .kick_validate = dasd_eckd_kick_validate_server, | |
| .check_attention = dasd_eckd_check_attention, | |
| .host_access_count = dasd_eckd_host_access_count, | |
| .hosts_print = dasd_hosts_print, | |
| .handle_hpf_error = dasd_eckd_handle_hpf_error, | |
| .disable_hpf = dasd_eckd_disable_hpf_device, | |
| .hpf_enabled = dasd_eckd_hpf_enabled, | |
| .reset_path = dasd_eckd_reset_path, | |
| .is_ese = dasd_eckd_is_ese, | |
| .ese_capable = dasd_eckd_ese_capable, | |
| .on_demand_format = dasd_eckd_on_demand_format, | |
| .space_allocated = dasd_eckd_space_allocated, | |
| .space_configured = dasd_eckd_space_configured, | |
| .logical_capacity = dasd_eckd_logical_capacity, | |
| .release_space = dasd_eckd_release_space, | |
| .ext_pool_id = dasd_eckd_ext_pool_id, | |
| .ext_size = dasd_eckd_ext_size, | |
| .ext_pool_cap_at_warnlevel = dasd_eckd_ext_pool_cap_at_warnlevel, | |
| .ext_pool_warn_thrshld = dasd_eckd_ext_pool_warn_thrshld, | |
| .ext_pool_oos = dasd_eckd_ext_pool_oos, | |
| .ext_pool_exhaust = dasd_eckd_ext_pool_exhaust, | |
| .ese_format = dasd_eckd_ese_format, | |
| .ese_read = dasd_eckd_ese_read, | |
| .pprc_status = dasd_eckd_query_pprc_status, | |
| .pprc_enabled = dasd_eckd_pprc_enabled, | |
| .copy_pair_swap = dasd_eckd_copy_pair_swap, | |
| .device_ping = dasd_eckd_device_ping, | |
| }; | |
| static int __init | |
| dasd_eckd_init(void) | |
| { | |
| int ret; | |
| ASCEBC(dasd_eckd_discipline.ebcname, 4); | |
| dasd_reserve_req = kmalloc_obj(*dasd_reserve_req, GFP_KERNEL | GFP_DMA); | |
| if (!dasd_reserve_req) | |
| return -ENOMEM; | |
| dasd_vol_info_req = kmalloc_obj(*dasd_vol_info_req, | |
| GFP_KERNEL | GFP_DMA); | |
| if (!dasd_vol_info_req) { | |
| kfree(dasd_reserve_req); | |
| return -ENOMEM; | |
| } | |
| pe_handler_worker = kmalloc_obj(*pe_handler_worker, | |
| GFP_KERNEL | GFP_DMA); | |
| if (!pe_handler_worker) { | |
| kfree(dasd_reserve_req); | |
| kfree(dasd_vol_info_req); | |
| return -ENOMEM; | |
| } | |
| rawpadpage = (void *)__get_free_page(GFP_KERNEL); | |
| if (!rawpadpage) { | |
| kfree(pe_handler_worker); | |
| kfree(dasd_reserve_req); | |
| kfree(dasd_vol_info_req); | |
| return -ENOMEM; | |
| } | |
| ret = ccw_driver_register(&dasd_eckd_driver); | |
| if (!ret) | |
| wait_for_device_probe(); | |
| else { | |
| kfree(pe_handler_worker); | |
| kfree(dasd_reserve_req); | |
| kfree(dasd_vol_info_req); | |
| free_page((unsigned long)rawpadpage); | |
| } | |
| return ret; | |
| } | |
| static void __exit | |
| dasd_eckd_cleanup(void) | |
| { | |
| ccw_driver_unregister(&dasd_eckd_driver); | |
| kfree(pe_handler_worker); | |
| kfree(dasd_reserve_req); | |
| free_page((unsigned long)rawpadpage); | |
| } | |
| module_init(dasd_eckd_init); | |
| module_exit(dasd_eckd_cleanup); |