Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/s390/block/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 188 kB image not shown  

Quelle  dasd_eckd.c   Sprache: C

 
// 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/compat.h>
#include <linux/init.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/io.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");

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 intunsigned intint,
   struct dasd_device *, struct dasd_device *,
   unsigned intintunsigned intunsigned int,
   unsigned intunsigned int);
static int dasd_eckd_query_pprc_status(struct dasd_device *,
           struct dasd_pprc_data_sc4 *);

/* initial attempt at a probe function. this can be simplified once
 * the other detection code is gone */

static int
dasd_eckd_probe (struct ccw_device *cdev)
{
 int ret;

 /* set ECKD specific ccw-device options */
 ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE |
         CCWDEV_DO_PATHGROUP | CCWDEV_DO_MULTIPATH);
 if (ret) {
  DBF_EVENT_DEVID(DBF_WARNING, cdev, "%s",
    "dasd_eckd_probe: could not set "
    "ccw-device options");
  return ret;
 }
 ret = dasd_generic_probe(cdev);
 return ret;
}

static int
dasd_eckd_set_online(struct ccw_device *cdev)
{
 return dasd_generic_set_online(cdev, &dasd_eckd_discipline);
}

static const int sizes_trk0[] = { 28, 148, 84 };
#define LABEL_SIZE 140

/* head and record addresses of count_area read in analysis ccw */
static const int count_area_head[] = { 0, 0, 0, 0, 1 };
static const int count_area_rec[] = { 1, 2, 3, 4, 1 };

static inline unsigned int
ceil_quot(unsigned int d1, unsigned int d2)
{
 return (d1 + (d2 - 1)) / d2;
}

static unsigned int
recs_per_track(struct dasd_eckd_characteristics * rdc,
        unsigned int kl, unsigned int dl)
{
 int dn, kn;

 switch (rdc->dev_type) {
 case 0x3380:
  if (kl)
   return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) +
           ceil_quot(dl + 12, 32));
  else
   return 1499 / (15 + ceil_quot(dl + 12, 32));
 case 0x3390:
  dn = ceil_quot(dl + 6, 232) + 1;
  if (kl) {
   kn = ceil_quot(kl + 6, 232) + 1;
   return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) +
           9 + ceil_quot(dl + 6 * dn, 34));
  } else
   return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34));
 case 0x9345:
  dn = ceil_quot(dl + 6, 232) + 1;
  if (kl) {
   kn = ceil_quot(kl + 6, 232) + 1;
   return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) +
           ceil_quot(dl + 6 * dn, 34));
  } else
   return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34));
 }
 return 0;
}

static void set_ch_t(struct ch_t *geo, __u32 cyl, __u8 head)
{
 geo->cyl = (__u16) cyl;
 geo->head = cyl >> 16;
 geo->head <<= 4;
 geo->head |= head;
}

/*
 * calculate failing track from sense data depending if
 * it is an EAV device or not
 */

static int dasd_eckd_track_from_irb(struct irb *irb, struct dasd_device *device,
        sector_t *track)
{
 struct dasd_eckd_private *private = device->private;
 u8 *sense = NULL;
 u32 cyl;
 u8 head;

 sense = dasd_get_sense(irb);
 if (!sense) {
  DBF_DEV_EVENT(DBF_WARNING, device, "%s",
         "ESE error no sense data\n");
  return -EINVAL;
 }
 if (!(sense[27] & DASD_SENSE_BIT_2)) {
  DBF_DEV_EVENT(DBF_WARNING, device, "%s",
         "ESE error no valid track data\n");
  return -EINVAL;
 }

 if (sense[27] & DASD_SENSE_BIT_3) {
  /* enhanced addressing */
  cyl = sense[30] << 20;
  cyl |= (sense[31] & 0xF0) << 12;
  cyl |= sense[28] << 8;
  cyl |= sense[29];
 } else {
  cyl = sense[29] << 8;
  cyl |= sense[30];
 }
 head = sense[31] & 0x0F;
 *track = cyl * private->rdc_data.trk_per_cyl + head;
 return 0;
}

