Skip to content

Commit

Permalink
[SCSI] sd: WRITE SAME(16) / UNMAP support
Browse files Browse the repository at this point in the history
Implement a function for handling discard requests that sends either
WRITE SAME(16) or UNMAP(10) depending on parameters indicated by the
device in the block limits VPD.

Extract unmap constraints and report them to the block layer.

Based in part by a patch by Christoph Hellwig <hch@lst.de>.

Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Signed-off-by: James Bottomley <James.Bottomley@suse.de>
  • Loading branch information
Martin K. Petersen authored and James Bottomley committed Dec 10, 2009
1 parent cc9b2e9 commit e339c1a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
107 changes: 107 additions & 0 deletions drivers/scsi/sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,15 @@ sd_show_app_tag_own(struct device *dev, struct device_attribute *attr,
return snprintf(buf, 20, "%u\n", sdkp->ATO);
}

static ssize_t
sd_show_thin_provisioning(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct scsi_disk *sdkp = to_scsi_disk(dev);

return snprintf(buf, 20, "%u\n", sdkp->thin_provisioning);
}

static struct device_attribute sd_disk_attrs[] = {
__ATTR(cache_type, S_IRUGO|S_IWUSR, sd_show_cache_type,
sd_store_cache_type),
Expand All @@ -274,6 +283,7 @@ static struct device_attribute sd_disk_attrs[] = {
sd_store_manage_start_stop),
__ATTR(protection_type, S_IRUGO, sd_show_protection_type, NULL),
__ATTR(app_tag_own, S_IRUGO, sd_show_app_tag_own, NULL),
__ATTR(thin_provisioning, S_IRUGO, sd_show_thin_provisioning, NULL),
__ATTR_NULL,
};

Expand Down Expand Up @@ -398,6 +408,57 @@ static void sd_prot_op(struct scsi_cmnd *scmd, unsigned int dif)
scsi_set_prot_type(scmd, dif);
}

/**
* sd_prepare_discard - unmap blocks on thinly provisioned device
* @rq: Request to prepare
*
* Will issue either UNMAP or WRITE SAME(16) depending on preference
* indicated by target device.
**/
static int sd_prepare_discard(struct request *rq)
{
struct scsi_disk *sdkp = scsi_disk(rq->rq_disk);
struct bio *bio = rq->bio;
sector_t sector = bio->bi_sector;
unsigned int num = bio_sectors(bio);

if (sdkp->device->sector_size == 4096) {
sector >>= 3;
num >>= 3;
}

rq->cmd_type = REQ_TYPE_BLOCK_PC;
rq->timeout = SD_TIMEOUT;

memset(rq->cmd, 0, rq->cmd_len);

if (sdkp->unmap) {
char *buf = kmap_atomic(bio_page(bio), KM_USER0);

rq->cmd[0] = UNMAP;
rq->cmd[8] = 24;
rq->cmd_len = 10;

/* Ensure that data length matches payload */
rq->__data_len = bio->bi_size = bio->bi_io_vec->bv_len = 24;

put_unaligned_be16(6 + 16, &buf[0]);
put_unaligned_be16(16, &buf[2]);
put_unaligned_be64(sector, &buf[8]);
put_unaligned_be32(num, &buf[16]);

kunmap_atomic(buf, KM_USER0);
} else {
rq->cmd[0] = WRITE_SAME_16;
rq->cmd[1] = 0x8; /* UNMAP */
put_unaligned_be64(sector, &rq->cmd[2]);
put_unaligned_be32(num, &rq->cmd[10]);
rq->cmd_len = 16;
}

return BLKPREP_OK;
}

/**
* sd_init_command - build a scsi (read or write) command from
* information in the request structure.
Expand All @@ -418,6 +479,13 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq)
int ret, host_dif;
unsigned char protect;

/*
* Discard request come in as REQ_TYPE_FS but we turn them into
* block PC requests to make life easier.
*/
if (blk_discard_rq(rq))
ret = sd_prepare_discard(rq);

if (rq->cmd_type == REQ_TYPE_BLOCK_PC) {
ret = scsi_setup_blk_pc_cmnd(sdp, rq);
goto out;
Expand Down Expand Up @@ -1432,6 +1500,19 @@ static int read_capacity_16(struct scsi_disk *sdkp, struct scsi_device *sdp,
sd_printk(KERN_NOTICE, sdkp,
"physical block alignment offset: %u\n", alignment);

if (buffer[14] & 0x80) { /* TPE */
struct request_queue *q = sdp->request_queue;

sdkp->thin_provisioning = 1;
q->limits.discard_granularity = sdkp->hw_sector_size;
q->limits.max_discard_sectors = 0xffffffff;

if (buffer[14] & 0x40) /* TPRZ */
q->limits.discard_zeroes_data = 1;

queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q);
}

sdkp->capacity = lba + 1;
return sector_size;
}
Expand Down Expand Up @@ -1863,6 +1944,7 @@ void sd_read_app_tag_own(struct scsi_disk *sdkp, unsigned char *buffer)
*/
static void sd_read_block_limits(struct scsi_disk *sdkp)
{
struct request_queue *q = sdkp->disk->queue;
unsigned int sector_sz = sdkp->device->sector_size;
char *buffer;

Expand All @@ -1877,6 +1959,31 @@ static void sd_read_block_limits(struct scsi_disk *sdkp)
blk_queue_io_opt(sdkp->disk->queue,
get_unaligned_be32(&buffer[12]) * sector_sz);

/* Thin provisioning enabled and page length indicates TP support */
if (sdkp->thin_provisioning && buffer[3] == 0x3c) {
unsigned int lba_count, desc_count, granularity;

lba_count = get_unaligned_be32(&buffer[20]);
desc_count = get_unaligned_be32(&buffer[24]);

if (lba_count) {
q->limits.max_discard_sectors =
lba_count * sector_sz >> 9;

if (desc_count)
sdkp->unmap = 1;
}

granularity = get_unaligned_be32(&buffer[28]);

if (granularity)
q->limits.discard_granularity = granularity * sector_sz;

if (buffer[32] & 0x80)
q->limits.discard_alignment =
get_unaligned_be32(&buffer[32]) & ~(1 << 31);
}

kfree(buffer);
}

Expand Down
2 changes: 2 additions & 0 deletions drivers/scsi/sd.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ struct scsi_disk {
unsigned RCD : 1; /* state of disk RCD bit, unused */
unsigned DPOFUA : 1; /* state of disk DPOFUA bit */
unsigned first_scan : 1;
unsigned thin_provisioning : 1;
unsigned unmap : 1;
};
#define to_scsi_disk(obj) container_of(obj,struct scsi_disk,dev)

Expand Down

0 comments on commit e339c1a

Please sign in to comment.