mtip32xx: add trim support
authorAsai Thambi S P <asamymuthupa@micron.com>
Fri, 11 Jan 2013 13:41:34 +0000 (14:41 +0100)
committerJens Axboe <axboe@kernel.dk>
Fri, 11 Jan 2013 13:41:34 +0000 (14:41 +0100)
TRIM support added through vendor unique command.

Signed-off-by: Sam Bradshaw < sbradshaw@micron.com>
Signed-off-by: Asai Thambi S P <asamymuthupa@micron.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
drivers/block/mtip32xx/mtip32xx.c
drivers/block/mtip32xx/mtip32xx.h

index 99c2cf4..b7579b7 100644 (file)
@@ -1519,6 +1519,12 @@ static int mtip_get_identify(struct mtip_port *port, void __user *user_buffer)
        }
 #endif
 
+       /* Demux ID.DRAT & ID.RZAT to determine trim support */
+       if (port->identify[69] & (1 << 14) && port->identify[69] & (1 << 5))
+               port->dd->trim_supp = true;
+       else
+               port->dd->trim_supp = false;
+
        /* Set the identify buffer as valid. */
        port->identify_valid = 1;
 
@@ -1706,6 +1712,81 @@ static int mtip_get_smart_attr(struct mtip_port *port, unsigned int id,
 }
 
 /*
+ * Trim unused sectors
+ *
+ * @dd         pointer to driver_data structure
+ * @lba                starting lba
+ * @len                # of 512b sectors to trim
+ *
+ * return value
+ *      -ENOMEM                Out of dma memory
+ *      -EINVAL                Invalid parameters passed in, trim not supported
+ *      -EIO           Error submitting trim request to hw
+ */
+int mtip_send_trim(struct driver_data *dd, unsigned int lba, unsigned int len)
+{
+       int i, rv = 0;
+       u64 tlba, tlen, sect_left;
+       struct mtip_trim_entry *buf;
+       dma_addr_t dma_addr;
+       struct host_to_dev_fis fis;
+
+       if (!len || dd->trim_supp == false)
+               return -EINVAL;
+
+       /* Trim request too big */
+       WARN_ON(len > (MTIP_MAX_TRIM_ENTRY_LEN * MTIP_MAX_TRIM_ENTRIES));
+
+       /* Trim request not aligned on 4k boundary */
+       WARN_ON(len % 8 != 0);
+
+       /* Warn if vu_trim structure is too big */
+       WARN_ON(sizeof(struct mtip_trim) > ATA_SECT_SIZE);
+
+       /* Allocate a DMA buffer for the trim structure */
+       buf = dmam_alloc_coherent(&dd->pdev->dev, ATA_SECT_SIZE, &dma_addr,
+                                                               GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+       memset(buf, 0, ATA_SECT_SIZE);
+
+       for (i = 0, sect_left = len, tlba = lba;
+                       i < MTIP_MAX_TRIM_ENTRIES && sect_left;
+                       i++) {
+               tlen = (sect_left >= MTIP_MAX_TRIM_ENTRY_LEN ?
+                                       MTIP_MAX_TRIM_ENTRY_LEN :
+                                       sect_left);
+               buf[i].lba = __force_bit2int cpu_to_le32(tlba);
+               buf[i].range = __force_bit2int cpu_to_le16(tlen);
+               tlba += tlen;
+               sect_left -= tlen;
+       }
+       WARN_ON(sect_left != 0);
+
+       /* Build the fis */
+       memset(&fis, 0, sizeof(struct host_to_dev_fis));
+       fis.type       = 0x27;
+       fis.opts       = 1 << 7;
+       fis.command    = 0xfb;
+       fis.features   = 0x60;
+       fis.sect_count = 1;
+       fis.device     = ATA_DEVICE_OBS;
+
+       if (mtip_exec_internal_command(dd->port,
+                                       &fis,
+                                       5,
+                                       dma_addr,
+                                       ATA_SECT_SIZE,
+                                       0,
+                                       GFP_KERNEL,
+                                       MTIP_TRIM_TIMEOUT_MS) < 0)
+               rv = -EIO;
+
+       dmam_free_coherent(&dd->pdev->dev, ATA_SECT_SIZE, buf, dma_addr);
+       return rv;
+}
+
+/*
  * Get the drive capacity.
  *
  * @dd      Pointer to the device data structure.
@@ -3675,6 +3756,12 @@ static void mtip_make_request(struct request_queue *queue, struct bio *bio)
                }
        }
 
+       if (unlikely(bio->bi_rw & REQ_DISCARD)) {
+               bio_endio(bio, mtip_send_trim(dd, bio->bi_sector,
+                                               bio_sectors(bio)));
+               return;
+       }
+
        if (unlikely(!bio_has_data(bio))) {
                blk_queue_flush(queue, 0);
                bio_endio(bio, 0);
@@ -3817,6 +3904,15 @@ skip_create_disk:
         */
        blk_queue_flush(dd->queue, 0);
 
+       /* Signal trim support */
+       if (dd->trim_supp == true) {
+               set_bit(QUEUE_FLAG_DISCARD, &dd->queue->queue_flags);
+               dd->queue->limits.discard_granularity = 4096;
+               blk_queue_max_discard_sectors(dd->queue,
+                       MTIP_MAX_TRIM_ENTRY_LEN * MTIP_MAX_TRIM_ENTRIES);
+               dd->queue->limits.discard_zeroes_data = 0;
+       }
+
        /* Set the capacity of the device in 512 byte sectors. */
        if (!(mtip_hw_get_capacity(dd, &capacity))) {
                dev_warn(&dd->pdev->dev,
index d782b1a..3bffff5 100644 (file)
@@ -178,6 +178,21 @@ struct mtip_work {
                mtip_workq_sdbfx(w->port, group, w->completed);     \
        }
 
+#define MTIP_TRIM_TIMEOUT_MS           240000
+#define MTIP_MAX_TRIM_ENTRIES          8
+#define MTIP_MAX_TRIM_ENTRY_LEN        0xfff8
+
+struct mtip_trim_entry {
+       u32 lba;   /* starting lba of region */
+       u16 rsvd;  /* unused */
+       u16 range; /* # of 512b blocks to trim */
+} __packed;
+
+struct mtip_trim {
+       /* Array of regions to trim */
+       struct mtip_trim_entry entry[MTIP_MAX_TRIM_ENTRIES];
+} __packed;
+
 /* Register Frame Information Structure (FIS), host to device. */
 struct host_to_dev_fis {
        /*
@@ -473,6 +488,8 @@ struct driver_data {
 
        struct dentry *dfs_node;
 
+       bool trim_supp; /* flag indicating trim support */
+
        int numa_node; /* NUMA support */
 
        char workq_name[32];