Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/roland...
[pandora-kernel.git] / drivers / scsi / sd.c
index e567302..b61ebec 100644 (file)
@@ -96,6 +96,7 @@ MODULE_ALIAS_SCSI_DEVICE(TYPE_RBC);
 #define SD_MINORS      0
 #endif
 
+static void sd_config_discard(struct scsi_disk *, unsigned int);
 static int  sd_revalidate_disk(struct gendisk *);
 static void sd_unlock_native_capacity(struct gendisk *disk);
 static int  sd_probe(struct device *);
@@ -294,7 +295,54 @@ sd_show_thin_provisioning(struct device *dev, struct device_attribute *attr,
 {
        struct scsi_disk *sdkp = to_scsi_disk(dev);
 
-       return snprintf(buf, 20, "%u\n", sdkp->thin_provisioning);
+       return snprintf(buf, 20, "%u\n", sdkp->lbpme);
+}
+
+static const char *lbp_mode[] = {
+       [SD_LBP_FULL]           = "full",
+       [SD_LBP_UNMAP]          = "unmap",
+       [SD_LBP_WS16]           = "writesame_16",
+       [SD_LBP_WS10]           = "writesame_10",
+       [SD_LBP_ZERO]           = "writesame_zero",
+       [SD_LBP_DISABLE]        = "disabled",
+};
+
+static ssize_t
+sd_show_provisioning_mode(struct device *dev, struct device_attribute *attr,
+                         char *buf)
+{
+       struct scsi_disk *sdkp = to_scsi_disk(dev);
+
+       return snprintf(buf, 20, "%s\n", lbp_mode[sdkp->provisioning_mode]);
+}
+
+static ssize_t
+sd_store_provisioning_mode(struct device *dev, struct device_attribute *attr,
+                          const char *buf, size_t count)
+{
+       struct scsi_disk *sdkp = to_scsi_disk(dev);
+       struct scsi_device *sdp = sdkp->device;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EACCES;
+
+       if (sdp->type != TYPE_DISK)
+               return -EINVAL;
+
+       if (!strncmp(buf, lbp_mode[SD_LBP_UNMAP], 20))
+               sd_config_discard(sdkp, SD_LBP_UNMAP);
+       else if (!strncmp(buf, lbp_mode[SD_LBP_WS16], 20))
+               sd_config_discard(sdkp, SD_LBP_WS16);
+       else if (!strncmp(buf, lbp_mode[SD_LBP_WS10], 20))
+               sd_config_discard(sdkp, SD_LBP_WS10);
+       else if (!strncmp(buf, lbp_mode[SD_LBP_ZERO], 20))
+               sd_config_discard(sdkp, SD_LBP_ZERO);
+       else if (!strncmp(buf, lbp_mode[SD_LBP_DISABLE], 20))
+               sd_config_discard(sdkp, SD_LBP_DISABLE);
+       else
+               return -EINVAL;
+
+       return count;
 }
 
 static struct device_attribute sd_disk_attrs[] = {
@@ -309,6 +357,8 @@ static struct device_attribute sd_disk_attrs[] = {
        __ATTR(protection_mode, S_IRUGO, sd_show_protection_mode, NULL),
        __ATTR(app_tag_own, S_IRUGO, sd_show_app_tag_own, NULL),
        __ATTR(thin_provisioning, S_IRUGO, sd_show_thin_provisioning, NULL),
+       __ATTR(provisioning_mode, S_IRUGO|S_IWUSR, sd_show_provisioning_mode,
+              sd_store_provisioning_mode),
        __ATTR_NULL,
 };
 
@@ -433,6 +483,49 @@ static void sd_prot_op(struct scsi_cmnd *scmd, unsigned int dif)
        scsi_set_prot_type(scmd, dif);
 }
 
+static void sd_config_discard(struct scsi_disk *sdkp, unsigned int mode)
+{
+       struct request_queue *q = sdkp->disk->queue;
+       unsigned int logical_block_size = sdkp->device->sector_size;
+       unsigned int max_blocks = 0;
+
+       q->limits.discard_zeroes_data = sdkp->lbprz;
+       q->limits.discard_alignment = sdkp->unmap_alignment;
+       q->limits.discard_granularity =
+               max(sdkp->physical_block_size,
+                   sdkp->unmap_granularity * logical_block_size);
+
+       switch (mode) {
+
+       case SD_LBP_DISABLE:
+               q->limits.max_discard_sectors = 0;
+               queue_flag_clear_unlocked(QUEUE_FLAG_DISCARD, q);
+               return;
+
+       case SD_LBP_UNMAP:
+               max_blocks = min_not_zero(sdkp->max_unmap_blocks, 0xffffffff);
+               break;
+
+       case SD_LBP_WS16:
+               max_blocks = min_not_zero(sdkp->max_ws_blocks, 0xffffffff);
+               break;
+
+       case SD_LBP_WS10:
+               max_blocks = min_not_zero(sdkp->max_ws_blocks, (u32)0xffff);
+               break;
+
+       case SD_LBP_ZERO:
+               max_blocks = min_not_zero(sdkp->max_ws_blocks, (u32)0xffff);
+               q->limits.discard_zeroes_data = 1;
+               break;
+       }
+
+       q->limits.max_discard_sectors = max_blocks * (logical_block_size >> 9);
+       queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q);
+
+       sdkp->provisioning_mode = mode;
+}
+
 /**
  * scsi_setup_discard_cmnd - unmap blocks on thinly provisioned device
  * @sdp: scsi device to operate one
@@ -449,6 +542,7 @@ static int scsi_setup_discard_cmnd(struct scsi_device *sdp, struct request *rq)
        unsigned int nr_sectors = bio_sectors(bio);
        unsigned int len;
        int ret;
+       char *buf;
        struct page *page;
 
        if (sdkp->device->sector_size == 4096) {
@@ -464,8 +558,9 @@ static int scsi_setup_discard_cmnd(struct scsi_device *sdp, struct request *rq)
        if (!page)
                return BLKPREP_DEFER;
 
-       if (sdkp->unmap) {
-               char *buf = page_address(page);
+       switch (sdkp->provisioning_mode) {
+       case SD_LBP_UNMAP:
+               buf = page_address(page);
 
                rq->cmd_len = 10;
                rq->cmd[0] = UNMAP;
@@ -477,7 +572,9 @@ static int scsi_setup_discard_cmnd(struct scsi_device *sdp, struct request *rq)
                put_unaligned_be32(nr_sectors, &buf[16]);
 
                len = 24;
-       } else {
+               break;
+
+       case SD_LBP_WS16:
                rq->cmd_len = 16;
                rq->cmd[0] = WRITE_SAME_16;
                rq->cmd[1] = 0x8; /* UNMAP */