static int set_timestamp(struct ccw1 *ccw, struct DE_eckd_data *data,
       struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;
 int rc;

 rc = get_phys_clock(&data->ep_sys_time);
 /*
 * Ignore return code if XRC is not supported or
 * sync clock is switched off
 */

 if ((rc && !private->rdc_data.facilities.XRC_supported) ||
     rc == -EOPNOTSUPP || rc == -EACCES)
  return 0;

 /* switch on System Time Stamp - needed for XRC Support */
 data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid'   */
 data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */

 if (ccw) {
  ccw->count = sizeof(struct DE_eckd_data);
  ccw->flags |= CCW_FLAG_SLI;
 }

 return rc;
}

static int
define_extent(struct ccw1 *ccw, struct DE_eckd_data *data, unsigned int trk,
       unsigned int totrk, int cmd, struct dasd_device *device,
       int blksize)
{
 struct dasd_eckd_private *private = device->private;
 u16 heads, beghead, endhead;
 u32 begcyl, endcyl;
 int rc = 0;

 if (ccw) {
  ccw->cmd_code = DASD_ECKD_CCW_DEFINE_EXTENT;
  ccw->flags = 0;
  ccw->count = 16;
  ccw->cda = 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;
 pfxdata->base_address = basepriv->conf.ned->unit_addr;
 pfxdata->base_lss = basepriv->conf.ned->ID;
 pfxdata->validity.define_extent = 1;

 /* private uid is kept up to date, conf_data may be outdated */
 if (startpriv->uid.type == UA_BASE_PAV_ALIAS)
  pfxdata->validity.verify_base = 1;

 if (startpriv->uid.type == UA_HYPER_PAV_ALIAS) {
  pfxdata->validity.verify_base = 1;
  pfxdata->validity.hyper_pav = 1;
 }

 rc = define_extent(NULL, dedata, trk, totrk, cmd, basedev, blksize);

 /*
 * For some commands the System Time Stamp is set in the define extent
 * data when XRC is supported. The validity of the time stamp must be
 * reflected in the prefix data as well.
 */

 if (dedata->ga_extended & 0x08 && dedata->ga_extended & 0x02)
  pfxdata->validity.time_stamp = 1; /* 'Time Stamp Valid'   */

 if (format == 1) {
  locate_record_ext(NULL, lredata, trk, rec_on_trk, count, cmd,
      basedev, blksize, tlf);
 }

 return rc;
}

static int prefix(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata,
    unsigned int trk, unsigned int totrk, int cmd,
    struct dasd_device *basedev, struct dasd_device *startdev)
{
 return prefix_LRE(ccw, pfxdata, trk, totrk, cmd, basedev, startdev,
     0, 0, 0, 0, 0);
}

static void
locate_record(struct ccw1 *ccw, struct LO_eckd_data *data, unsigned int trk,
       unsigned int rec_on_trk, int no_rec, int cmd,
       struct dasd_device * device, int reclen)
{
 struct dasd_eckd_private *private = device->private;
 int sector;
 int dn, d;

 DBF_DEV_EVENT(DBF_INFO, device,
    "Locate: trk %d, rec %d, no_rec %d, cmd %d, reclen %d",
    trk, rec_on_trk, no_rec, cmd, reclen);

 ccw->cmd_code = DASD_ECKD_CCW_LOCATE_RECORD;
 ccw->flags = 0;
 ccw->count = 16;
 ccw->cda = 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);
 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)
{
 struct pe_handler_work_data *data;
 struct dasd_device *device;

 data = container_of(work, struct pe_handler_work_data, worker);
 device = data->device;

 /* delay path verification until device was resumed */
 if (test_bit(DASD_FLAG_SUSPENDED, &device->flags)) {
  schedule_work(work);
  return;
 }
 /* check if path verification already running and delay if so */
 if (test_and_set_bit(DASD_FLAG_PATH_VERIFY, &device->flags)) {
  schedule_work(work);
  return;
 }

 if (data->tbvpm)
  dasd_eckd_path_available_action(device, data);
 if (data->fcsecpm)
  dasd_eckd_read_fc_security(device);

 clear_bit(DASD_FLAG_PATH_VERIFY, &device->flags);
 dasd_put_device(device);
 if (data->isglobal)
  mutex_unlock(&dasd_pe_handler_mutex);
 else
  kfree(data);
}

