X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fblock%2Fdasd_eckd.c;h=b7a7fac3f7c3a05ae3448e5d172db199578a4dc6;hb=16c70f8c1b54b61c3b951b6fb220df250fe09b32;hp=9b667cd5aed3c089c288f6a91ec9a5dd19943702;hpb=9213980e6a70d8473e0ffd4b39ab5b6caaba9ff5;p=linux-2.6.git diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 9b667cd5a..b7a7fac3f 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -1,16 +1,14 @@ -/* +/* * File...........: linux/drivers/s390/block/dasd_eckd.c * Author(s)......: Holger Smolinski - * Horst Hummel + * Horst Hummel * Carsten Otte * Martin Schwidefsky * Bugreports.to..: * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000 * - * $Revision: 1.55 $ */ -#include #include #include #include @@ -25,6 +23,7 @@ #include #include #include +#include #include #include "dasd_int.h" @@ -37,9 +36,12 @@ #define ECKD_C0(i) (i->home_bytes) #define ECKD_F(i) (i->formula) -#define ECKD_F1(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f1):(i->factors.f_0x02.f1)) -#define ECKD_F2(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f2):(i->factors.f_0x02.f2)) -#define ECKD_F3(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f3):(i->factors.f_0x02.f3)) +#define ECKD_F1(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f1):\ + (i->factors.f_0x02.f1)) +#define ECKD_F2(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f2):\ + (i->factors.f_0x02.f2)) +#define ECKD_F3(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f3):\ + (i->factors.f_0x02.f3)) #define ECKD_F4(i) (ECKD_F(i)==0x02?(i->factors.f_0x02.f4):0) #define ECKD_F5(i) (ECKD_F(i)==0x02?(i->factors.f_0x02.f5):0) #define ECKD_F6(i) (i->factor6) @@ -53,6 +55,7 @@ static struct dasd_discipline dasd_eckd_discipline; struct dasd_eckd_private { struct dasd_eckd_characteristics rdc_data; struct dasd_eckd_confdata conf_data; + struct dasd_eckd_path path_data; struct eckd_count count_area[5]; int init_cqr_status; int uses_cdl; @@ -62,12 +65,16 @@ struct dasd_eckd_private { /* 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, 0x3390, 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 (0x3990, 0, 0x3390, 0), .driver_info = 0x1}, + { CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3390, 0), .driver_info = 0x2}, + { CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3390, 0), .driver_info = 0x3}, + { CCW_DEVICE_DEVTYPE (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 */ }, }; @@ -82,17 +89,22 @@ dasd_eckd_probe (struct ccw_device *cdev) { int ret; - ret = dasd_generic_probe (cdev, &dasd_eckd_discipline); - if (ret) + /* set ECKD specific ccw-device options */ + ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE); + if (ret) { + printk(KERN_WARNING + "dasd_eckd_probe: could not set ccw-device options " + "for %s\n", cdev->dev.bus_id); return ret; - ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP | CCWDEV_ALLOW_FORCE); - return 0; + } + ret = dasd_generic_probe(cdev, &dasd_eckd_discipline); + return ret; } static int dasd_eckd_set_online(struct ccw_device *cdev) { - return dasd_generic_set_online (cdev, &dasd_eckd_discipline); + return dasd_generic_set_online(cdev, &dasd_eckd_discipline); } static struct ccw_driver dasd_eckd_driver = { @@ -147,7 +159,6 @@ bytes_per_record(struct dasd_eckd_characteristics *rdc, int kl, int dl) bpr = fl1 + fl2; break; default: - MESSAGE(KERN_ERR, "unknown formula%d", rdc->formula); bpr = 0; break; } @@ -204,14 +215,14 @@ check_XRC (struct ccw1 *de_ccw, /* switch on System Time Stamp - needed for XRC Support */ if (private->rdc_data.facilities.XRC_supported) { - - data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */ - data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */ - + + data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid' */ + data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */ + data->ep_sys_time = get_clock (); - + de_ccw->count = sizeof (struct DE_eckd_data); - de_ccw->flags |= CCW_FLAG_SLI; + de_ccw->flags |= CCW_FLAG_SLI; } return; @@ -268,15 +279,17 @@ define_extent(struct ccw1 * ccw, struct DE_eckd_data * data, int trk, check_XRC (ccw, data, device); break; default: - MESSAGE(KERN_ERR, "unknown opcode 0x%x", cmd); + DEV_MESSAGE(KERN_ERR, device, "unknown opcode 0x%x", cmd); break; } data->attributes.mode = 0x3; /* ECKD */ - if (private->rdc_data.cu_type == 0x2105 + 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; + data->ga_extended |= 0x40; /* Regular Data Format Mode */ geo.cyl = private->rdc_data.no_cyl; geo.head = private->rdc_data.trk_per_cyl; @@ -288,8 +301,8 @@ define_extent(struct ccw1 * ccw, struct DE_eckd_data * data, int trk, /* check for sequential prestage - enhance cylinder range */ if (data->attributes.operation == DASD_SEQ_PRESTAGE || data->attributes.operation == DASD_SEQ_ACCESS) { - - if (end.cyl + private->attrib.nr_cyl < geo.cyl) + + if (end.cyl + private->attrib.nr_cyl < geo.cyl) end.cyl += private->attrib.nr_cyl; else end.cyl = (geo.cyl - 1); @@ -309,10 +322,10 @@ locate_record(struct ccw1 *ccw, struct LO_eckd_data *data, int trk, struct dasd_eckd_private *private; int sector; int dn, d; - + private = (struct dasd_eckd_private *) device->private; - DBF_EVENT(DBF_INFO, + 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); @@ -394,7 +407,7 @@ locate_record(struct ccw1 *ccw, struct LO_eckd_data *data, int trk, data->operation.operation = 0x0b; break; default: - MESSAGE(KERN_ERR, "unknown opcode 0x%x", cmd); + DEV_MESSAGE(KERN_ERR, device, "unknown opcode 0x%x", cmd); } data->seek_addr.cyl = data->search_arg.cyl = trk / private->rdc_data.trk_per_cyl; @@ -438,22 +451,200 @@ dasd_eckd_cdl_reclen(int recid) return LABEL_SIZE; } +/* + * Generate device unique id that specifies the physical device. + */ +static int +dasd_eckd_generate_uid(struct dasd_device *device, struct dasd_uid *uid) +{ + struct dasd_eckd_private *private; + struct dasd_eckd_confdata *confdata; + + private = (struct dasd_eckd_private *) device->private; + if (!private) + return -ENODEV; + confdata = &private->conf_data; + if (!confdata) + return -ENODEV; + + memset(uid, 0, sizeof(struct dasd_uid)); + memcpy(uid->vendor, confdata->ned1.HDA_manufacturer, + sizeof(uid->vendor) - 1); + EBCASC(uid->vendor, sizeof(uid->vendor) - 1); + memcpy(uid->serial, confdata->ned1.HDA_location, + sizeof(uid->serial) - 1); + EBCASC(uid->serial, sizeof(uid->serial) - 1); + uid->ssid = confdata->neq.subsystemID; + if (confdata->ned2.sneq.flags == 0x40) { + uid->alias = 1; + uid->unit_addr = confdata->ned2.sneq.base_unit_addr; + } else + uid->unit_addr = confdata->ned1.unit_addr; + + return 0; +} + +static int +dasd_eckd_read_conf(struct dasd_device *device) +{ + void *conf_data; + int conf_len, conf_data_saved; + int rc; + __u8 lpm; + struct dasd_eckd_private *private; + struct dasd_eckd_path *path_data; + + private = (struct dasd_eckd_private *) device->private; + path_data = (struct dasd_eckd_path *) &private->path_data; + path_data->opm = ccw_device_get_path_mask(device->cdev); + lpm = 0x80; + conf_data_saved = 0; + + /* get configuration data per operational path */ + for (lpm = 0x80; lpm; lpm>>= 1) { + if (lpm & path_data->opm){ + rc = read_conf_data_lpm(device->cdev, &conf_data, + &conf_len, lpm); + if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */ + MESSAGE(KERN_WARNING, + "Read configuration data returned " + "error %d", rc); + return rc; + } + if (conf_data == NULL) { + MESSAGE(KERN_WARNING, "%s", "No configuration " + "data retrieved"); + continue; /* no errror */ + } + if (conf_len != sizeof (struct dasd_eckd_confdata)) { + MESSAGE(KERN_WARNING, + "sizes of configuration data mismatch" + "%d (read) vs %ld (expected)", + conf_len, + sizeof (struct dasd_eckd_confdata)); + kfree(conf_data); + continue; /* no errror */ + } + /* save first valid configuration data */ + if (!conf_data_saved){ + memcpy(&private->conf_data, conf_data, + sizeof (struct dasd_eckd_confdata)); + conf_data_saved++; + } + switch (((char *)conf_data)[242] & 0x07){ + case 0x02: + path_data->npm |= lpm; + break; + case 0x03: + path_data->ppm |= lpm; + break; + } + kfree(conf_data); + } + } + return 0; +} + +/* + * Build CP for Perform Subsystem Function - SSC. + */ +struct dasd_ccw_req * +dasd_eckd_build_psf_ssc(struct dasd_device *device) +{ + struct dasd_ccw_req *cqr; + struct dasd_psf_ssc_data *psf_ssc_data; + struct ccw1 *ccw; + + cqr = dasd_smalloc_request("ECKD", 1 /* PSF */ , + sizeof(struct dasd_psf_ssc_data), + device); + + if (IS_ERR(cqr)) { + DEV_MESSAGE(KERN_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 = 0x08; + + ccw = cqr->cpaddr; + ccw->cmd_code = DASD_ECKD_CCW_PSF; + ccw->cda = (__u32)(addr_t)psf_ssc_data; + ccw->count = 66; + + cqr->device = device; + cqr->expires = 10*HZ; + cqr->buildclk = get_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) +{ + struct dasd_ccw_req *cqr; + int rc; + + cqr = dasd_eckd_build_psf_ssc(device); + if (IS_ERR(cqr)) + return PTR_ERR(cqr); + + rc = dasd_sleep_on(cqr); + if (!rc) + /* trigger CIO to reprobe devices */ + css_schedule_reprobe(); + dasd_sfree_request(cqr, cqr->device); + return rc; +} + +/* + * Valide storage server of current device. + */ +static int +dasd_eckd_validate_server(struct dasd_device *device, struct dasd_uid *uid) +{ + int rc; + + /* Currently PAV is the only reason to 'validate' server on LPAR */ + if (dasd_nopav || MACHINE_IS_VM) + return 0; + + rc = dasd_eckd_psf_ssc(device); + /* may be requested feature is not available on server, + * therefore just report error and go ahead */ + DEV_MESSAGE(KERN_INFO, device, + "PSF-SSC on storage subsystem %s.%s.%04x returned rc=%d", + uid->vendor, uid->serial, uid->ssid, rc); + /* RE-Read Configuration Data */ + return dasd_eckd_read_conf(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; + struct dasd_uid uid; void *rdc_data; - void *conf_data; - int conf_len; int rc; private = (struct dasd_eckd_private *) device->private; if (private == NULL) { - private = kmalloc(sizeof(struct dasd_eckd_private), + private = kzalloc(sizeof(struct dasd_eckd_private), GFP_KERNEL | GFP_DMA); if (private == NULL) { - MESSAGE(KERN_WARNING, "%s", - "memory allocation failed for private data"); + DEV_MESSAGE(KERN_WARNING, device, "%s", + "memory allocation failed for private " + "data"); return -ENOMEM; } device->private = (void *) private; @@ -464,14 +655,29 @@ dasd_eckd_check_characteristics(struct dasd_device *device) private->attrib.operation = DASD_NORMAL_CACHE; private->attrib.nr_cyl = 0; + /* Read Configuration Data */ + rc = dasd_eckd_read_conf(device); + if (rc) + return rc; + + /* Generate device unique id and register in devmap */ + rc = dasd_eckd_generate_uid(device, &uid); + if (rc) + return rc; + rc = dasd_set_uid(device->cdev, &uid); + if (rc == 1) /* new server found */ + rc = dasd_eckd_validate_server(device, &uid); + if (rc) + return rc; + /* Read Device Characteristics */ rdc_data = (void *) &(private->rdc_data); + memset(rdc_data, 0, sizeof(rdc_data)); rc = read_dev_chars(device->cdev, &rdc_data, 64); - if (rc) { - MESSAGE(KERN_WARNING, - "Read device characteristics returned error %d", rc); - return rc; - } + if (rc) + DEV_MESSAGE(KERN_WARNING, device, + "Read device characteristics returned " + "rc=%d", rc); DEV_MESSAGE(KERN_INFO, device, "%04X/%02X(CU:%04X/%02X) Cyl:%d Head:%d Sec:%d", @@ -482,29 +688,7 @@ dasd_eckd_check_characteristics(struct dasd_device *device) private->rdc_data.no_cyl, private->rdc_data.trk_per_cyl, private->rdc_data.sec_per_trk); - - /* Read Configuration Data */ - rc = read_conf_data(device->cdev, &conf_data, &conf_len); - if (rc && rc != -EOPNOTSUPP) { /* -EOPNOTSUPP is ok */ - MESSAGE(KERN_WARNING, - "Read configuration data returned error %d", rc); - return rc; - } - if (conf_data == NULL) { - MESSAGE(KERN_WARNING, "%s", "No configuration data retrieved"); - return 0; /* no errror */ - } - if (conf_len != sizeof (struct dasd_eckd_confdata)) { - MESSAGE(KERN_WARNING, - "sizes of configuration data mismatch" - "%d (read) vs %ld (expected)", - conf_len, sizeof (struct dasd_eckd_confdata)); - return 0; /* no errror */ - } - memcpy(&private->conf_data, conf_data, - sizeof (struct dasd_eckd_confdata)); - - return 0; + return rc; } static struct dasd_ccw_req * @@ -673,7 +857,7 @@ dasd_eckd_end_analysis(struct dasd_device *device) ((private->rdc_data.no_cyl * private->rdc_data.trk_per_cyl * blk_per_trk * (device->bp_block >> 9)) >> 1), - ((blk_per_trk * device->bp_block) >> 10), + ((blk_per_trk * device->bp_block) >> 10), private->uses_cdl ? "compatible disk layout" : "linux disk layout"); @@ -738,8 +922,9 @@ dasd_eckd_format_device(struct dasd_device * device, return ERR_PTR(-EINVAL); } if (dasd_check_blocksize(fdata->blksize) != 0) { - MESSAGE(KERN_WARNING, "Invalid blocksize %d...terminating!", - fdata->blksize); + DEV_MESSAGE(KERN_WARNING, device, + "Invalid blocksize %d...terminating!", + fdata->blksize); return ERR_PTR(-EINVAL); } @@ -775,7 +960,8 @@ dasd_eckd_format_device(struct dasd_device * device, sizeof(struct eckd_count); break; default: - MESSAGE(KERN_WARNING, "Invalid flags 0x%x.", fdata->intensity); + DEV_MESSAGE(KERN_WARNING, device, "Invalid flags 0x%x.", + fdata->intensity); return ERR_PTR(-EINVAL); } /* Allocate the format ccw request. */ @@ -868,7 +1054,7 @@ dasd_eckd_format_device(struct dasd_device * device, if (i < 3) { ect->kl = 4; ect->dl = sizes_trk0[i] - 4; - } + } } if ((fdata->intensity & 0x08) && fdata->start_unit == 1) { @@ -903,13 +1089,15 @@ dasd_eckd_examine_error(struct dasd_ccw_req * cqr, struct irb * irb) switch (cdev->id.cu_type) { case 0x3990: case 0x2105: + case 0x2107: + case 0x1750: return dasd_3990_erp_examine(cqr, irb); case 0x9343: return dasd_9343_erp_examine(cqr, irb); case 0x3880: default: - MESSAGE(KERN_WARNING, "%s", - "default (unknown CU type) - RECOVERABLE return"); + DEV_MESSAGE(KERN_WARNING, device, "%s", + "default (unknown CU type) - RECOVERABLE return"); return dasd_era_recover; } } @@ -923,9 +1111,10 @@ dasd_eckd_erp_action(struct dasd_ccw_req * cqr) switch (cdev->id.cu_type) { case 0x3990: case 0x2105: + case 0x2107: + case 0x1750: return dasd_3990_erp_action; case 0x9343: - /* Return dasd_9343_erp_action; */ case 0x3880: default: return dasd_default_erp_action; @@ -982,9 +1171,10 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req) /* Eckd can only do full blocks. */ return ERR_PTR(-EINVAL); count += bv->bv_len >> (device->s2b_shift + 9); -#if defined(CONFIG_ARCH_S390X) - cidaw += idal_nr_words(page_address(bv->bv_page) + - bv->bv_offset, bv->bv_len); +#if defined(CONFIG_64BIT) + if (idal_is_needed (page_address(bv->bv_page), + bv->bv_len)) + cidaw += bv->bv_len >> (device->s2b_shift + 9); #endif } } @@ -1023,6 +1213,14 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req) } rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) { dst = page_address(bv->bv_page) + bv->bv_offset; + if (dasd_page_cache) { + char *copy = kmem_cache_alloc(dasd_page_cache, + SLAB_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); @@ -1033,7 +1231,8 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req) if (dasd_eckd_cdl_special(blk_per_trk, recid)){ rcmd |= 0x8; count = dasd_eckd_cdl_reclen(recid); - if (count < blksize) + if (count < blksize && + rq_data_dir(req) == READ) memset(dst + count, 0xe5, blksize - count); } @@ -1067,15 +1266,69 @@ dasd_eckd_build_cp(struct dasd_device * device, struct request *req) recid++; } } + if (req->flags & REQ_FAILFAST) + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->device = device; cqr->expires = 5 * 60 * HZ; /* 5 minutes */ - cqr->lpm = LPM_ANYPATH; + cqr->lpm = private->path_data.ppm; cqr->retries = 256; cqr->buildclk = get_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 bio *bio; + struct bio_vec *bv; + char *dst, *cda; + unsigned int blksize, blk_per_trk, off; + sector_t recid; + int i, status; + + if (!dasd_page_cache) + goto out; + private = (struct dasd_eckd_private *) cqr->device->private; + blksize = cqr->device->bp_block; + blk_per_trk = recs_per_track(&private->rdc_data, 0, blksize); + recid = req->sector >> cqr->device->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_bio(bio, req) bio_for_each_segment(bv, bio, i) { + dst = page_address(bv->bv_page) + bv->bv_offset; + for (off = 0; off < bv->bv_len; off += blksize) { + /* Skip locate record. */ + if (private->uses_cdl && recid <= 2*blk_per_trk) + ccw++; + if (dst) { + if (ccw->flags & CCW_FLAG_IDA) + cda = *((char **)((addr_t) ccw->cda)); + else + cda = (char *)((addr_t) 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: + status = cqr->status == DASD_CQR_DONE; + dasd_sfree_request(cqr, cqr->device); + return status; +} + static int dasd_eckd_fill_info(struct dasd_device * device, struct dasd_information2_t * info) @@ -1101,29 +1354,24 @@ dasd_eckd_fill_info(struct dasd_device * device, /* * Release device ioctl. - * Buils a channel programm to releases a prior reserved + * Buils a channel programm to releases a prior reserved * (see dasd_eckd_reserve) device. */ static int -dasd_eckd_release(struct block_device *bdev, int no, long args) +dasd_eckd_release(struct dasd_device *device) { - struct dasd_device *device; struct dasd_ccw_req *cqr; int rc; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, 1, 32, device); - if (cqr == NULL) { - MESSAGE(KERN_WARNING, "%s", - "No memory to allocate initialization request"); - return -ENOMEM; + if (IS_ERR(cqr)) { + DEV_MESSAGE(KERN_WARNING, device, "%s", + "Could not allocate initialization request"); + return PTR_ERR(cqr); } cqr->cpaddr->cmd_code = DASD_ECKD_CCW_RELEASE; cqr->cpaddr->flags |= CCW_FLAG_SLI; @@ -1131,6 +1379,7 @@ dasd_eckd_release(struct block_device *bdev, int no, long args) cqr->cpaddr->cda = (__u32)(addr_t) cqr->data; cqr->device = device; clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->retries = 0; cqr->expires = 2 * HZ; cqr->buildclk = get_clock(); @@ -1145,29 +1394,24 @@ dasd_eckd_release(struct block_device *bdev, int no, long args) /* * 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. + * 'timeout the request'. This leads to a terminate IO if + * the interrupt is outstanding for a certain time. */ static int -dasd_eckd_reserve(struct block_device *bdev, int no, long args) +dasd_eckd_reserve(struct dasd_device *device) { - struct dasd_device *device; struct dasd_ccw_req *cqr; int rc; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, 1, 32, device); - if (cqr == NULL) { - MESSAGE(KERN_WARNING, "%s", - "No memory to allocate initialization request"); - return -ENOMEM; + if (IS_ERR(cqr)) { + DEV_MESSAGE(KERN_WARNING, device, "%s", + "Could not allocate initialization request"); + return PTR_ERR(cqr); } cqr->cpaddr->cmd_code = DASD_ECKD_CCW_RESERVE; cqr->cpaddr->flags |= CCW_FLAG_SLI; @@ -1175,6 +1419,7 @@ dasd_eckd_reserve(struct block_device *bdev, int no, long args) cqr->cpaddr->cda = (__u32)(addr_t) cqr->data; cqr->device = device; clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->retries = 0; cqr->expires = 2 * HZ; cqr->buildclk = get_clock(); @@ -1188,29 +1433,24 @@ dasd_eckd_reserve(struct block_device *bdev, int no, long args) /* * Steal lock ioctl - unconditional reserve device. - * Buils a channel programm to break a device's reservation. + * Buils a channel programm to break a device's reservation. * (unconditional reserve) */ static int -dasd_eckd_steal_lock(struct block_device *bdev, int no, long args) +dasd_eckd_steal_lock(struct dasd_device *device) { - struct dasd_device *device; struct dasd_ccw_req *cqr; int rc; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, 1, 32, device); - if (cqr == NULL) { - MESSAGE(KERN_WARNING, "%s", - "No memory to allocate initialization request"); - return -ENOMEM; + if (IS_ERR(cqr)) { + DEV_MESSAGE(KERN_WARNING, device, "%s", + "Could not allocate initialization request"); + return PTR_ERR(cqr); } cqr->cpaddr->cmd_code = DASD_ECKD_CCW_SLCK; cqr->cpaddr->flags |= CCW_FLAG_SLI; @@ -1218,6 +1458,7 @@ dasd_eckd_steal_lock(struct block_device *bdev, int no, long args) cqr->cpaddr->cda = (__u32)(addr_t) cqr->data; cqr->device = device; clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); + set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); cqr->retries = 0; cqr->expires = 2 * HZ; cqr->buildclk = get_clock(); @@ -1233,28 +1474,23 @@ dasd_eckd_steal_lock(struct block_device *bdev, int no, long args) * Read performance statistics */ static int -dasd_eckd_performance(struct block_device *bdev, int no, long args) +dasd_eckd_performance(struct dasd_device *device, void __user *argp) { - struct dasd_device *device; struct dasd_psf_prssd_data *prssdp; struct dasd_rssd_perf_stats_t *stats; struct dasd_ccw_req *cqr; struct ccw1 *ccw; int rc; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - cqr = dasd_smalloc_request(dasd_eckd_discipline.name, 1 /* PSF */ + 1 /* RSSD */ , (sizeof (struct dasd_psf_prssd_data) + sizeof (struct dasd_rssd_perf_stats_t)), device); - if (cqr == NULL) { - MESSAGE(KERN_WARNING, "%s", - "No memory to allocate initialization request"); - return -ENOMEM; + if (IS_ERR(cqr)) { + DEV_MESSAGE(KERN_WARNING, device, "%s", + "Could not allocate initialization request"); + return PTR_ERR(cqr); } cqr->device = device; cqr->retries = 0; @@ -1289,8 +1525,9 @@ dasd_eckd_performance(struct block_device *bdev, int no, long args) /* Prepare for Read Subsystem Data */ prssdp = (struct dasd_psf_prssd_data *) cqr->data; stats = (struct dasd_rssd_perf_stats_t *) (prssdp + 1); - rc = copy_to_user((long __user *) args, (long *) stats, - sizeof(struct dasd_rssd_perf_stats_t)); + if (copy_to_user(argp, stats, + sizeof(struct dasd_rssd_perf_stats_t))) + rc = -EFAULT; } dasd_sfree_request(cqr, cqr->device); return rc; @@ -1301,27 +1538,22 @@ dasd_eckd_performance(struct block_device *bdev, int no, long args) * Returnes the cache attributes used in Define Extend (DE). */ static int -dasd_eckd_get_attrib (struct block_device *bdev, int no, long args) +dasd_eckd_get_attrib(struct dasd_device *device, void __user *argp) { - struct dasd_device *device; - struct dasd_eckd_private *private; - struct attrib_data_t attrib; + struct dasd_eckd_private *private = + (struct dasd_eckd_private *)device->private; + struct attrib_data_t attrib = private->attrib; int rc; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - if (!args) + if (!argp) return -EINVAL; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - - private = (struct dasd_eckd_private *) device->private; - attrib = private->attrib; - - rc = copy_to_user((long __user *) args, (long *) &attrib, - sizeof (struct attrib_data_t)); + rc = 0; + if (copy_to_user(argp, (long *) &attrib, + sizeof (struct attrib_data_t))) + rc = -EFAULT; return rc; } @@ -1331,77 +1563,115 @@ dasd_eckd_get_attrib (struct block_device *bdev, int no, long args) * Stores the attributes for cache operation to be used in Define Extend (DE). */ static int -dasd_eckd_set_attrib(struct block_device *bdev, int no, long args) +dasd_eckd_set_attrib(struct dasd_device *device, void __user *argp) { - struct dasd_device *device; - struct dasd_eckd_private *private; + struct dasd_eckd_private *private = + (struct dasd_eckd_private *)device->private; struct attrib_data_t attrib; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - if (!args) + if (!argp) return -EINVAL; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - - if (copy_from_user(&attrib, (void __user *) args, - sizeof (struct attrib_data_t))) { + if (copy_from_user(&attrib, argp, sizeof(struct attrib_data_t))) return -EFAULT; - } - - private = (struct dasd_eckd_private *) device->private; - private->attrib = attrib; - DBF_DEV_EVENT(DBF_ERR, device, - "cache operation mode set to " - "%x (%i cylinder prestage)", - private->attrib.operation, private->attrib.nr_cyl); - + DEV_MESSAGE(KERN_INFO, device, + "cache operation mode set to %x (%i cylinder prestage)", + private->attrib.operation, private->attrib.nr_cyl); return 0; } +static int +dasd_eckd_ioctl(struct dasd_device *device, unsigned int cmd, void __user *argp) +{ + 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); + default: + return -ENOIOCTLCMD; + } +} + +/* + * Dump the range of CCWs into 'page' buffer + * and return number of printed chars. + */ +static inline int +dasd_eckd_dump_ccw_range(struct ccw1 *from, struct ccw1 *to, char *page) +{ + int len, count; + char *datap; + + len = 0; + while (from <= to) { + len += sprintf(page + len, KERN_ERR PRINTK_HEADER + " CCW %p: %08X %08X DAT:", + from, ((int *) from)[0], ((int *) from)[1]); + + /* get pointer to data (consider IDALs) */ + if (from->flags & CCW_FLAG_IDA) + datap = (char *) *((addr_t *) (addr_t) from->cda); + else + datap = (char *) ((addr_t) from->cda); + + /* dump data (max 32 bytes) */ + for (count = 0; count < from->count && count < 32; count++) { + 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++; + } + return len; +} + +/* + * Print sense data and related channel program. + * Parts are printed because printk buffer is only 1024 bytes. + */ static void dasd_eckd_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req, struct irb *irb) { - char *page; - struct ccw1 *act; + struct ccw1 *first, *last, *fail, *from, *to; int len, sl, sct; page = (char *) get_zeroed_page(GFP_ATOMIC); if (page == NULL) { - MESSAGE(KERN_ERR, "%s", "No memory to dump sense data"); + DEV_MESSAGE(KERN_ERR, device, " %s", + "No memory to dump sense data"); return; } - len = sprintf(page, KERN_ERR PRINTK_HEADER - "device %s: I/O status report:\n", + /* dump the sense data */ + len = sprintf(page, KERN_ERR PRINTK_HEADER + " I/O status report for device %s:\n", device->cdev->dev.bus_id); len += sprintf(page + len, KERN_ERR PRINTK_HEADER - "in req: %p CS: 0x%02X DS: 0x%02X\n", req, + " in req: %p CS: 0x%02X DS: 0x%02X\n", req, irb->scsw.cstat, irb->scsw.dstat); len += sprintf(page + len, KERN_ERR PRINTK_HEADER - "Failing CCW: %p\n", + " device %s: Failing CCW: %p\n", + device->cdev->dev.bus_id, (void *) (addr_t) irb->scsw.cpa); - act = req->cpaddr; - do { - DBF_EVENT(DBF_INFO, - "CCW %p: %08X %08X", - act, ((int *) act)[0], ((int *) act)[1]); - DBF_EVENT(DBF_INFO, - "DAT: %08X %08X %08X %08X", - ((int *) (addr_t) act->cda)[0], - ((int *) (addr_t) act->cda)[1], - ((int *) (addr_t) act->cda)[2], - ((int *) (addr_t) act->cda)[3]); - } while (act++->flags & (CCW_FLAG_CC | CCW_FLAG_DC)); if (irb->esw.esw0.erw.cons) { for (sl = 0; sl < 4; sl++) { len += sprintf(page + len, KERN_ERR PRINTK_HEADER - "Sense(hex) %2d-%2d:", + " Sense(hex) %2d-%2d:", (8 * sl), ((8 * sl) + 7)); for (sct = 0; sct < 8; sct++) { @@ -1413,24 +1683,55 @@ dasd_eckd_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req, if (irb->ecw[27] & DASD_SENSE_BIT_0) { /* 24 Byte Sense Data */ - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - "24 Byte: %x MSG %x, %s MSGb to SYSOP\n", - irb->ecw[7] >> 4, irb->ecw[7] & 0x0f, - irb->ecw[1] & 0x10 ? "" : "no"); + sprintf(page + len, KERN_ERR PRINTK_HEADER + " 24 Byte: %x MSG %x, " + "%s MSGb to SYSOP\n", + irb->ecw[7] >> 4, irb->ecw[7] & 0x0f, + irb->ecw[1] & 0x10 ? "" : "no"); } else { /* 32 Byte Sense Data */ - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - "32 Byte: Format: %x " - "Exception class %x\n", - irb->ecw[6] & 0x0f, irb->ecw[22] >> 4); + sprintf(page + len, KERN_ERR PRINTK_HEADER + " 32 Byte: Format: %x " + "Exception class %x\n", + irb->ecw[6] & 0x0f, irb->ecw[22] >> 4); } } else { - len += sprintf(page + len, KERN_ERR PRINTK_HEADER - "SORRY - NO VALID SENSE AVAILABLE\n"); + sprintf(page + len, KERN_ERR PRINTK_HEADER + " SORRY - NO VALID SENSE AVAILABLE\n"); } - - MESSAGE(KERN_ERR, "Sense data:\n%s", page); - + printk("%s", page); + + /* dump the Channel Program (max 140 Bytes per line) */ + /* Count CCW and print first CCWs (maximum 1024 % 140 = 7) */ + first = req->cpaddr; + for (last = first; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++); + to = min(first + 6, last); + len = sprintf(page, KERN_ERR PRINTK_HEADER + " Related CP in req: %p\n", req); + dasd_eckd_dump_ccw_range(first, to, page + len); + printk("%s", page); + + /* print failing CCW area (maximum 4) */ + /* scsw->cda is either valid or zero */ + len = 0; + from = ++to; + fail = (struct ccw1 *)(addr_t) irb->scsw.cpa; /* failing CCW */ + if (from < fail - 2) { + from = fail - 2; /* there is a gap - print header */ + len += sprintf(page, KERN_ERR PRINTK_HEADER "......\n"); + } + to = min(fail + 1, last); + len += dasd_eckd_dump_ccw_range(from, to, page + len); + + /* print last CCWs (maximum 2) */ + from = max(from, ++to); + if (from < last - 1) { + from = last - 1; /* there is a gap - print header */ + len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n"); + } + len += dasd_eckd_dump_ccw_range(from, last, page + len); + if (len > 0) + printk("%s", page); free_page((unsigned long) page); } @@ -1462,88 +1763,24 @@ static struct dasd_discipline dasd_eckd_discipline = { .erp_action = dasd_eckd_erp_action, .erp_postaction = dasd_eckd_erp_postaction, .build_cp = dasd_eckd_build_cp, + .free_cp = dasd_eckd_free_cp, .dump_sense = dasd_eckd_dump_sense, .fill_info = dasd_eckd_fill_info, + .ioctl = dasd_eckd_ioctl, }; static int __init dasd_eckd_init(void) { - int ret; - - dasd_ioctl_no_register(THIS_MODULE, BIODASDGATTR, - dasd_eckd_get_attrib); - dasd_ioctl_no_register(THIS_MODULE, BIODASDSATTR, - dasd_eckd_set_attrib); - dasd_ioctl_no_register(THIS_MODULE, BIODASDPSRD, - dasd_eckd_performance); - dasd_ioctl_no_register(THIS_MODULE, BIODASDRLSE, - dasd_eckd_release); - dasd_ioctl_no_register(THIS_MODULE, BIODASDRSRV, - dasd_eckd_reserve); - dasd_ioctl_no_register(THIS_MODULE, BIODASDSLCK, - dasd_eckd_steal_lock); - ASCEBC(dasd_eckd_discipline.ebcname, 4); - - ret = ccw_driver_register(&dasd_eckd_driver); - if (ret) { - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDGATTR, - dasd_eckd_get_attrib); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSATTR, - dasd_eckd_set_attrib); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDPSRD, - dasd_eckd_performance); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRLSE, - dasd_eckd_release); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRSRV, - dasd_eckd_reserve); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSLCK, - dasd_eckd_steal_lock); - return ret; - } - - dasd_generic_auto_online(&dasd_eckd_driver); - return 0; + return ccw_driver_register(&dasd_eckd_driver); } static void __exit dasd_eckd_cleanup(void) { ccw_driver_unregister(&dasd_eckd_driver); - - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDGATTR, - dasd_eckd_get_attrib); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSATTR, - dasd_eckd_set_attrib); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDPSRD, - dasd_eckd_performance); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRLSE, - dasd_eckd_release); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRSRV, - dasd_eckd_reserve); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSLCK, - dasd_eckd_steal_lock); } module_init(dasd_eckd_init); module_exit(dasd_eckd_cleanup); - -/* - * Overrides for Emacs so that we follow Linus's tabbing style. - * Emacs will notice this stuff at the end of the file and automatically - * adjust the settings for this buffer only. This must remain at the end - * of the file. - * --------------------------------------------------------------------------- - * Local variables: - * c-indent-level: 4 - * c-brace-imaginary-offset: 0 - * c-brace-offset: -4 - * c-argdecl-indent: 4 - * c-label-offset: -4 - * c-continued-statement-offset: 4 - * c-continued-brace-offset: 0 - * indent-tabs-mode: 1 - * tab-width: 8 - * End: - */