@@ -485,11 +582,30 @@ static int scsi_setup_discard_cmnd(struct scsi_device *sdp, struct request *rq)
                put_unaligned_be32(nr_sectors, &rq->cmd[10]);
 
                len = sdkp->device->sector_size;
+               break;
+
+       case SD_LBP_WS10:
+       case SD_LBP_ZERO:
+               rq->cmd_len = 10;
+               rq->cmd[0] = WRITE_SAME;
+               if (sdkp->provisioning_mode == SD_LBP_WS10)
+                       rq->cmd[1] = 0x8; /* UNMAP */
+               put_unaligned_be32(sector, &rq->cmd[2]);
+               put_unaligned_be16(nr_sectors, &rq->cmd[7]);
+
+               len = sdkp->device->sector_size;
+               break;
+
+       default:
+               ret = BLKPREP_KILL;
+               goto out;
        }
 
        blk_add_request_payload(rq, page, len);
        ret = scsi_setup_blk_pc_cmnd(sdp, rq);
        rq->buffer = page_address(page);
+
+out:
        if (ret != BLKPREP_OK) {
                __free_page(page);
                rq->buffer = NULL;
@@ -1251,12 +1367,10 @@ static int sd_done(struct scsi_cmnd *SCpnt)
        struct scsi_disk *sdkp = scsi_disk(SCpnt->request->rq_disk);
        int sense_valid = 0;
        int sense_deferred = 0;
+       unsigned char op = SCpnt->cmnd[0];
 
-       if (SCpnt->request->cmd_flags & REQ_DISCARD) {
-               if (!result)
-                       scsi_set_resid(SCpnt, 0);
-               return good_bytes;
-       }
+       if ((SCpnt->request->cmd_flags & REQ_DISCARD) && !result)
+               scsi_set_resid(SCpnt, 0);
 
        if (result) {
                sense_valid = scsi_command_normalize_sense(SCpnt, &sshdr);
@@ -1295,10 +1409,17 @@ static int sd_done(struct scsi_cmnd *SCpnt)
                SCpnt->result = 0;
                memset(SCpnt->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE);
                break;
-       case ABORTED_COMMAND: /* DIF: Target detected corruption */
-       case ILLEGAL_REQUEST: /* DIX: Host detected corruption */
-               if (sshdr.asc == 0x10)
+       case ABORTED_COMMAND:
+               if (sshdr.asc == 0x10)  /* DIF: Target detected corruption */
+                       good_bytes = sd_completed_bytes(SCpnt);
+               break;
+       case ILLEGAL_REQUEST:
+               if (sshdr.asc == 0x10)  /* DIX: Host detected corruption */
                        good_bytes = sd_completed_bytes(SCpnt);
+               /* INVALID COMMAND OPCODE or INVALID FIELD IN CDB */
+               if ((sshdr.asc == 0x20 || sshdr.asc == 0x24) &&
+                   (op == UNMAP || op == WRITE_SAME_16 || op == WRITE_SAME))
+                       sd_config_discard(sdkp, SD_LBP_DISABLE);
                break;
        default:
                break;
@@ -1596,17 +1717,13 @@ 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;
+       if (buffer[14] & 0x80) { /* LBPME */
+               sdkp->lbpme = 1;
 
-               sdkp->thin_provisioning = 1;
-               q->limits.discard_granularity = sdkp->physical_block_size;
-               q->limits.max_discard_sectors = 0xffffffff;
+               if (buffer[14] & 0x40) /* LBPRZ */
+                       sdkp->lbprz = 1;
 
-               if (buffer[14] & 0x40) /* TPRZ */
-                       q->limits.discard_zeroes_data = 1;
-
-               queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q);
+               sd_config_discard(sdkp, SD_LBP_WS16);
        }
 
        sdkp->capacity = lba + 1;
@@ -1910,14 +2027,10 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer)
        int old_rcd = sdkp->RCD;
        int old_dpofua = sdkp->DPOFUA;
 