static int dasd_eckd_pe_handler(struct dasd_device *device,
    __u8 tbvpm, __u8 fcsecpm)
{
 struct pe_handler_work_data *data;

 data = kzalloc(sizeof(*data), GFP_ATOMIC | GFP_DMA);
 if (!data) {
  if (mutex_trylock(&dasd_pe_handler_mutex)) {
   data = pe_handler_worker;
   data->isglobal = 1;
  } else {
   return -ENOMEM;
  }
 }
 INIT_WORK(&data->worker, do_pe_handler_work);
 dasd_get_device(device);
 data->device = device;
 data->tbvpm = tbvpm;
 data->fcsecpm = fcsecpm;
 schedule_work(&data->worker);
 return 0;
}

static void dasd_eckd_reset_path(struct dasd_device *device, __u8 pm)
{
 struct dasd_eckd_private *private = device->private;
 unsigned long flags;

 if (!private->fcx_max_data)
  private->fcx_max_data = get_fcx_max_data(device);
 spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
 dasd_path_set_tbvpm(device, pm ? : dasd_path_get_notoperpm(device));
 dasd_schedule_device_bh(device);
 spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
}

static int dasd_eckd_read_features(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;
 struct dasd_psf_prssd_data *prssdp;
 struct dasd_rssd_features *features;
 struct dasd_ccw_req *cqr;
 struct ccw1 *ccw;
 int rc;

 memset(&private->features, 0, sizeof(struct dasd_rssd_features));
 cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ + 1 /* RSSD */,
       (sizeof(struct dasd_psf_prssd_data) +
        sizeof(struct dasd_rssd_features)),
       device, NULL);
 if (IS_ERR(cqr)) {
  DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s""Could not "
    "allocate initialization request");
  return PTR_ERR(cqr);
 }
 cqr->startdev = device;
 cqr->memdev = device;
 cqr->block = NULL;
 cqr->retries = 256;
 cqr->expires = 10 * HZ;

 /* Prepare for Read Subsystem Data */
 prssdp = (struct dasd_psf_prssd_data *) cqr->data;
 memset(prssdp, 0, sizeof(struct dasd_psf_prssd_data));
 prssdp->order = PSF_ORDER_PRSSD;
 prssdp->suborder = 0x41; /* Read Feature Codes */
 /* all other bytes of prssdp must be zero */

 ccw = cqr->cpaddr;
 ccw->cmd_code = DASD_ECKD_CCW_PSF;
 ccw->count = sizeof(struct dasd_psf_prssd_data);
 ccw->flags |= CCW_FLAG_CC;
 ccw->cda = 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)
{
 struct dasd_eckd_private *private = device->private;
 struct dasd_psf_prssd_data *prssdp;
 struct dasd_rssd_vsq *vsq;
 struct dasd_ccw_req *cqr;
 struct ccw1 *ccw;
 int useglobal;
 int rc;

 /* This command cannot be executed on an alias device */
 if (private->uid.type == UA_BASE_PAV_ALIAS ||
     private->uid.type == UA_HYPER_PAV_ALIAS)
  return 0;

 useglobal = 0;
 cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 2 /* PSF + RSSD */,
       sizeof(*prssdp) + sizeof(*vsq), device, NULL);
 if (IS_ERR(cqr)) {
  DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
    "Could not allocate initialization request");
  mutex_lock(&dasd_vol_info_mutex);
  useglobal = 1;
  cqr = &dasd_vol_info_req->cqr;
  memset(cqr, 0, sizeof(*cqr));
  memset(dasd_vol_info_req, 0, sizeof(*dasd_vol_info_req));
  cqr->cpaddr = &dasd_vol_info_req->ccw;
  cqr->data = &dasd_vol_info_req->data;
  cqr->magic = DASD_ECKD_MAGIC;
 }

 /* Prepare for Read Subsystem Data */
 prssdp = cqr->data;
 prssdp->order = PSF_ORDER_PRSSD;
 prssdp->suborder = PSF_SUBORDER_VSQ; /* Volume Storage Query */
 prssdp->lss = private->conf.ned->ID;
 prssdp->volume = private->conf.ned->unit_addr;

 ccw = cqr->cpaddr;
 ccw->cmd_code = DASD_ECKD_CCW_PSF;
 ccw->count = sizeof(*prssdp);
 ccw->flags |= CCW_FLAG_CC;
 ccw->cda = 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;
}

