Commit 392bbeb8 authored by Kashyap Desai's avatar Kashyap Desai Committed by Martin K. Petersen
Browse files

scsi: mpi3mr: Hardware workaround for UNMAP commands to NVMe drives

The controller hardware can not handle certain UNMAP commands for NVMe
drives. Add support in the driver for checking those commands and handle
them appropriately.

Link: https://lore.kernel.org/r/20210520152545.2710479-17-kashyap.desai@broadcom.com


Cc: sathya.prakash@broadcom.com
Reviewed-by: default avatarHannes Reinecke <hare@suse.de>
Reviewed-by: default avatarTomas Henzl <thenzl@redhat.com>
Reviewed-by: default avatarHimanshu Madhani <himanshu.madhani@oracle.com>
Signed-off-by: default avatarKashyap Desai <kashyap.desai@broadcom.com>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 82141ddb
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
@@ -2767,6 +2767,101 @@ static int mpi3mr_target_alloc(struct scsi_target *starget)
	return retval;
}

/**
 * mpi3mr_check_return_unmap - Whether an unmap is allowed
 * @mrioc: Adapter instance reference
 * @scmd: SCSI Command reference
 *
 * The controller hardware cannot handle certain unmap commands
 * for NVMe drives, this routine checks those and return true
 * and completes the SCSI command with proper status and sense
 * data.
 *
 * Return: TRUE for not  allowed unmap, FALSE otherwise.
 */
static bool mpi3mr_check_return_unmap(struct mpi3mr_ioc *mrioc,
	struct scsi_cmnd *scmd)
{
	unsigned char *buf;
	u16 param_len, desc_len;

	param_len = get_unaligned_be16(scmd->cmnd + 7);

	if (!param_len) {
		ioc_warn(mrioc,
		    "%s: cdb received with zero parameter length\n",
		    __func__);
		scsi_print_command(scmd);
		scmd->result = DID_OK << 16;
		scmd->scsi_done(scmd);
		return true;
	}

	if (param_len < 24) {
		ioc_warn(mrioc,
		    "%s: cdb received with invalid param_len: %d\n",
		    __func__, param_len);
		scsi_print_command(scmd);
		scmd->result = (DRIVER_SENSE << 24) |
		    SAM_STAT_CHECK_CONDITION;
		scsi_build_sense_buffer(0, scmd->sense_buffer, ILLEGAL_REQUEST,
		    0x1A, 0);
		scmd->scsi_done(scmd);
		return true;
	}
	if (param_len != scsi_bufflen(scmd)) {
		ioc_warn(mrioc,
		    "%s: cdb received with param_len: %d bufflen: %d\n",
		    __func__, param_len, scsi_bufflen(scmd));
		scsi_print_command(scmd);
		scmd->result = (DRIVER_SENSE << 24) |
		    SAM_STAT_CHECK_CONDITION;
		scsi_build_sense_buffer(0, scmd->sense_buffer, ILLEGAL_REQUEST,
		    0x1A, 0);
		scmd->scsi_done(scmd);
		return true;
	}
	buf = kzalloc(scsi_bufflen(scmd), GFP_ATOMIC);
	if (!buf) {
		scsi_print_command(scmd);
		scmd->result = (DRIVER_SENSE << 24) |
		    SAM_STAT_CHECK_CONDITION;
		scsi_build_sense_buffer(0, scmd->sense_buffer, ILLEGAL_REQUEST,
		    0x55, 0x03);
		scmd->scsi_done(scmd);
		return true;
	}
	scsi_sg_copy_to_buffer(scmd, buf, scsi_bufflen(scmd));
	desc_len = get_unaligned_be16(&buf[2]);

	if (desc_len < 16) {
		ioc_warn(mrioc,
		    "%s: Invalid descriptor length in param list: %d\n",
		    __func__, desc_len);
		scsi_print_command(scmd);
		scmd->result = (DRIVER_SENSE << 24) |
		    SAM_STAT_CHECK_CONDITION;
		scsi_build_sense_buffer(0, scmd->sense_buffer, ILLEGAL_REQUEST,
		    0x26, 0);
		scmd->scsi_done(scmd);
		kfree(buf);
		return true;
	}

	if (param_len > (desc_len + 8)) {
		scsi_print_command(scmd);
		ioc_warn(mrioc,
		    "%s: Truncating param_len(%d) to desc_len+8(%d)\n",
		    __func__, param_len, (desc_len + 8));
		param_len = desc_len + 8;
		put_unaligned_be16(param_len, scmd->cmnd + 7);
		scsi_print_command(scmd);
	}

	kfree(buf);
	return false;
}

/**
 * mpi3mr_allow_scmd_to_fw - Command is allowed during shutdown
 * @scmd: SCSI Command reference
@@ -2858,6 +2953,11 @@ static int mpi3mr_qcmd(struct Scsi_Host *shost,
		goto out;
	}

	if ((scmd->cmnd[0] == UNMAP) &&
	    (stgt_priv_data->dev_type == MPI3_DEVICE_DEVFORM_PCIE) &&
	    mpi3mr_check_return_unmap(mrioc, scmd))
		goto out;

	host_tag = mpi3mr_host_tag_for_scmd(mrioc, scmd);
	if (host_tag == MPI3MR_HOSTTAG_INVALID) {
		scmd->result = DID_ERROR << 16;