Don't try to fake up basic SCSI logical block provisioning and WRITE SAME support, but offer support for the Linux Vendor Specific TRIM command instead. This simplifies the implementation a lot, and avoids rewriting the data out buffer in the I/O path. Note that this new command is only offered to the block layer and will fail for pass through commands. While this is theoretically a regression in the functionality offered through SG_IO the previous support was buggy and corrupted user memory by rewriting the data out buffer in place. Last but not least this removes the global ata_scsi_rbuf_lock from the trim path. Signed-off-by: Christoph Hellwig <hch@xxxxxx> --- drivers/ata/libata-scsi.c | 179 ++++++++-------------------------------------- 1 file changed, 28 insertions(+), 151 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index b93d7e33789a..965b9e7dbb7d 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1322,6 +1322,16 @@ static int ata_scsi_dev_config(struct scsi_device *sdev, blk_queue_flush_queueable(q, false); + if (ata_id_has_trim(dev->id) && + !(dev->horkage & ATA_HORKAGE_NOTRIM)) { + sdev->ata_trim = 1; + if (ata_id_has_zero_after_trim(dev->id) && + (dev->horkage & ATA_HORKAGE_ZERO_AFTER_TRIM)) { + ata_dev_info(dev, "Enabling discard_zeroes_data\n"); + sdev->ata_trim_zeroes_data = 1; + } + } + dev->sdev = sdev; return 0; } @@ -2383,21 +2393,6 @@ static unsigned int ata_scsiop_inq_b0(struct ata_scsi_args *args, u8 *rbuf) */ min_io_sectors = 1 << ata_id_log2_per_physical_sector(args->id); put_unaligned_be16(min_io_sectors, &rbuf[6]); - - /* - * Optimal unmap granularity. - * - * The ATA spec doesn't even know about a granularity or alignment - * for the TRIM command. We can leave away most of the unmap related - * VPD page entries, but we have specifify a granularity to signal - * that we support some form of unmap - in thise case via WRITE SAME - * with the unmap bit set. - */ - if (ata_id_has_trim(args->id)) { - put_unaligned_be64(65535 * ATA_MAX_TRIM_RNUM, &rbuf[36]); - put_unaligned_be32(1, &rbuf[28]); - } - return 0; } @@ -2746,16 +2741,6 @@ static unsigned int ata_scsiop_read_cap(struct ata_scsi_args *args, u8 *rbuf) rbuf[14] = (lowest_aligned >> 8) & 0x3f; rbuf[15] = lowest_aligned; - if (ata_id_has_trim(args->id) && - !(dev->horkage & ATA_HORKAGE_NOTRIM)) { - rbuf[14] |= 0x80; /* LBPME */ - - if (ata_id_has_zero_after_trim(args->id) && - dev->horkage & ATA_HORKAGE_ZERO_AFTER_TRIM) { - ata_dev_info(dev, "Enabling discard_zeroes_data\n"); - rbuf[14] |= 0x40; /* LBPRZ */ - } - } if (ata_id_zoned_cap(args->id) || args->dev->class == ATA_DEV_ZAC) rbuf[12] = (1 << 4); /* RC_BASIS */ @@ -3339,141 +3324,45 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc) } /** - * ata_format_dsm_trim_descr() - SATL Write Same to DSM Trim - * @cmd: SCSI command being translated - * @trmax: Maximum number of entries that will fit in sector_size bytes. - * @sector: Starting sector - * @count: Total Range of request in logical sectors - * - * Rewrite the WRITE SAME descriptor to be a DSM TRIM little-endian formatted - * descriptor. - * - * Upto 64 entries of the format: - * 63:48 Range Length - * 47:0 LBA - * - * Range Length of 0 is ignored. - * LBA's should be sorted order and not overlap. - * - * NOTE: this is the same format as ADD LBA(S) TO NV CACHE PINNED SET - * - * Return: Number of bytes copied into sglist. - */ -static size_t ata_format_dsm_trim_descr(struct scsi_cmnd *cmd, u32 trmax, - u64 sector, u32 count) -{ - struct scsi_device *sdp = cmd->device; - size_t len = sdp->sector_size; - size_t r; - __le64 *buf; - u32 i = 0; - unsigned long flags; - - WARN_ON(len > ATA_SCSI_RBUF_SIZE); - - if (len > ATA_SCSI_RBUF_SIZE) - len = ATA_SCSI_RBUF_SIZE; - - spin_lock_irqsave(&ata_scsi_rbuf_lock, flags); - buf = ((void *)ata_scsi_rbuf); - memset(buf, 0, len); - while (i < trmax) { - u64 entry = sector | - ((u64)(count > 0xffff ? 0xffff : count) << 48); - buf[i++] = __cpu_to_le64(entry); - if (count <= 0xffff) - break; - count -= 0xffff; - sector += 0xffff; - } - r = sg_copy_from_buffer(scsi_sglist(cmd), scsi_sg_count(cmd), buf, len); - spin_unlock_irqrestore(&ata_scsi_rbuf_lock, flags); - - return r; -} - -/** - * ata_scsi_write_same_xlat() - SATL Write Same to ATA SCT Write Same + * ata_scsi_trim_xlat() - Handle the vendor specific TRIM command. * @qc: Command to be translated * - * Translate a SCSI WRITE SAME command to be either a DSM TRIM command or - * an SCT Write Same command. - * Based on WRITE SAME has the UNMAP flag - * When set translate to DSM TRIM - * When clear translate to SCT Write Same + * Setup a DSM TRIM command (or it's queued variant) after sd already + * prepared the payload for us. */ -static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) +static unsigned int ata_scsi_trim_xlat(struct ata_queued_cmd *qc) { struct ata_taskfile *tf = &qc->tf; struct scsi_cmnd *scmd = qc->scsicmd; - struct scsi_device *sdp = scmd->device; - size_t len = sdp->sector_size; struct ata_device *dev = qc->dev; - const u8 *cdb = scmd->cmnd; - u64 block; - u32 n_block; - const u32 trmax = len >> 3; - u32 size; - u16 fp; - u8 bp = 0xff; - u8 unmap = cdb[1] & 0x8; - - /* we may not issue DMA commands if no DMA mode is set */ - if (unlikely(!dev->dma_mode)) - goto invalid_opcode; - if (unlikely(scmd->cmd_len < 16)) { - fp = 15; - goto invalid_fld; + if (unlikely(!dev->dma_mode)) { + ata_scsi_set_sense(dev, scmd, ILLEGAL_REQUEST, 0x20, 0x0); + return 1; } - scsi_16_lba_len(cdb, &block, &n_block); - if (!unmap || - (dev->horkage & ATA_HORKAGE_NOTRIM) || - !ata_id_has_trim(dev->id)) { - fp = 1; - bp = 3; - goto invalid_fld; - } - /* If the request is too large the cmd is invalid */ - if (n_block > 0xffff * trmax) { - fp = 2; - goto invalid_fld; + /* We only allow sending this command through the block layer */ + if (unlikely(req_op(scmd->request) != REQ_OP_DISCARD)) { + ata_scsi_set_sense(dev, scmd, ILLEGAL_REQUEST, 0x20, 0x0); + return 1; } - /* - * WRITE SAME always has a sector sized buffer as payload, this - * should never be a multiple entry S/G list. - */ - if (!scsi_sg_count(scmd)) - goto invalid_param_len; - - /* - * size must match sector size in bytes - * For DATA SET MANAGEMENT TRIM in ACS-2 nsect (aka count) - * is defined as number of 512 byte blocks to be transferred. - */ - - size = ata_format_dsm_trim_descr(scmd, trmax, block, n_block); - if (size != len) - goto invalid_param_len; - if (ata_ncq_enabled(dev) && ata_fpdma_dsm_supported(dev)) { /* Newer devices support queued TRIM commands */ tf->protocol = ATA_PROT_NCQ; tf->command = ATA_CMD_FPDMA_SEND; tf->hob_nsect = ATA_SUBCMD_FPDMA_SEND_DSM & 0x1f; tf->nsect = qc->tag << 3; - tf->hob_feature = (size / 512) >> 8; - tf->feature = size / 512; + tf->hob_feature = (scmd->device->sector_size / 512) >> 8; + tf->feature = scmd->device->sector_size / 512; tf->auxiliary = 1; } else { tf->protocol = ATA_PROT_DMA; tf->hob_feature = 0; tf->feature = ATA_DSM_TRIM; - tf->hob_nsect = (size / 512) >> 8; - tf->nsect = size / 512; + tf->hob_nsect = (scmd->device->sector_size / 512) >> 8; + tf->nsect = scmd->device->sector_size / 512; tf->command = ATA_CMD_DSM; } @@ -3483,18 +3372,6 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) ata_qc_set_pc_nbytes(qc); return 0; - -invalid_fld: - ata_scsi_set_invalid_field(dev, scmd, fp, bp); - return 1; -invalid_param_len: - /* "Parameter list length error" */ - ata_scsi_set_sense(dev, scmd, ILLEGAL_REQUEST, 0x1a, 0x0); - return 1; -invalid_opcode: - /* "Invalid command operation code" */ - ata_scsi_set_sense(dev, scmd, ILLEGAL_REQUEST, 0x20, 0x0); - return 1; } /** @@ -4087,9 +3964,6 @@ static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd) case WRITE_16: return ata_scsi_rw_xlat; - case WRITE_SAME_16: - return ata_scsi_write_same_xlat; - case SYNCHRONIZE_CACHE: if (ata_try_flush_cache(dev)) return ata_scsi_flush_xlat; @@ -4116,6 +3990,9 @@ static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd) case START_STOP: return ata_scsi_start_stop_xlat; + + case LINUX_VS_TRIM: + return ata_scsi_trim_xlat; } return NULL; -- 2.11.0