static int dasd_eckd_is_ese(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->vsq.vol_info.ese;
}

static int dasd_eckd_ext_pool_id(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->vsq.extent_pool_id;
}

/*
 * This value represents the total amount of available space. As more space is
 * allocated by ESE volumes, this value will decrease.
 * The data for this value is therefore updated on any call.
 */

static int dasd_eckd_space_configured(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;
 int rc;

 rc = dasd_eckd_read_vol_info(device);

 return rc ? : private->vsq.space_configured;
}

/*
 * The value of space allocated by an ESE volume may have changed and is
 * therefore updated on any call.
 */

static int dasd_eckd_space_allocated(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;
 int rc;

 rc = dasd_eckd_read_vol_info(device);

 return rc ? : private->vsq.space_allocated;
}

static int dasd_eckd_logical_capacity(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->vsq.logical_capacity;
}

static void dasd_eckd_ext_pool_exhaust_work(struct work_struct *work)
{
 struct ext_pool_exhaust_work_data *data;
 struct dasd_device *device;
 struct dasd_device *base;

 data = container_of(work, struct ext_pool_exhaust_work_data, worker);
 device = data->device;
 base = data->base;

 if (!base)
  base = device;
 if (dasd_eckd_space_configured(base) != 0) {
  dasd_generic_space_avail(device);
 } else {
  dev_warn(&device->cdev->dev, "No space left in the extent pool\n");
  DBF_DEV_EVENT(DBF_WARNING, device, "%s""out of space");
 }

 dasd_put_device(device);
 kfree(data);
}

static int dasd_eckd_ext_pool_exhaust(struct dasd_device *device,
          struct dasd_ccw_req *cqr)
{
 struct ext_pool_exhaust_work_data *data;

 data = kzalloc(sizeof(*data), GFP_ATOMIC);
 if (!data)
  return -ENOMEM;
 INIT_WORK(&data->worker, dasd_eckd_ext_pool_exhaust_work);
 dasd_get_device(device);
 data->device = device;

 if (cqr->block)
  data->base = cqr->block->base;
 else if (cqr->basedev)
  data->base = cqr->basedev;
 else
  data->base = NULL;

 schedule_work(&data->worker);

 return 0;
}

static void dasd_eckd_cpy_ext_pool_data(struct dasd_device *device,
     struct dasd_rssd_lcq *lcq)
{
 struct dasd_eckd_private *private = device->private;
 int pool_id = dasd_eckd_ext_pool_id(device);
 struct dasd_ext_pool_sum eps;
 int i;

 for (i = 0; i < lcq->pool_count; i++) {
  eps = lcq->ext_pool_sum[i];
  if (eps.pool_id == pool_id) {
   memcpy(&private->eps, &eps,
          sizeof(struct dasd_ext_pool_sum));
  }
 }
}

