diff options
author | Stefan Weinhuber <wein@de.ibm.com> | 2011-01-05 06:48:02 -0500 |
---|---|---|
committer | Martin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com> | 2011-01-05 06:47:29 -0500 |
commit | ef19298b406f93af4bb249f0776deb8366e97532 (patch) | |
tree | 8b729739a57fd50e9fe67232b1c3b1264bf678a5 /drivers/s390/block/dasd_eckd.c | |
parent | 9062014cb60194630272709da82d5879d563865e (diff) |
[S390] dasd: add High Performance FICON multitrack support
Some storage systems support multitrack High Performance FICON
requests, which read or write data to more than one track.
This patch enables the DASD device driver to generate multitrack
High Performance FICON requests.
Signed-off-by: Stefan Weinhuber <wein@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/block/dasd_eckd.c')
-rw-r--r-- | drivers/s390/block/dasd_eckd.c | 158 |
1 files changed, 116 insertions, 42 deletions
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index bf61274af3bb..549443af121c 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c | |||
@@ -1105,6 +1105,37 @@ static void dasd_eckd_validate_server(struct dasd_device *device) | |||
1105 | "returned rc=%d", private->uid.ssid, rc); | 1105 | "returned rc=%d", private->uid.ssid, rc); |
1106 | } | 1106 | } |
1107 | 1107 | ||
1108 | static u32 get_fcx_max_data(struct dasd_device *device) | ||
1109 | { | ||
1110 | #if defined(CONFIG_64BIT) | ||
1111 | int tpm, mdc; | ||
1112 | int fcx_in_css, fcx_in_gneq, fcx_in_features; | ||
1113 | struct dasd_eckd_private *private; | ||
1114 | |||
1115 | if (dasd_nofcx) | ||
1116 | return 0; | ||
1117 | /* is transport mode supported? */ | ||
1118 | private = (struct dasd_eckd_private *) device->private; | ||
1119 | fcx_in_css = css_general_characteristics.fcx; | ||
1120 | fcx_in_gneq = private->gneq->reserved2[7] & 0x04; | ||
1121 | fcx_in_features = private->features.feature[40] & 0x80; | ||
1122 | tpm = fcx_in_css && fcx_in_gneq && fcx_in_features; | ||
1123 | |||
1124 | if (!tpm) | ||
1125 | return 0; | ||
1126 | |||
1127 | mdc = ccw_device_get_mdc(device->cdev, 0); | ||
1128 | if (mdc < 0) { | ||
1129 | dev_warn(&device->cdev->dev, "Detecting the maximum supported" | ||
1130 | " data size for zHPF requests failed\n"); | ||
1131 | return 0; | ||
1132 | } else | ||
1133 | return mdc * FCX_MAX_DATA_FACTOR; | ||
1134 | #else | ||
1135 | return 0; | ||
1136 | #endif | ||
1137 | } | ||
1138 | |||
1108 | /* | 1139 | /* |
1109 | * Check device characteristics. | 1140 | * Check device characteristics. |
1110 | * If the device is accessible using ECKD discipline, the device is enabled. | 1141 | * If the device is accessible using ECKD discipline, the device is enabled. |
@@ -1223,6 +1254,8 @@ dasd_eckd_check_characteristics(struct dasd_device *device) | |||
1223 | else | 1254 | else |
1224 | private->real_cyl = private->rdc_data.no_cyl; | 1255 | private->real_cyl = private->rdc_data.no_cyl; |
1225 | 1256 | ||
1257 | private->fcx_max_data = get_fcx_max_data(device); | ||
1258 | |||
1226 | readonly = dasd_device_is_ro(device); | 1259 | readonly = dasd_device_is_ro(device); |
1227 | if (readonly) | 1260 | if (readonly) |
1228 | set_bit(DASD_FLAG_DEVICE_RO, &device->flags); | 1261 | set_bit(DASD_FLAG_DEVICE_RO, &device->flags); |
@@ -2326,6 +2359,12 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( | |||
2326 | struct tidaw *last_tidaw = NULL; | 2359 | struct tidaw *last_tidaw = NULL; |
2327 | int itcw_op; | 2360 | int itcw_op; |
2328 | size_t itcw_size; | 2361 | size_t itcw_size; |
2362 | u8 tidaw_flags; | ||
2363 | unsigned int seg_len, part_len, len_to_track_end; | ||
2364 | unsigned char new_track; | ||
2365 | sector_t recid, trkid; | ||
2366 | unsigned int offs; | ||
2367 | unsigned int count, count_to_trk_end; | ||
2329 | 2368 | ||
2330 | basedev = block->base; | 2369 | basedev = block->base; |
2331 | private = (struct dasd_eckd_private *) basedev->private; | 2370 | private = (struct dasd_eckd_private *) basedev->private; |
@@ -2341,12 +2380,16 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( | |||
2341 | /* trackbased I/O needs address all memory via TIDAWs, | 2380 | /* trackbased I/O needs address all memory via TIDAWs, |
2342 | * not just for 64 bit addresses. This allows us to map | 2381 | * not just for 64 bit addresses. This allows us to map |
2343 | * each segment directly to one tidaw. | 2382 | * each segment directly to one tidaw. |
2383 | * In the case of write requests, additional tidaws may | ||
2384 | * be needed when a segment crosses a track boundary. | ||
2344 | */ | 2385 | */ |
2345 | trkcount = last_trk - first_trk + 1; | 2386 | trkcount = last_trk - first_trk + 1; |
2346 | ctidaw = 0; | 2387 | ctidaw = 0; |
2347 | rq_for_each_segment(bv, req, iter) { | 2388 | rq_for_each_segment(bv, req, iter) { |
2348 | ++ctidaw; | 2389 | ++ctidaw; |
2349 | } | 2390 | } |
2391 | if (rq_data_dir(req) == WRITE) | ||
2392 | ctidaw += (last_trk - first_trk); | ||
2350 | 2393 | ||
2351 | /* Allocate the ccw request. */ | 2394 | /* Allocate the ccw request. */ |
2352 | itcw_size = itcw_calc_size(0, ctidaw, 0); | 2395 | itcw_size = itcw_calc_size(0, ctidaw, 0); |
@@ -2354,15 +2397,6 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( | |||
2354 | if (IS_ERR(cqr)) | 2397 | if (IS_ERR(cqr)) |
2355 | return cqr; | 2398 | return cqr; |
2356 | 2399 | ||
2357 | cqr->cpmode = 1; | ||
2358 | cqr->startdev = startdev; | ||
2359 | cqr->memdev = startdev; | ||
2360 | cqr->block = block; | ||
2361 | cqr->expires = 100*HZ; | ||
2362 | cqr->buildclk = get_clock(); | ||
2363 | cqr->status = DASD_CQR_FILLED; | ||
2364 | cqr->retries = 10; | ||
2365 | |||
2366 | /* transfer length factor: how many bytes to read from the last track */ | 2400 | /* transfer length factor: how many bytes to read from the last track */ |
2367 | if (first_trk == last_trk) | 2401 | if (first_trk == last_trk) |
2368 | tlf = last_offs - first_offs + 1; | 2402 | tlf = last_offs - first_offs + 1; |
@@ -2371,8 +2405,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( | |||
2371 | tlf *= blksize; | 2405 | tlf *= blksize; |
2372 | 2406 | ||
2373 | itcw = itcw_init(cqr->data, itcw_size, itcw_op, 0, ctidaw, 0); | 2407 | itcw = itcw_init(cqr->data, itcw_size, itcw_op, 0, ctidaw, 0); |
2408 | if (IS_ERR(itcw)) { | ||
2409 | dasd_sfree_request(cqr, startdev); | ||
2410 | return ERR_PTR(-EINVAL); | ||
2411 | } | ||
2374 | cqr->cpaddr = itcw_get_tcw(itcw); | 2412 | cqr->cpaddr = itcw_get_tcw(itcw); |
2375 | |||
2376 | if (prepare_itcw(itcw, first_trk, last_trk, | 2413 | if (prepare_itcw(itcw, first_trk, last_trk, |
2377 | cmd, basedev, startdev, | 2414 | cmd, basedev, startdev, |
2378 | first_offs + 1, | 2415 | first_offs + 1, |
@@ -2385,26 +2422,64 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_tpm_track( | |||
2385 | dasd_sfree_request(cqr, startdev); | 2422 | dasd_sfree_request(cqr, startdev); |
2386 | return ERR_PTR(-EAGAIN); | 2423 | return ERR_PTR(-EAGAIN); |
2387 | } | 2424 | } |
2388 | |||
2389 | /* | 2425 | /* |
2390 | * A tidaw can address 4k of memory, but must not cross page boundaries | 2426 | * A tidaw can address 4k of memory, but must not cross page boundaries |
2391 | * We can let the block layer handle this by setting | 2427 | * We can let the block layer handle this by setting |
2392 | * blk_queue_segment_boundary to page boundaries and | 2428 | * blk_queue_segment_boundary to page boundaries and |
2393 | * blk_max_segment_size to page size when setting up the request queue. | 2429 | * blk_max_segment_size to page size when setting up the request queue. |
2430 | * For write requests, a TIDAW must not cross track boundaries, because | ||
2431 | * we have to set the CBC flag on the last tidaw for each track. | ||
2394 | */ | 2432 | */ |
2395 | rq_for_each_segment(bv, req, iter) { | 2433 | if (rq_data_dir(req) == WRITE) { |
2396 | dst = page_address(bv->bv_page) + bv->bv_offset; | 2434 | new_track = 1; |
2397 | last_tidaw = itcw_add_tidaw(itcw, 0x00, dst, bv->bv_len); | 2435 | recid = first_rec; |
2398 | if (IS_ERR(last_tidaw)) | 2436 | rq_for_each_segment(bv, req, iter) { |
2399 | return (struct dasd_ccw_req *)last_tidaw; | 2437 | dst = page_address(bv->bv_page) + bv->bv_offset; |
2438 | seg_len = bv->bv_len; | ||
2439 | while (seg_len) { | ||
2440 | if (new_track) { | ||
2441 | trkid = recid; | ||
2442 | offs = sector_div(trkid, blk_per_trk); | ||
2443 | count_to_trk_end = blk_per_trk - offs; | ||
2444 | count = min((last_rec - recid + 1), | ||
2445 | (sector_t)count_to_trk_end); | ||
2446 | len_to_track_end = count * blksize; | ||
2447 | recid += count; | ||
2448 | new_track = 0; | ||
2449 | } | ||
2450 | part_len = min(seg_len, len_to_track_end); | ||
2451 | seg_len -= part_len; | ||
2452 | len_to_track_end -= part_len; | ||
2453 | /* We need to end the tidaw at track end */ | ||
2454 | if (!len_to_track_end) { | ||
2455 | new_track = 1; | ||
2456 | tidaw_flags = TIDAW_FLAGS_INSERT_CBC; | ||
2457 | } else | ||
2458 | tidaw_flags = 0; | ||
2459 | last_tidaw = itcw_add_tidaw(itcw, tidaw_flags, | ||
2460 | dst, part_len); | ||
2461 | if (IS_ERR(last_tidaw)) | ||
2462 | return ERR_PTR(-EINVAL); | ||
2463 | dst += part_len; | ||
2464 | } | ||
2465 | } | ||
2466 | } else { | ||
2467 | rq_for_each_segment(bv, req, iter) { | ||
2468 | dst = page_address(bv->bv_page) + bv->bv_offset; | ||
2469 | last_tidaw = itcw_add_tidaw(itcw, 0x00, | ||
2470 | dst, bv->bv_len); | ||
2471 | if (IS_ERR(last_tidaw)) | ||
2472 | return ERR_PTR(-EINVAL); | ||
2473 | } | ||
2400 | } | 2474 | } |
2401 | 2475 | last_tidaw->flags |= TIDAW_FLAGS_LAST; | |
2402 | last_tidaw->flags |= 0x80; | 2476 | last_tidaw->flags &= ~TIDAW_FLAGS_INSERT_CBC; |
2403 | itcw_finalize(itcw); | 2477 | itcw_finalize(itcw); |
2404 | 2478 | ||
2405 | if (blk_noretry_request(req) || | 2479 | if (blk_noretry_request(req) || |
2406 | block->base->features & DASD_FEATURE_FAILFAST) | 2480 | block->base->features & DASD_FEATURE_FAILFAST) |
2407 | set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); | 2481 | set_bit(DASD_CQR_FLAGS_FAILFAST, &cqr->flags); |
2482 | cqr->cpmode = 1; | ||
2408 | cqr->startdev = startdev; | 2483 | cqr->startdev = startdev; |
2409 | cqr->memdev = startdev; | 2484 | cqr->memdev = startdev; |
2410 | cqr->block = block; | 2485 | cqr->block = block; |
@@ -2420,11 +2495,9 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, | |||
2420 | struct dasd_block *block, | 2495 | struct dasd_block *block, |
2421 | struct request *req) | 2496 | struct request *req) |
2422 | { | 2497 | { |
2423 | int tpm, cmdrtd, cmdwtd; | 2498 | int cmdrtd, cmdwtd; |
2424 | int use_prefix; | 2499 | int use_prefix; |
2425 | #if defined(CONFIG_64BIT) | 2500 | int fcx_multitrack; |
2426 | int fcx_in_css, fcx_in_gneq, fcx_in_features; | ||
2427 | #endif | ||
2428 | struct dasd_eckd_private *private; | 2501 | struct dasd_eckd_private *private; |
2429 | struct dasd_device *basedev; | 2502 | struct dasd_device *basedev; |
2430 | sector_t first_rec, last_rec; | 2503 | sector_t first_rec, last_rec; |
@@ -2432,6 +2505,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, | |||
2432 | unsigned int first_offs, last_offs; | 2505 | unsigned int first_offs, last_offs; |
2433 | unsigned int blk_per_trk, blksize; | 2506 | unsigned int blk_per_trk, blksize; |
2434 | int cdlspecial; | 2507 | int cdlspecial; |
2508 | unsigned int data_size; | ||
2435 | struct dasd_ccw_req *cqr; | 2509 | struct dasd_ccw_req *cqr; |
2436 | 2510 | ||
2437 | basedev = block->base; | 2511 | basedev = block->base; |
@@ -2450,15 +2524,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, | |||
2450 | last_offs = sector_div(last_trk, blk_per_trk); | 2524 | last_offs = sector_div(last_trk, blk_per_trk); |
2451 | cdlspecial = (private->uses_cdl && first_rec < 2*blk_per_trk); | 2525 | cdlspecial = (private->uses_cdl && first_rec < 2*blk_per_trk); |
2452 | 2526 | ||
2453 | /* is transport mode supported? */ | 2527 | fcx_multitrack = private->features.feature[40] & 0x20; |
2454 | #if defined(CONFIG_64BIT) | 2528 | data_size = blk_rq_bytes(req); |
2455 | fcx_in_css = css_general_characteristics.fcx; | 2529 | /* tpm write request add CBC data on each track boundary */ |
2456 | fcx_in_gneq = private->gneq->reserved2[7] & 0x04; | 2530 | if (rq_data_dir(req) == WRITE) |
2457 | fcx_in_features = private->features.feature[40] & 0x80; | 2531 | data_size += (last_trk - first_trk) * 4; |
2458 | tpm = fcx_in_css && fcx_in_gneq && fcx_in_features; | ||
2459 | #else | ||
2460 | tpm = 0; | ||
2461 | #endif | ||
2462 | 2532 | ||
2463 | /* is read track data and write track data in command mode supported? */ | 2533 | /* is read track data and write track data in command mode supported? */ |
2464 | cmdrtd = private->features.feature[9] & 0x20; | 2534 | cmdrtd = private->features.feature[9] & 0x20; |
@@ -2468,13 +2538,15 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, | |||
2468 | cqr = NULL; | 2538 | cqr = NULL; |
2469 | if (cdlspecial || dasd_page_cache) { | 2539 | if (cdlspecial || dasd_page_cache) { |
2470 | /* do nothing, just fall through to the cmd mode single case */ | 2540 | /* do nothing, just fall through to the cmd mode single case */ |
2471 | } else if (!dasd_nofcx && tpm && (first_trk == last_trk)) { | 2541 | } else if ((data_size <= private->fcx_max_data) |
2542 | && (fcx_multitrack || (first_trk == last_trk))) { | ||
2472 | cqr = dasd_eckd_build_cp_tpm_track(startdev, block, req, | 2543 | cqr = dasd_eckd_build_cp_tpm_track(startdev, block, req, |
2473 | first_rec, last_rec, | 2544 | first_rec, last_rec, |
2474 | first_trk, last_trk, | 2545 | first_trk, last_trk, |
2475 | first_offs, last_offs, | 2546 | first_offs, last_offs, |
2476 | blk_per_trk, blksize); | 2547 | blk_per_trk, blksize); |
2477 | if (IS_ERR(cqr) && PTR_ERR(cqr) != -EAGAIN) | 2548 | if (IS_ERR(cqr) && (PTR_ERR(cqr) != -EAGAIN) && |
2549 | (PTR_ERR(cqr) != -ENOMEM)) | ||
2478 | cqr = NULL; | 2550 | cqr = NULL; |
2479 | } else if (use_prefix && | 2551 | } else if (use_prefix && |
2480 | (((rq_data_dir(req) == READ) && cmdrtd) || | 2552 | (((rq_data_dir(req) == READ) && cmdrtd) || |
@@ -2484,7 +2556,8 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev, | |||
2484 | first_trk, last_trk, | 2556 | first_trk, last_trk, |
2485 | first_offs, last_offs, | 2557 | first_offs, last_offs, |
2486 | blk_per_trk, blksize); | 2558 | blk_per_trk, blksize); |
2487 | if (IS_ERR(cqr) && PTR_ERR(cqr) != -EAGAIN) | 2559 | if (IS_ERR(cqr) && (PTR_ERR(cqr) != -EAGAIN) && |
2560 | (PTR_ERR(cqr) != -ENOMEM)) | ||
2488 | cqr = NULL; | 2561 | cqr = NULL; |
2489 | } | 2562 | } |
2490 | if (!cqr) | 2563 | if (!cqr) |
@@ -3279,10 +3352,8 @@ static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, | |||
3279 | { | 3352 | { |
3280 | char *page; | 3353 | char *page; |
3281 | int len, sl, sct, residual; | 3354 | int len, sl, sct, residual; |
3282 | |||
3283 | struct tsb *tsb; | 3355 | struct tsb *tsb; |
3284 | u8 *sense; | 3356 | u8 *sense, *rcq; |
3285 | |||
3286 | 3357 | ||
3287 | page = (char *) get_zeroed_page(GFP_ATOMIC); | 3358 | page = (char *) get_zeroed_page(GFP_ATOMIC); |
3288 | if (page == NULL) { | 3359 | if (page == NULL) { |
@@ -3348,12 +3419,15 @@ static void dasd_eckd_dump_sense_tcw(struct dasd_device *device, | |||
3348 | case 2: /* ts_ddpc */ | 3419 | case 2: /* ts_ddpc */ |
3349 | len += sprintf(page + len, KERN_ERR PRINTK_HEADER | 3420 | len += sprintf(page + len, KERN_ERR PRINTK_HEADER |
3350 | " tsb->tsa.ddpc.rc %d\n", tsb->tsa.ddpc.rc); | 3421 | " tsb->tsa.ddpc.rc %d\n", tsb->tsa.ddpc.rc); |
3351 | len += sprintf(page + len, KERN_ERR PRINTK_HEADER | 3422 | for (sl = 0; sl < 2; sl++) { |
3352 | " tsb->tsa.ddpc.rcq: "); | 3423 | len += sprintf(page + len, |
3353 | for (sl = 0; sl < 16; sl++) { | 3424 | KERN_ERR PRINTK_HEADER |
3425 | " tsb->tsa.ddpc.rcq %2d-%2d: ", | ||
3426 | (8 * sl), ((8 * sl) + 7)); | ||
3427 | rcq = tsb->tsa.ddpc.rcq; | ||
3354 | for (sct = 0; sct < 8; sct++) { | 3428 | for (sct = 0; sct < 8; sct++) { |
3355 | len += sprintf(page + len, " %02x", | 3429 | len += sprintf(page + len, " %02x", |
3356 | tsb->tsa.ddpc.rcq[sl]); | 3430 | rcq[8 * sl + sct]); |
3357 | } | 3431 | } |
3358 | len += sprintf(page + len, "\n"); | 3432 | len += sprintf(page + len, "\n"); |
3359 | } | 3433 | } |
@@ -3573,7 +3647,7 @@ static struct dasd_discipline dasd_eckd_discipline = { | |||
3573 | .owner = THIS_MODULE, | 3647 | .owner = THIS_MODULE, |
3574 | .name = "ECKD", | 3648 | .name = "ECKD", |
3575 | .ebcname = "ECKD", | 3649 | .ebcname = "ECKD", |
3576 | .max_blocks = 240, | 3650 | .max_blocks = 190, |
3577 | .check_device = dasd_eckd_check_characteristics, | 3651 | .check_device = dasd_eckd_check_characteristics, |
3578 | .uncheck_device = dasd_eckd_uncheck_device, | 3652 | .uncheck_device = dasd_eckd_uncheck_device, |
3579 | .do_analysis = dasd_eckd_do_analysis, | 3653 | .do_analysis = dasd_eckd_do_analysis, |