Skip to content

Commit

Permalink
s390/dasd: Add new ioctl to release space
Browse files Browse the repository at this point in the history
Userspace tools might have the need to release space for Extent Space
Efficient (ESE) volumes when working with such a device.

Provide the necessarry interface for such a task by implementing a new
ioctl BIODASDRAS. The ioctl uses the format_data_t data structure for
data input:

typedef struct format_data_t {
        unsigned int start_unit;        /* from track */
        unsigned int stop_unit;         /* to track */
        unsigned int blksize;           /* sectorsize */
        unsigned int intensity;
} format_data_t;

If the intensity is set to 0x40, start_unit and stop_unit are ignored
and space for the entire volume is released. Otherwise, if intensity is
set to 0, the respective range is released (if possible).

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>
  • Loading branch information
Jan Höppner authored and Vasily Gorbik committed Jul 11, 2019
1 parent bcf3676 commit 91dc4a1
Show file tree
Hide file tree
Showing 5 changed files with 364 additions and 0 deletions.
3 changes: 3 additions & 0 deletions arch/s390/include/uapi/asm/dasd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
264 changes: 264 additions & 0 deletions drivers/s390/block/dasd_eckd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions drivers/s390/block/dasd_eckd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions drivers/s390/block/dasd_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down
Loading

0 comments on commit 91dc4a1

Please sign in to comment.