/* Read Extent Pool Information - Logical Configuration Query */
static int dasd_eckd_read_ext_pool_info(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;
 struct dasd_psf_prssd_data *prssdp;
 struct dasd_rssd_lcq *lcq;
 struct dasd_ccw_req *cqr;
 struct ccw1 *ccw;
 int rc;

 /* This command cannot be executed on an alias device */
 if (private->uid.type == UA_BASE_PAV_ALIAS ||
     private->uid.type == UA_HYPER_PAV_ALIAS)
  return 0;

 cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 2 /* PSF + RSSD */,
       sizeof(*prssdp) + sizeof(*lcq), device, NULL);
 if (IS_ERR(cqr)) {
  DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
    "Could not allocate initialization request");
  return PTR_ERR(cqr);
 }

 /* Prepare for Read Subsystem Data */
 prssdp = cqr->data;
 memset(prssdp, 0, sizeof(*prssdp));
 prssdp->order = PSF_ORDER_PRSSD;
 prssdp->suborder = PSF_SUBORDER_LCQ; /* Logical Configuration Query */

 ccw = cqr->cpaddr;
 ccw->cmd_code = DASD_ECKD_CCW_PSF;
 ccw->count = sizeof(*prssdp);
 ccw->flags |= CCW_FLAG_CC;
 ccw->cda = 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 = private->eps;

 if (!eps.flags.extent_size_valid)
  return 0;
 if (eps.extent_size.size_1G)
  return 1113;
 if (eps.extent_size.size_16M)
  return 21;

 return 0;
}

static int dasd_eckd_ext_pool_warn_thrshld(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->eps.warn_thrshld;
}

static int dasd_eckd_ext_pool_cap_at_warnlevel(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->eps.flags.capacity_at_warnlevel;
}

/*
 * Extent Pool out of space
 */

static int dasd_eckd_ext_pool_oos(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->eps.flags.pool_oos;
}

/*
 * Build CP for Perform Subsystem Function - SSC.
 */

static struct dasd_ccw_req *dasd_eckd_build_psf_ssc(struct dasd_device *device,
          int enable_pav)
{
 struct dasd_ccw_req *cqr;
 struct dasd_psf_ssc_data *psf_ssc_data;
 struct ccw1 *ccw;

 cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* PSF */ ,
      sizeof(struct dasd_psf_ssc_data),
       device, NULL);

 if (IS_ERR(cqr)) {
  DBF_DEV_EVENT(DBF_WARNING, device, "%s",
      "Could not allocate PSF-SSC request");
  return cqr;
 }
 psf_ssc_data = (struct dasd_psf_ssc_data *)cqr->data;
 psf_ssc_data->order = PSF_ORDER_SSC;
 psf_ssc_data->suborder = 0xc0;
 if (enable_pav) {
  psf_ssc_data->suborder |= 0x08;
  psf_ssc_data->reserved[0] = 0x88;
 }
 ccw = cqr->cpaddr;
 ccw->cmd_code = DASD_ECKD_CCW_PSF;
 ccw->cda = 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;
}

/*
 * Valide storage server of current device.
 */

static int dasd_eckd_validate_server(struct dasd_device *device,
         unsigned long flags)
{
 struct dasd_eckd_private *private = device->private;
 int enable_pav, rc;

 if (private->uid.type == UA_BASE_PAV_ALIAS ||
     private->uid.type == UA_HYPER_PAV_ALIAS)
  return 0;
 if (dasd_nopav || machine_is_vm())
  enable_pav = 0;
 else
  enable_pav = 1;
 rc = dasd_eckd_psf_ssc(device, enable_pav, flags);

 /* may be requested feature is not available on server,
 * therefore just report error and go ahead */

 DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "PSF-SSC for SSID %04x "
   "returned rc=%d", private->uid.ssid, rc);
 return rc;
}

