From 7e64db1597fe114b83fe17d0ba96c6aa5fca419a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20H=C3=B6ppner?= <hoeppner@linux.ibm.com>
Date: Fri, 27 Apr 2018 16:57:44 +0200
Subject: [PATCH] s390/dasd: Add discard support for ESE volumes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

ESE (Extent Space Efficient) volumes are thin-provisioned and therefore
space is only occupied with real data. In order to make previously used
space available for re-allocation again, discard support is enabled for
ESE volumes allowing the DASD driver to release said space.

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
---
 drivers/s390/block/dasd_eckd.c | 57 ++++++++++++++++++++++++++++++++--
 1 file changed, 54 insertions(+), 3 deletions(-)

diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c
index 3edb35359843e..7f7429a99a671 100644
--- a/drivers/s390/block/dasd_eckd.c
+++ b/drivers/s390/block/dasd_eckd.c
@@ -1987,6 +1987,9 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
 	if (readonly)
 		set_bit(DASD_FLAG_DEVICE_RO, &device->flags);
 
+	if (dasd_eckd_is_ese(device))
+		dasd_set_feature(device->cdev, DASD_FEATURE_DISCARD, 1);
+
 	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,
@@ -3617,6 +3620,14 @@ static int dasd_eckd_release_space(struct dasd_device *device,
 		return -EINVAL;
 }
 
+static struct dasd_ccw_req *
+dasd_eckd_build_cp_discard(struct dasd_device *device, struct dasd_block *block,
+			   struct request *req, sector_t first_trk,
+			   sector_t last_trk)
+{
+	return dasd_eckd_dso_ras(device, block, req, first_trk, last_trk, 1);
+}
+
 static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single(
 					       struct dasd_device *startdev,
 					       struct dasd_block *block,
@@ -4361,6 +4372,10 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev,
 	cmdwtd = private->features.feature[12] & 0x40;
 	use_prefix = private->features.feature[8] & 0x01;
 
+	if (req_op(req) == REQ_OP_DISCARD)
+		return dasd_eckd_build_cp_discard(startdev, block, req,
+						  first_trk, last_trk);
+
 	cqr = NULL;
 	if (cdlspecial || dasd_page_cache) {
 		/* do nothing, just fall through to the cmd mode single case */
@@ -4639,12 +4654,14 @@ static struct dasd_ccw_req *dasd_eckd_build_alias_cp(struct dasd_device *base,
 						     struct dasd_block *block,
 						     struct request *req)
 {
+	struct dasd_device *startdev = NULL;
 	struct dasd_eckd_private *private;
-	struct dasd_device *startdev;
-	unsigned long flags;
 	struct dasd_ccw_req *cqr;
+	unsigned long flags;
 
-	startdev = dasd_alias_get_start_dev(base);
+	/* Discard requests can only be processed on base devices */
+	if (req_op(req) != REQ_OP_DISCARD)
+		startdev = dasd_alias_get_start_dev(base);
 	if (!startdev)
 		startdev = base;
 	private = startdev->private;
@@ -6357,8 +6374,20 @@ static void dasd_eckd_setup_blk_queue(struct dasd_block *block)
 	unsigned int logical_block_size = block->bp_block;
 	struct request_queue *q = block->request_queue;
 	struct dasd_device *device = block->base;
+	struct dasd_eckd_private *private;
+	unsigned int max_discard_sectors;
+	unsigned int max_bytes;
+	unsigned int ext_bytes; /* Extent Size in Bytes */
+	int recs_per_trk;
+	int trks_per_cyl;
+	int ext_limit;
+	int ext_size; /* Extent Size in Cylinders */
 	int max;
 
+	private = device->private;
+	trks_per_cyl = private->rdc_data.trk_per_cyl;
+	recs_per_trk = recs_per_track(&private->rdc_data, 0, logical_block_size);
+
 	if (device->features & DASD_FEATURE_USERAW) {
 		/*
 		 * the max_blocks value for raw_track access is 256
@@ -6379,6 +6408,28 @@ static void dasd_eckd_setup_blk_queue(struct dasd_block *block)
 	/* With page sized segments each segment can be translated into one idaw/tidaw */
 	blk_queue_max_segment_size(q, PAGE_SIZE);
 	blk_queue_segment_boundary(q, PAGE_SIZE - 1);
+
+	if (dasd_eckd_is_ese(device)) {
+		/*
+		 * Depending on the extent size, up to UINT_MAX bytes can be
+		 * accepted. However, neither DASD_ECKD_RAS_EXTS_MAX nor the
+		 * device limits should be exceeded.
+		 */
+		ext_size = dasd_eckd_ext_size(device);
+		ext_limit = min(private->real_cyl / ext_size, DASD_ECKD_RAS_EXTS_MAX);
+		ext_bytes = ext_size * trks_per_cyl * recs_per_trk *
+			logical_block_size;
+		max_bytes = UINT_MAX - (UINT_MAX % ext_bytes);
+		if (max_bytes / ext_bytes > ext_limit)
+			max_bytes = ext_bytes * ext_limit;
+
+		max_discard_sectors = max_bytes / 512;
+
+		blk_queue_max_discard_sectors(q, max_discard_sectors);
+		blk_queue_flag_set(QUEUE_FLAG_DISCARD, q);
+		q->limits.discard_granularity = ext_bytes;
+		q->limits.discard_alignment = ext_bytes;
+	}
 }
 
 static struct ccw_driver dasd_eckd_driver = {
-- 
GitLab