-       if (sdp->skip_ms_page_8) {
-               if (sdp->type == TYPE_RBC)
-                       goto defaults;
-               else {
-                       modepage = 0x3F;
-                       dbd = 0;
-               }
-       } else if (sdp->type == TYPE_RBC) {
+       if (sdp->skip_ms_page_8)
+               goto defaults;
+
+       if (sdp->type == TYPE_RBC) {
                modepage = 6;
                dbd = 8;
        } else {
@@ -1945,11 +2058,13 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer)
         */
        if (len < 3)
                goto bad_sense;
-       else if (len > SD_BUF_SIZE) {
-               sd_printk(KERN_NOTICE, sdkp, "Truncating mode parameter "
-                         "data from %d to %d bytes\n", len, SD_BUF_SIZE);
-               len = SD_BUF_SIZE;
-       }
+       if (len > 20)
+               len = 20;
+
+       /* Take headers and block descriptors into account */
+       len += data.header_length + data.block_descriptor_length;
+       if (len > SD_BUF_SIZE)
+               goto bad_sense;
 
        /* Get the data */
        res = sd_do_mode_sense(sdp, dbd, modepage, buffer, len, &data, &sshdr);
@@ -1957,45 +2072,16 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer)
        if (scsi_status_is_good(res)) {
                int offset = data.header_length + data.block_descriptor_length;
 
-               while (offset < len) {
-                       u8 page_code = buffer[offset] & 0x3F;
-                       u8 spf       = buffer[offset] & 0x40;
-
-                       if (page_code == 8 || page_code == 6) {
-                               /* We're interested only in the first 3 bytes.
-                                */
-                               if (len - offset <= 2) {
-                                       sd_printk(KERN_ERR, sdkp, "Incomplete "
-                                                 "mode parameter data\n");
-                                       goto defaults;
-                               } else {
-                                       modepage = page_code;
-                                       goto Page_found;
-                               }
-                       } else {
-                               /* Go to the next page */
-                               if (spf && len - offset > 3)
-                                       offset += 4 + (buffer[offset+2] << 8) +
-                                               buffer[offset+3];
-                               else if (!spf && len - offset > 1)
-                                       offset += 2 + buffer[offset+1];
-                               else {
-                                       sd_printk(KERN_ERR, sdkp, "Incomplete "
-                                                 "mode parameter data\n");
-                                       goto defaults;
-                               }
-                       }
+               if (offset >= SD_BUF_SIZE - 2) {
+                       sd_printk(KERN_ERR, sdkp, "Malformed MODE SENSE response\n");
+                       goto defaults;
                }
 
-               if (modepage == 0x3F) {
-                       sd_printk(KERN_ERR, sdkp, "No Caching mode page "
-                                 "present\n");
-                       goto defaults;
-               } else if ((buffer[offset] & 0x3f) != modepage) {
+               if ((buffer[offset] & 0x3f) != modepage) {
                        sd_printk(KERN_ERR, sdkp, "Got wrong page\n");
                        goto defaults;
                }
-       Page_found:
+
                if (modepage == 8) {
                        sdkp->WCE = ((buffer[offset + 2] & 0x04) != 0);
                        sdkp->RCD = ((buffer[offset + 2] & 0x01) != 0);
@@ -2091,7 +2177,6 @@ static 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;
        const int vpd_len = 64;
        unsigned char *buffer = kmalloc(vpd_len, GFP_KERNEL);
@@ -2106,39 +2191,46 @@ 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;
+       if (buffer[3] == 0x3c) {
+               unsigned int lba_count, desc_count;
 
-               lba_count = get_unaligned_be32(&buffer[20]);
-               desc_count = get_unaligned_be32(&buffer[24]);
-
-               if (lba_count && desc_count) {
-                       if (sdkp->tpvpd && !sdkp->tpu)
-                               sdkp->unmap = 0;
-                       else
-                               sdkp->unmap = 1;
-               }
+               sdkp->max_ws_blocks =
+                       (u32) min_not_zero(get_unaligned_be64(&buffer[36]),
+                                          (u64)0xffffffff);
 
-               if (sdkp->tpvpd && !sdkp->tpu && !sdkp->tpws) {
-                       sd_printk(KERN_ERR, sdkp, "Thin provisioning is " \
-                                 "enabled but neither TPU, nor TPWS are " \
-                                 "set. Disabling discard!\n");
+               if (!sdkp->lbpme)
                        goto out;
-               }
 
-               if (lba_count)
-                       q->limits.max_discard_sectors =
-                               lba_count * sector_sz >> 9;
+               lba_count = get_unaligned_be32(&buffer[20]);
+               desc_count = get_unaligned_be32(&buffer[24]);
 
-               granularity = get_unaligned_be32(&buffer[28]);
+               if (lba_count && desc_count)
+                       sdkp->max_unmap_blocks = lba_count;
 
-               if (granularity)
-                       q->limits.discard_granularity = granularity * sector_sz;
+               sdkp->unmap_granularity = get_unaligned_be32(&buffer[28]);
 
                if (buffer[32] & 0x80)
-                       q->limits.discard_alignment =
+                       sdkp->unmap_alignment =
                                get_unaligned_be32(&buffer[32]) & ~(1 << 31);
+
+               if (!sdkp->lbpvpd) { /* LBP VPD page not provided */
+
+                       if (sdkp->max_unmap_blocks)
+                               sd_config_discard(sdkp, SD_LBP_UNMAP);
+                       else
+                               sd_config_discard(sdkp, SD_LBP_WS16);
+
+               } else {        /* LBP VPD page tells us what to use */
+
+                       if (sdkp->lbpu && sdkp->max_unmap_blocks)
+                               sd_config_discard(sdkp, SD_LBP_UNMAP);
+                       else if (sdkp->lbpws)
+                               sd_config_discard(sdkp, SD_LBP_WS16);
+                       else if (sdkp->lbpws10)
+                               sd_config_discard(sdkp, SD_LBP_WS10);
+                       else
+                               sd_config_discard(sdkp, SD_LBP_DISABLE);
+               }
        }
 
  out:
@@ -2172,15 +2264,15 @@ static void sd_read_block_characteristics(struct scsi_disk *sdkp)
 }
 
 /**
- * sd_read_thin_provisioning - Query thin provisioning VPD page
+ * sd_read_block_provisioning - Query provisioning VPD page
  * @disk: disk to query
  */
-static void sd_read_thin_provisioning(struct scsi_disk *sdkp)
+static void sd_read_block_provisioning(struct scsi_disk *sdkp)
 {
        unsigned char *buffer;
        const int vpd_len = 8;
 
-       if (sdkp->thin_provisioning == 0)
+       if (sdkp->lbpme == 0)
                return;
 
        buffer = kmalloc(vpd_len, GFP_KERNEL);
@@ -2188,9 +2280,10 @@ static void sd_read_thin_provisioning(struct scsi_disk *sdkp)
        if (!buffer || scsi_get_vpd_page(sdkp->device, 0xb2, buffer, vpd_len))
                goto out;
 
-       sdkp->tpvpd = 1;
-       sdkp->tpu   = (buffer[5] >> 7) & 1;     /* UNMAP */
-       sdkp->tpws  = (buffer[5] >> 6) & 1;     /* WRITE SAME(16) with UNMAP */
+       sdkp->lbpvpd    = 1;
+       sdkp->lbpu      = (buffer[5] >> 7) & 1; /* UNMAP */
+       sdkp->lbpws     = (buffer[5] >> 6) & 1; /* WRITE SAME(16) with UNMAP */
+       sdkp->lbpws10   = (buffer[5] >> 5) & 1; /* WRITE SAME(10) with UNMAP */
 
  out:
        kfree(buffer);
@@ -2247,7 +2340,7 @@ static int sd_revalidate_disk(struct gendisk *disk)
                sd_read_capacity(sdkp, buffer);
 
                if (sd_try_extended_inquiry(sdp)) {
-                       sd_read_thin_provisioning(sdkp);
+                       sd_read_block_provisioning(sdkp);
                        sd_read_block_limits(sdkp);
                        sd_read_block_characteristics(sdkp);
                }