/*
 * worker to do a validate server in case of a lost pathgroup
 */

static void dasd_eckd_do_validate_server(struct work_struct *work)
{
 struct dasd_device *device = container_of(work, struct dasd_device,
        kick_validate);
 unsigned long flags = 0;

 set_bit(DASD_CQR_FLAGS_FAILFAST, &flags);
 if (dasd_eckd_validate_server(device, flags)
     == -EAGAIN) {
  /* schedule worker again if failed */
  schedule_work(&device->kick_validate);
  return;
 }

 dasd_put_device(device);
}

static void dasd_eckd_kick_validate_server(struct dasd_device *device)
{
 dasd_get_device(device);
 /* exit if device not online or in offline processing */
 if (test_bit(DASD_FLAG_OFFLINE, &device->flags) ||
    device->state < DASD_STATE_ONLINE) {
  dasd_put_device(device);
  return;
 }
 /* queue call to do_validate_server to the kernel event daemon. */
 if (!schedule_work(&device->kick_validate))
  dasd_put_device(device);
}

/*
 * return if the device is the copy relation primary if a copy relation is active
 */

static int dasd_device_is_primary(struct dasd_device *device)
{
 if (!device->copy)
  return 1;

 if (device->copy->active->device == device)
  return 1;

 return 0;
}

static int dasd_eckd_alloc_block(struct dasd_device *device)
{
 struct dasd_block *block;
 struct dasd_uid temp_uid;

 if (!dasd_device_is_primary(device))
  return 0;

 dasd_eckd_get_uid(device, &temp_uid);
 if (temp_uid.type == UA_BASE_DEVICE) {
  block = dasd_alloc_block();
  if (IS_ERR(block)) {
   DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
     "could not allocate dasd block structure");
   return PTR_ERR(block);
  }
  device->block = block;
  block->base = device;
 }
 return 0;
}

static bool dasd_eckd_pprc_enabled(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;

 return private->rdc_data.facilities.PPRC_enabled;
}

/*
 * Check device characteristics.
 * If the device is accessible using ECKD discipline, the device is enabled.
 */

static int
dasd_eckd_check_characteristics(struct dasd_device *device)
{
 struct dasd_eckd_private *private = device->private;
 int rc, i;
 int readonly;
 unsigned long value;

 /* setup work queue for validate server*/
 INIT_WORK(&device->kick_validate, dasd_eckd_do_validate_server);
 /* setup work queue for summary unit check */
 INIT_WORK(&device->suc_work, dasd_alias_handle_summary_unit_check);

 if (!ccw_device_is_pathgroup(device->cdev)) {
  dev_warn(&device->cdev->dev,
    "A channel path group could not be established\n");
  return -EIO;
 }
 if (!ccw_device_is_multipath(device->cdev)) {
  dev_info(&device->cdev->dev,
    "The DASD is not operating in multipath mode\n");
 }
 if (!private) {
  private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA);
  if (!private) {
   dev_warn(&device->cdev->dev,
     "Allocating memory for private DASD data "
     "failed\n");
   return -ENOMEM;
  }
  device->private = private;
 } else {
  memset(private, 0, sizeof(*private));
 }
 /* Invalidate status of initial analysis. */
 private->init_cqr_status = -1;
 /* Set default cache operations. */
 private->attrib.operation = DASD_NORMAL_CACHE;
 private->attrib.nr_cyl = 0;

 /* Read Configuration Data */
 rc = dasd_eckd_read_conf(device);
 if (rc)
  goto out_err1;

 /* set some default values */
 device->default_expires = DASD_EXPIRES;
 device->default_retries = DASD_RETRIES;
 device->path_thrhld = DASD_ECKD_PATH_THRHLD;
 device->path_interval = DASD_ECKD_PATH_INTERVAL;
 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);

 /* 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;
 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");
 }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=95 G=95

¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.