aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/s390/include/uapi/asm/dasd.h3
-rw-r--r--drivers/s390/block/dasd_eckd.c264
-rw-r--r--drivers/s390/block/dasd_eckd.h40
-rw-r--r--drivers/s390/block/dasd_int.h1
-rw-r--r--drivers/s390/block/dasd_ioctl.c56
5 files changed, 364 insertions, 0 deletions
diff --git a/arch/s390/include/uapi/asm/dasd.h b/arch/s390/include/uapi/asm/dasd.h
index 48a2f475b90a..9ec86fae9980 100644
--- a/arch/s390/include/uapi/asm/dasd.h
+++ b/arch/s390/include/uapi/asm/dasd.h
@@ -194,6 +194,7 @@ typedef struct format_data_t {
#define DASD_FMT_INT_INVAL 4 /* invalidate tracks */
#define DASD_FMT_INT_COMPAT 8 /* use OS/390 compatible disk layout */
#define DASD_FMT_INT_FMT_NOR0 16 /* remove permission to write record zero */
+#define DASD_FMT_INT_ESE_FULL 32 /* release space for entire volume */
/*
* struct format_check_t
@@ -323,6 +324,8 @@ struct dasd_snid_ioctl_data {
#define BIODASDFMT _IOW(DASD_IOCTL_LETTER,1,format_data_t)
/* Set Attributes (cache operations) */
#define BIODASDSATTR _IOW(DASD_IOCTL_LETTER,2,attrib_data_t)
+/* Release Allocated Space */
+#define BIODASDRAS _IOW(DASD_IOCTL_LETTER, 3, format_data_t)
/* Get Sense Path Group ID (SNID) data */
#define BIODASDSNID _IOWR(DASD_IOCTL_LETTER, 1, struct dasd_snid_ioctl_data)
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c
index 6109a0e68911..21164a48317d 100644
--- a/drivers/s390/block/dasd_eckd.c
+++ b/drivers/s390/block/dasd_eckd.c
@@ -3354,6 +3354,269 @@ static void dasd_eckd_check_for_device_change(struct dasd_device *device,
}
}
+static int dasd_eckd_ras_sanity_checks(struct dasd_device *device,
+ unsigned int first_trk,
+ unsigned int last_trk)
+{
+ struct dasd_eckd_private *private = device->private;
+ unsigned int trks_per_vol;
+ int rc = 0;
+
+ trks_per_vol = private->real_cyl * private->rdc_data.trk_per_cyl;
+
+ if (first_trk >= trks_per_vol) {
+ dev_warn(&device->cdev->dev,
+ "Start track number %u used in the space release command is too big\n",
+ first_trk);
+ rc = -EINVAL;
+ } else if (last_trk >= trks_per_vol) {
+ dev_warn(&device->cdev->dev,
+ "Stop track number %u used in the space release command is too big\n",
+ last_trk);
+ rc = -EINVAL;
+ } else if (first_trk > last_trk) {
+ dev_warn(&device->cdev->dev,
+ "Start track %u used in the space release command exceeds the end track\n",
+ first_trk);
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+/*
+ * Helper function to count the amount of involved extents within a given range
+ * with extent alignment in mind.
+ */
+static int count_exts(unsigned int from, unsigned int to, int trks_per_ext)
+{
+ int cur_pos = 0;
+ int count = 0;
+ int tmp;
+
+ if (from == to)
+ return 1;
+
+ /* Count first partial extent */
+ if (from % trks_per_ext != 0) {
+ tmp = from + trks_per_ext - (from % trks_per_ext) - 1;
+ if (tmp > to)
+ tmp = to;
+ cur_pos = tmp - from + 1;
+ count++;
+ }
+ /* Count full extents */
+ if (to - (from + cur_pos) + 1 >= trks_per_ext) {
+ tmp = to - ((to - trks_per_ext + 1) % trks_per_ext);
+ count += (tmp - (from + cur_pos) + 1) / trks_per_ext;
+ cur_pos = tmp;
+ }
+ /* Count last partial extent */
+ if (cur_pos < to)
+ count++;
+
+ return count;
+}
+
+/*
+ * Release allocated space for a given range or an entire volume.
+ */
+static struct dasd_ccw_req *
+dasd_eckd_dso_ras(struct dasd_device *device, struct dasd_block *block,
+ struct request *req, unsigned int first_trk,
+ unsigned int last_trk, int by_extent)
+{
+ struct dasd_eckd_private *private = device->private;
+ struct dasd_dso_ras_ext_range *ras_range;
+ struct dasd_rssd_features *features;
+ struct dasd_dso_ras_data *ras_data;
+ u16 heads, beg_head, end_head;
+ int cur_to_trk, cur_from_trk;
+ struct dasd_ccw_req *cqr;
+ u32 beg_cyl, end_cyl;
+ struct ccw1 *ccw;
+ int trks_per_ext;
+ size_t ras_size;
+ size_t size;
+ int nr_exts;
+ void *rq;
+ int i;
+
+ if (dasd_eckd_ras_sanity_checks(device, first_trk, last_trk))
+ return ERR_PTR(-EINVAL);
+
+ rq = req ? blk_mq_rq_to_pdu(req) : NULL;
+
+ features = &private->features;
+
+ trks_per_ext = dasd_eckd_ext_size(device) * private->rdc_data.trk_per_cyl;
+ nr_exts = 0;
+ if (by_extent)
+ nr_exts = count_exts(first_trk, last_trk, trks_per_ext);
+ ras_size = sizeof(*ras_data);
+ size = ras_size + (nr_exts * sizeof(*ras_range));
+
+ cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1, size, device, rq);
+ if (IS_ERR(cqr)) {
+ DBF_EVENT_DEVID(DBF_WARNING, device->cdev, "%s",
+ "Could not allocate RAS request");
+ return cqr;
+ }
+
+ ras_data = cqr->data;
+ memset(ras_data, 0, size);
+
+ ras_data->order = DSO_ORDER_RAS;
+ ras_data->flags.vol_type = 0; /* CKD volume */
+ /* Release specified extents or entire volume */
+ ras_data->op_flags.by_extent = by_extent;
+ /*
+ * This bit guarantees initialisation of tracks within an extent that is
+ * not fully specified, but is only supported with a certain feature
+ * subset.
+ */
+ ras_data->op_flags.guarantee_init = !!(features->feature[56] & 0x01);
+ ras_data->lss = private->ned->ID;
+ ras_data->dev_addr = private->ned->unit_addr;
+ ras_data->nr_exts = nr_exts;
+
+ if (by_extent) {
+ heads = private->rdc_data.trk_per_cyl;
+ cur_from_trk = first_trk;
+ cur_to_trk = first_trk + trks_per_ext -
+ (first_trk % trks_per_ext) - 1;
+ if (cur_to_trk > last_trk)
+ cur_to_trk = last_trk;
+ ras_range = (struct dasd_dso_ras_ext_range *)(cqr->data + ras_size);
+
+ for (i = 0; i < nr_exts; i++) {
+ beg_cyl = cur_from_trk / heads;
+ beg_head = cur_from_trk % heads;
+ end_cyl = cur_to_trk / heads;
+ end_head = cur_to_trk % heads;
+
+ set_ch_t(&ras_range->beg_ext, beg_cyl, beg_head);
+ set_ch_t(&ras_range->end_ext, end_cyl, end_head);
+
+ cur_from_trk = cur_to_trk + 1;
+ cur_to_trk = cur_from_trk + trks_per_ext - 1;
+ if (cur_to_trk > last_trk)
+ cur_to_trk = last_trk;
+ ras_range++;
+ }
+ }
+
+ ccw = cqr->cpaddr;
+ ccw->cda = (__u32)(addr_t)cqr->data;
+ ccw->cmd_code = DASD_ECKD_CCW_DSO;
+ ccw->count = size;
+
+ cqr->startdev = device;
+ cqr->memdev = device;
+ cqr->block = block;
+ cqr->retries = 256;
+ cqr->expires = device->default_expires * HZ;
+ cqr->buildclk = get_tod_clock();
+ cqr->status = DASD_CQR_FILLED;
+
+ return cqr;
+}
+
+static int dasd_eckd_release_space_full(struct dasd_device *device)
+{
+ struct dasd_ccw_req *cqr;
+ int rc;
+
+ cqr = dasd_eckd_dso_ras(device, NULL, NULL, 0, 0, 0);
+ if (IS_ERR(cqr))
+ return PTR_ERR(cqr);
+
+ rc = dasd_sleep_on_interruptible(cqr);
+
+ dasd_sfree_request(cqr, cqr->memdev);
+
+ return rc;
+}
+
+static int dasd_eckd_release_space_trks(struct dasd_device *device,
+ unsigned int from, unsigned int to)
+{
+ struct dasd_eckd_private *private = device->private;
+ struct dasd_block *block = device->block;
+ struct dasd_ccw_req *cqr, *n;
+ struct list_head ras_queue;
+ unsigned int device_exts;
+ int trks_per_ext;
+ int stop, step;
+ int cur_pos;
+ int rc = 0;
+ int retry;
+
+ INIT_LIST_HEAD(&ras_queue);
+
+ device_exts = private->real_cyl / dasd_eckd_ext_size(device);
+ trks_per_ext = dasd_eckd_ext_size(device) * private->rdc_data.trk_per_cyl;
+
+ /* Make sure device limits are not exceeded */
+ step = trks_per_ext * min(device_exts, DASD_ECKD_RAS_EXTS_MAX);
+ cur_pos = from;
+
+ do {
+ retry = 0;
+ while (cur_pos < to) {
+ stop = cur_pos + step -
+ ((cur_pos + step) % trks_per_ext) - 1;
+ if (stop > to)
+ stop = to;
+
+ cqr = dasd_eckd_dso_ras(device, NULL, NULL, cur_pos, stop, 1);
+ if (IS_ERR(cqr)) {
+ rc = PTR_ERR(cqr);
+ if (rc == -ENOMEM) {
+ if (list_empty(&ras_queue))
+ goto out;
+ retry = 1;
+ break;
+ }
+ goto err_out;
+ }
+
+ spin_lock_irq(&block->queue_lock);
+ list_add_tail(&cqr->blocklist, &ras_queue);
+ spin_unlock_irq(&block->queue_lock);
+ cur_pos = stop + 1;
+ }
+
+ rc = dasd_sleep_on_queue_interruptible(&ras_queue);
+
+err_out:
+ list_for_each_entry_safe(cqr, n, &ras_queue, blocklist) {
+ device = cqr->startdev;
+ private = device->private;
+
+ spin_lock_irq(&block->queue_lock);
+ list_del_init(&cqr->blocklist);
+ spin_unlock_irq(&block->queue_lock);
+ dasd_sfree_request(cqr, device);
+ private->count--;
+ }
+ } while (retry);
+
+out:
+ return rc;
+}
+
+static int dasd_eckd_release_space(struct dasd_device *device,
+ struct format_data_t *rdata)
+{
+ if (rdata->intensity & DASD_FMT_INT_ESE_FULL)
+ return dasd_eckd_release_space_full(device);
+ else if (rdata->intensity == 0)
+ return dasd_eckd_release_space_trks(device, rdata->start_unit,
+ rdata->stop_unit);
+ else
+ return -EINVAL;
+}
+
static struct dasd_ccw_req *dasd_eckd_build_cp_cmd_single(
struct dasd_device *startdev,
struct dasd_block *block,
@@ -6162,6 +6425,7 @@ static struct dasd_discipline dasd_eckd_discipline = {
.space_allocated = dasd_eckd_space_allocated,
.space_configured = dasd_eckd_space_configured,
.logical_capacity = dasd_eckd_logical_capacity,
+ .release_space = dasd_eckd_release_space,
.ext_pool_id = dasd_eckd_ext_pool_id,
.ext_size = dasd_eckd_ext_size,
.ext_pool_cap_at_warnlevel = dasd_eckd_ext_pool_cap_at_warnlevel,
diff --git a/drivers/s390/block/dasd_eckd.h b/drivers/s390/block/dasd_eckd.h
index bc5998068ddf..4226936427ec 100644
--- a/drivers/s390/block/dasd_eckd.h
+++ b/drivers/s390/block/dasd_eckd.h
@@ -50,6 +50,10 @@
#define DASD_ECKD_CCW_PFX_READ 0xEA
#define DASD_ECKD_CCW_RSCK 0xF9
#define DASD_ECKD_CCW_RCD 0xFA
+#define DASD_ECKD_CCW_DSO 0xF7
+
+/* Define Subssystem Function / Orders */
+#define DSO_ORDER_RAS 0x81
/*
* Perform Subsystem Function / Orders
@@ -513,6 +517,42 @@ struct dasd_psf_ssc_data {
unsigned char reserved[59];
} __attribute__((packed));
+/* Maximum number of extents for a single Release Allocated Space command */
+#define DASD_ECKD_RAS_EXTS_MAX 110U
+
+struct dasd_dso_ras_ext_range {
+ struct ch_t beg_ext;
+ struct ch_t end_ext;
+} __packed;
+
+/*
+ * Define Subsytem Operation - Release Allocated Space
+ */
+struct dasd_dso_ras_data {
+ __u8 order;
+ struct {
+ __u8 message:1; /* Must be zero */
+ __u8 reserved1:2;
+ __u8 vol_type:1; /* 0 - CKD/FBA, 1 - FB */
+ __u8 reserved2:4;
+ } __packed flags;
+ /* Operation Flags to specify scope */
+ struct {
+ __u8 reserved1:2;
+ /* Release Space by Extent */
+ __u8 by_extent:1; /* 0 - entire volume, 1 - specified extents */
+ __u8 guarantee_init:1;
+ __u8 force_release:1; /* Internal - will be ignored */
+ __u16 reserved2:11;
+ } __packed op_flags;
+ __u8 lss;
+ __u8 dev_addr;
+ __u32 reserved1;
+ __u8 reserved2[10];
+ __u16 nr_exts; /* Defines number of ext_scope - max 110 */
+ __u16 reserved3;
+} __packed;
+
/*
* some structures and definitions for alias handling
diff --git a/drivers/s390/block/dasd_int.h b/drivers/s390/block/dasd_int.h
index 9a8ef372535b..7fe0c6b9d9ef 100644
--- a/drivers/s390/block/dasd_int.h
+++ b/drivers/s390/block/dasd_int.h
@@ -376,6 +376,7 @@ struct dasd_discipline {
int (*space_allocated)(struct dasd_device *);
int (*space_configured)(struct dasd_device *);
int (*logical_capacity)(struct dasd_device *);
+ int (*release_space)(struct dasd_device *, struct format_data_t *);
/* Extent Pool */
int (*ext_pool_id)(struct dasd_device *);
int (*ext_size)(struct dasd_device *);
diff --git a/drivers/s390/block/dasd_ioctl.c b/drivers/s390/block/dasd_ioctl.c
index 8e26001dc11c..9a5f3add325f 100644
--- a/drivers/s390/block/dasd_ioctl.c
+++ b/drivers/s390/block/dasd_ioctl.c
@@ -333,6 +333,59 @@ out_err:
return rc;
}
+static int dasd_release_space(struct dasd_device *device,
+ struct format_data_t *rdata)
+{
+ if (!device->discipline->is_ese && !device->discipline->is_ese(device))
+ return -ENOTSUPP;
+ if (!device->discipline->release_space)
+ return -ENOTSUPP;
+
+ return device->discipline->release_space(device, rdata);
+}
+
+/*
+ * Release allocated space
+ */
+static int dasd_ioctl_release_space(struct block_device *bdev, void __user *argp)
+{
+ struct format_data_t rdata;
+ struct dasd_device *base;
+ int rc = 0;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+ if (!argp)
+ return -EINVAL;
+
+ base = dasd_device_from_gendisk(bdev->bd_disk);
+ if (!base)
+ return -ENODEV;
+ if (base->features & DASD_FEATURE_READONLY ||
+ test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) {
+ rc = -EROFS;
+ goto out_err;
+ }
+ if (bdev != bdev->bd_contains) {
+ pr_warn("%s: The specified DASD is a partition and tracks cannot be released\n",
+ dev_name(&base->cdev->dev));
+ rc = -EINVAL;
+ goto out_err;
+ }
+
+ if (copy_from_user(&rdata, argp, sizeof(rdata))) {
+ rc = -EFAULT;
+ goto out_err;
+ }
+
+ rc = dasd_release_space(base, &rdata);
+
+out_err:
+ dasd_put_device(base);
+
+ return rc;
+}
+
#ifdef CONFIG_DASD_PROFILE
/*
* Reset device profile information
@@ -595,6 +648,9 @@ int dasd_ioctl(struct block_device *bdev, fmode_t mode,
case BIODASDREADALLCMB:
rc = dasd_ioctl_readall_cmb(block, cmd, argp);
break;
+ case BIODASDRAS:
+ rc = dasd_ioctl_release_space(bdev, argp);
+ break;
default:
/* if the discipline has an ioctl method try it. */
rc = -ENOTTY;