diff options
Diffstat (limited to 'drivers/scsi/mpi3mr/mpi3mr_app.c')
| -rw-r--r-- | drivers/scsi/mpi3mr/mpi3mr_app.c | 1152 | 
1 files changed, 1149 insertions, 3 deletions
| diff --git a/drivers/scsi/mpi3mr/mpi3mr_app.c b/drivers/scsi/mpi3mr/mpi3mr_app.c index 1638109a68a0..8b0eded6ef36 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_app.c +++ b/drivers/scsi/mpi3mr/mpi3mr_app.c @@ -12,6 +12,821 @@  #include <uapi/scsi/scsi_bsg_mpi3mr.h>  /** + * mpi3mr_alloc_trace_buffer:	Allocate trace buffer + * @mrioc: Adapter instance reference + * @trace_size: Trace buffer size + * + * Allocate trace buffer + * Return: 0 on success, non-zero on failure. + */ +static int mpi3mr_alloc_trace_buffer(struct mpi3mr_ioc *mrioc, u32 trace_size) +{ +	struct diag_buffer_desc *diag_buffer = &mrioc->diag_buffers[0]; + +	diag_buffer->addr = dma_alloc_coherent(&mrioc->pdev->dev, +	    trace_size, &diag_buffer->dma_addr, GFP_KERNEL); +	if (diag_buffer->addr) { +		dprint_init(mrioc, "trace diag buffer is allocated successfully\n"); +		return 0; +	} +	return -1; +} + +/** + * mpi3mr_alloc_diag_bufs - Allocate memory for diag buffers + * @mrioc: Adapter instance reference + * + * This functions checks whether the driver defined buffer sizes + * are greater than IOCFacts provided controller local buffer + * sizes and if the driver defined sizes are more then the + * driver allocates the specific buffer by reading driver page1 + * + * Return: Nothing. + */ +void mpi3mr_alloc_diag_bufs(struct mpi3mr_ioc *mrioc) +{ +	struct diag_buffer_desc *diag_buffer; +	struct mpi3_driver_page1 driver_pg1; +	u32 trace_dec_size, trace_min_size, fw_dec_size, fw_min_size, +		trace_size, fw_size; +	u16 pg_sz = sizeof(driver_pg1); +	int retval = 0; +	bool retry = false; + +	if (mrioc->diag_buffers[0].addr || mrioc->diag_buffers[1].addr) +		return; + +	retval = mpi3mr_cfg_get_driver_pg1(mrioc, &driver_pg1, pg_sz); +	if (retval) { +		ioc_warn(mrioc, +		    "%s: driver page 1 read failed, allocating trace\n" +		    "and firmware diag buffers of default size\n", __func__); +		trace_size = fw_size = MPI3MR_DEFAULT_HDB_MAX_SZ; +		trace_dec_size = fw_dec_size = MPI3MR_DEFAULT_HDB_DEC_SZ; +		trace_min_size = fw_min_size = MPI3MR_DEFAULT_HDB_MIN_SZ; + +	} else { +		trace_size = driver_pg1.host_diag_trace_max_size * 1024; +		trace_dec_size = driver_pg1.host_diag_trace_decrement_size +			 * 1024; +		trace_min_size = driver_pg1.host_diag_trace_min_size * 1024; +		fw_size = driver_pg1.host_diag_fw_max_size * 1024; +		fw_dec_size = driver_pg1.host_diag_fw_decrement_size * 1024; +		fw_min_size = driver_pg1.host_diag_fw_min_size * 1024; +		dprint_init(mrioc, +		    "%s:trace diag buffer sizes read from driver\n" +		    "page1: maximum size = %dKB, decrement size = %dKB\n" +		    ", minimum size = %dKB\n", __func__, driver_pg1.host_diag_trace_max_size, +		    driver_pg1.host_diag_trace_decrement_size, +		    driver_pg1.host_diag_trace_min_size); +		dprint_init(mrioc, +		    "%s:firmware diag buffer sizes read from driver\n" +		    "page1: maximum size = %dKB, decrement size = %dKB\n" +		    ", minimum size = %dKB\n", __func__, driver_pg1.host_diag_fw_max_size, +		    driver_pg1.host_diag_fw_decrement_size, +		    driver_pg1.host_diag_fw_min_size); +		if ((trace_size == 0) && (fw_size == 0)) +			return; +	} + + +retry_trace: +	diag_buffer = &mrioc->diag_buffers[0]; +	diag_buffer->type = MPI3_DIAG_BUFFER_TYPE_TRACE; +	diag_buffer->status = MPI3MR_HDB_BUFSTATUS_NOT_ALLOCATED; +	if ((mrioc->facts.diag_trace_sz < trace_size) && (trace_size >= +		trace_min_size)) { +		if (!retry) +			dprint_init(mrioc, +			    "trying to allocate trace diag buffer of size = %dKB\n", +			    trace_size / 1024); +		if (mpi3mr_alloc_trace_buffer(mrioc, trace_size)) { +			retry = true; +			trace_size -= trace_dec_size; +			dprint_init(mrioc, "trace diag buffer allocation failed\n" +			"retrying smaller size %dKB\n", trace_size / 1024); +			goto retry_trace; +		} else +			diag_buffer->size = trace_size; +	} + +	retry = false; +retry_fw: + +	diag_buffer = &mrioc->diag_buffers[1]; + +	diag_buffer->type = MPI3_DIAG_BUFFER_TYPE_FW; +	diag_buffer->status = MPI3MR_HDB_BUFSTATUS_NOT_ALLOCATED; +	if ((mrioc->facts.diag_fw_sz < fw_size) && (fw_size >= fw_min_size)) { +		diag_buffer->addr = dma_alloc_coherent(&mrioc->pdev->dev, +		    fw_size, &diag_buffer->dma_addr, GFP_KERNEL); +		if (!retry) +			dprint_init(mrioc, +			    "%s:trying to allocate firmware diag buffer of size = %dKB\n", +			    __func__, fw_size / 1024); +		if (diag_buffer->addr) { +			dprint_init(mrioc, "%s:firmware diag buffer allocated successfully\n", +			    __func__); +			diag_buffer->size = fw_size; +		} else { +			retry = true; +			fw_size -= fw_dec_size; +			dprint_init(mrioc, "%s:trace diag buffer allocation failed,\n" +					"retrying smaller size %dKB\n", +					__func__, fw_size / 1024); +			goto retry_fw; +		} +	} +} + +/** + * mpi3mr_issue_diag_buf_post - Send diag buffer post req + * @mrioc: Adapter instance reference + * @diag_buffer: Diagnostic buffer descriptor + * + * Issue diagnostic buffer post MPI request through admin queue + * and wait for the completion of it or time out. + * + * Return: 0 on success, non-zero on failures. + */ +int mpi3mr_issue_diag_buf_post(struct mpi3mr_ioc *mrioc, +	struct diag_buffer_desc *diag_buffer) +{ +	struct mpi3_diag_buffer_post_request diag_buf_post_req; +	u8 prev_status; +	int retval = 0; + +	memset(&diag_buf_post_req, 0, sizeof(diag_buf_post_req)); +	mutex_lock(&mrioc->init_cmds.mutex); +	if (mrioc->init_cmds.state & MPI3MR_CMD_PENDING) { +		dprint_bsg_err(mrioc, "%s: command is in use\n", __func__); +		mutex_unlock(&mrioc->init_cmds.mutex); +		return -1; +	} +	mrioc->init_cmds.state = MPI3MR_CMD_PENDING; +	mrioc->init_cmds.is_waiting = 1; +	mrioc->init_cmds.callback = NULL; +	diag_buf_post_req.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_INITCMDS); +	diag_buf_post_req.function = MPI3_FUNCTION_DIAG_BUFFER_POST; +	diag_buf_post_req.type = diag_buffer->type; +	diag_buf_post_req.address = le64_to_cpu(diag_buffer->dma_addr); +	diag_buf_post_req.length = le32_to_cpu(diag_buffer->size); + +	dprint_bsg_info(mrioc, "%s: posting diag buffer type %d\n", __func__, +	    diag_buffer->type); +	prev_status = diag_buffer->status; +	diag_buffer->status = MPI3MR_HDB_BUFSTATUS_POSTED_UNPAUSED; +	init_completion(&mrioc->init_cmds.done); +	retval = mpi3mr_admin_request_post(mrioc, &diag_buf_post_req, +	    sizeof(diag_buf_post_req), 1); +	if (retval) { +		dprint_bsg_err(mrioc, "%s: admin request post failed\n", +		    __func__); +		goto out_unlock; +	} +	wait_for_completion_timeout(&mrioc->init_cmds.done, +	    (MPI3MR_INTADMCMD_TIMEOUT * HZ)); +	if (!(mrioc->init_cmds.state & MPI3MR_CMD_COMPLETE)) { +		mrioc->init_cmds.is_waiting = 0; +		dprint_bsg_err(mrioc, "%s: command timedout\n", __func__); +		mpi3mr_check_rh_fault_ioc(mrioc, +		    MPI3MR_RESET_FROM_DIAG_BUFFER_POST_TIMEOUT); +		retval = -1; +		goto out_unlock; +	} +	if ((mrioc->init_cmds.ioc_status & MPI3_IOCSTATUS_STATUS_MASK) +	    != MPI3_IOCSTATUS_SUCCESS) { +		dprint_bsg_err(mrioc, +		    "%s: command failed, buffer_type (%d) ioc_status(0x%04x) log_info(0x%08x)\n", +		    __func__, diag_buffer->type, +		    (mrioc->init_cmds.ioc_status & MPI3_IOCSTATUS_STATUS_MASK), +		    mrioc->init_cmds.ioc_loginfo); +		retval = -1; +		goto out_unlock; +	} +	dprint_bsg_info(mrioc, "%s: diag buffer type %d posted successfully\n", +	    __func__, diag_buffer->type); + +out_unlock: +	if (retval) +		diag_buffer->status = prev_status; +	mrioc->init_cmds.state = MPI3MR_CMD_NOTUSED; +	mutex_unlock(&mrioc->init_cmds.mutex); +	return retval; +} + +/** + * mpi3mr_post_diag_bufs - Post diag buffers to the controller + * @mrioc: Adapter instance reference + * + * This function calls helper function to post both trace and + * firmware buffers to the controller. + * + * Return: None + */ +int mpi3mr_post_diag_bufs(struct mpi3mr_ioc *mrioc) +{ +	u8 i; +	struct diag_buffer_desc *diag_buffer; + +	for (i = 0; i < MPI3MR_MAX_NUM_HDB; i++) { +		diag_buffer = &mrioc->diag_buffers[i]; +		if (!(diag_buffer->addr)) +			continue; +		if (mpi3mr_issue_diag_buf_post(mrioc, diag_buffer)) +			return -1; +	} +	return 0; +} + +/** + * mpi3mr_issue_diag_buf_release - Send diag buffer release req + * @mrioc: Adapter instance reference + * @diag_buffer: Diagnostic buffer descriptor + * + * Issue diagnostic buffer manage MPI request with release + * action request through admin queue and wait for the + * completion of it or time out. + * + * Return: 0 on success, non-zero on failures. + */ +int mpi3mr_issue_diag_buf_release(struct mpi3mr_ioc *mrioc, +	struct diag_buffer_desc *diag_buffer) +{ +	struct mpi3_diag_buffer_manage_request diag_buf_manage_req; +	int retval = 0; + +	if ((diag_buffer->status != MPI3MR_HDB_BUFSTATUS_POSTED_UNPAUSED) && +	    (diag_buffer->status != MPI3MR_HDB_BUFSTATUS_POSTED_PAUSED)) +		return retval; + +	memset(&diag_buf_manage_req, 0, sizeof(diag_buf_manage_req)); +	mutex_lock(&mrioc->init_cmds.mutex); +	if (mrioc->init_cmds.state & MPI3MR_CMD_PENDING) { +		dprint_reset(mrioc, "%s: command is in use\n", __func__); +		mutex_unlock(&mrioc->init_cmds.mutex); +		return -1; +	} +	mrioc->init_cmds.state = MPI3MR_CMD_PENDING; +	mrioc->init_cmds.is_waiting = 1; +	mrioc->init_cmds.callback = NULL; +	diag_buf_manage_req.host_tag = cpu_to_le16(MPI3MR_HOSTTAG_INITCMDS); +	diag_buf_manage_req.function = MPI3_FUNCTION_DIAG_BUFFER_MANAGE; +	diag_buf_manage_req.type = diag_buffer->type; +	diag_buf_manage_req.action = MPI3_DIAG_BUFFER_ACTION_RELEASE; + + +	dprint_reset(mrioc, "%s: releasing diag buffer type %d\n", __func__, +	    diag_buffer->type); +	init_completion(&mrioc->init_cmds.done); +	retval = mpi3mr_admin_request_post(mrioc, &diag_buf_manage_req, +	    sizeof(diag_buf_manage_req), 1); +	if (retval) { +		dprint_reset(mrioc, "%s: admin request post failed\n", __func__); +		mpi3mr_set_trigger_data_in_hdb(diag_buffer, +		    MPI3MR_HDB_TRIGGER_TYPE_UNKNOWN, NULL, 1); +		goto out_unlock; +	} +	wait_for_completion_timeout(&mrioc->init_cmds.done, +	    (MPI3MR_INTADMCMD_TIMEOUT * HZ)); +	if (!(mrioc->init_cmds.state & MPI3MR_CMD_COMPLETE)) { +		mrioc->init_cmds.is_waiting = 0; +		dprint_reset(mrioc, "%s: command timedout\n", __func__); +		mpi3mr_check_rh_fault_ioc(mrioc, +		    MPI3MR_RESET_FROM_DIAG_BUFFER_RELEASE_TIMEOUT); +		retval = -1; +		goto out_unlock; +	} +	if ((mrioc->init_cmds.ioc_status & MPI3_IOCSTATUS_STATUS_MASK) +	    != MPI3_IOCSTATUS_SUCCESS) { +		dprint_reset(mrioc, +		    "%s: command failed, buffer_type (%d) ioc_status(0x%04x) log_info(0x%08x)\n", +		    __func__, diag_buffer->type, +		    (mrioc->init_cmds.ioc_status & MPI3_IOCSTATUS_STATUS_MASK), +		    mrioc->init_cmds.ioc_loginfo); +		retval = -1; +		goto out_unlock; +	} +	dprint_reset(mrioc, "%s: diag buffer type %d released successfully\n", +	    __func__, diag_buffer->type); + +out_unlock: +	mrioc->init_cmds.state = MPI3MR_CMD_NOTUSED; +	mutex_unlock(&mrioc->init_cmds.mutex); +	return retval; +} + +/** + * mpi3mr_process_trigger - Generic HDB Trigger handler + * @mrioc: Adapter instance reference + * @trigger_type: Trigger type + * @trigger_data: Trigger data + * @trigger_flags: Trigger flags + * + * This function checks validity of HDB, triggers and based on + * trigger information, creates an event to be processed in the + * firmware event worker thread . + * + * This function should be called with trigger spinlock held + * + * Return: Nothing + */ +static void mpi3mr_process_trigger(struct mpi3mr_ioc *mrioc, u8 trigger_type, +	union mpi3mr_trigger_data *trigger_data, u8 trigger_flags) +{ +	struct trigger_event_data event_data; +	struct diag_buffer_desc *trace_hdb = NULL; +	struct diag_buffer_desc *fw_hdb = NULL; +	u64 global_trigger; + +	trace_hdb = mpi3mr_diag_buffer_for_type(mrioc, +	    MPI3_DIAG_BUFFER_TYPE_TRACE); +	if (trace_hdb && +	    (trace_hdb->status != MPI3MR_HDB_BUFSTATUS_POSTED_UNPAUSED) && +	    (trace_hdb->status != MPI3MR_HDB_BUFSTATUS_POSTED_PAUSED)) +		trace_hdb =  NULL; + +	fw_hdb = mpi3mr_diag_buffer_for_type(mrioc, MPI3_DIAG_BUFFER_TYPE_FW); + +	if (fw_hdb && +	    (fw_hdb->status != MPI3MR_HDB_BUFSTATUS_POSTED_UNPAUSED) && +	    (fw_hdb->status != MPI3MR_HDB_BUFSTATUS_POSTED_PAUSED)) +		fw_hdb = NULL; + +	if (mrioc->snapdump_trigger_active || (mrioc->fw_release_trigger_active +	    && mrioc->trace_release_trigger_active) || +	    (!trace_hdb && !fw_hdb) || (!mrioc->driver_pg2) || +	    ((trigger_type == MPI3MR_HDB_TRIGGER_TYPE_ELEMENT) +	     && (!mrioc->driver_pg2->num_triggers))) +		return; + +	memset(&event_data, 0, sizeof(event_data)); +	event_data.trigger_type = trigger_type; +	memcpy(&event_data.trigger_specific_data, trigger_data, +	    sizeof(*trigger_data)); +	global_trigger = le64_to_cpu(mrioc->driver_pg2->global_trigger); + +	if (global_trigger & MPI3_DRIVER2_GLOBALTRIGGER_SNAPDUMP_ENABLED) { +		event_data.snapdump = true; +		event_data.trace_hdb = trace_hdb; +		event_data.fw_hdb = fw_hdb; +		mrioc->snapdump_trigger_active = true; +	} else if (trigger_type == MPI3MR_HDB_TRIGGER_TYPE_GLOBAL) { +		if ((trace_hdb) && (global_trigger & +		    MPI3_DRIVER2_GLOBALTRIGGER_DIAG_TRACE_RELEASE) && +		    (!mrioc->trace_release_trigger_active)) { +			event_data.trace_hdb = trace_hdb; +			mrioc->trace_release_trigger_active = true; +		} +		if ((fw_hdb) && (global_trigger & +		    MPI3_DRIVER2_GLOBALTRIGGER_DIAG_FW_RELEASE) && +		    (!mrioc->fw_release_trigger_active)) { +			event_data.fw_hdb = fw_hdb; +			mrioc->fw_release_trigger_active = true; +		} +	} else if (trigger_type == MPI3MR_HDB_TRIGGER_TYPE_ELEMENT) { +		if ((trace_hdb) && (trigger_flags & +		    MPI3_DRIVER2_TRIGGER_FLAGS_DIAG_TRACE_RELEASE) && +		    (!mrioc->trace_release_trigger_active)) { +			event_data.trace_hdb = trace_hdb; +			mrioc->trace_release_trigger_active = true; +		} +		if ((fw_hdb) && (trigger_flags & +		    MPI3_DRIVER2_TRIGGER_FLAGS_DIAG_FW_RELEASE) && +		    (!mrioc->fw_release_trigger_active)) { +			event_data.fw_hdb = fw_hdb; +			mrioc->fw_release_trigger_active = true; +		} +	} + +	if (event_data.trace_hdb || event_data.fw_hdb) +		mpi3mr_hdb_trigger_data_event(mrioc, &event_data); +} + +/** + * mpi3mr_global_trigger - Global HDB trigger handler + * @mrioc: Adapter instance reference + * @trigger_data: Trigger data + * + * This function checks whether the given global trigger is + * enabled in the driver page 2 and if so calls generic trigger + * handler to queue event for HDB release. + * + * Return: Nothing + */ +void mpi3mr_global_trigger(struct mpi3mr_ioc *mrioc, u64 trigger_data) +{ +	unsigned long flags; +	union mpi3mr_trigger_data trigger_specific_data; + +	spin_lock_irqsave(&mrioc->trigger_lock, flags); +	if (le64_to_cpu(mrioc->driver_pg2->global_trigger) & trigger_data) { +		memset(&trigger_specific_data, 0, +		    sizeof(trigger_specific_data)); +		trigger_specific_data.global = trigger_data; +		mpi3mr_process_trigger(mrioc, MPI3MR_HDB_TRIGGER_TYPE_GLOBAL, +		    &trigger_specific_data, 0); +	} +	spin_unlock_irqrestore(&mrioc->trigger_lock, flags); +} + +/** + * mpi3mr_scsisense_trigger - SCSI sense HDB trigger handler + * @mrioc: Adapter instance reference + * @sensekey: Sense Key + * @asc: Additional Sense Code + * @ascq: Additional Sense Code Qualifier + * + * This function compares SCSI sense trigger values with driver + * page 2 values and calls generic trigger handler to release + * HDBs if match found + * + * Return: Nothing + */ +void mpi3mr_scsisense_trigger(struct mpi3mr_ioc *mrioc, u8 sensekey, u8 asc, +	u8 ascq) +{ +	struct mpi3_driver2_trigger_scsi_sense *scsi_sense_trigger = NULL; +	u64 i = 0; +	unsigned long flags; +	u8 num_triggers, trigger_flags; + +	if (mrioc->scsisense_trigger_present) { +		spin_lock_irqsave(&mrioc->trigger_lock, flags); +		scsi_sense_trigger = (struct mpi3_driver2_trigger_scsi_sense *) +			mrioc->driver_pg2->trigger; +		num_triggers = mrioc->driver_pg2->num_triggers; +		for (i = 0; i < num_triggers; i++, scsi_sense_trigger++) { +			if (scsi_sense_trigger->type != +			    MPI3_DRIVER2_TRIGGER_TYPE_SCSI_SENSE) +				continue; +			if (!(scsi_sense_trigger->sense_key == +			    MPI3_DRIVER2_TRIGGER_SCSI_SENSE_SENSE_KEY_MATCH_ALL +			      || scsi_sense_trigger->sense_key == sensekey)) +				continue; +			if (!(scsi_sense_trigger->asc == +			    MPI3_DRIVER2_TRIGGER_SCSI_SENSE_ASC_MATCH_ALL || +			    scsi_sense_trigger->asc == asc)) +				continue; +			if (!(scsi_sense_trigger->ascq == +			    MPI3_DRIVER2_TRIGGER_SCSI_SENSE_ASCQ_MATCH_ALL || +			    scsi_sense_trigger->ascq == ascq)) +				continue; +			trigger_flags = scsi_sense_trigger->flags; +			mpi3mr_process_trigger(mrioc, +			    MPI3MR_HDB_TRIGGER_TYPE_ELEMENT, +			    (union mpi3mr_trigger_data *)scsi_sense_trigger, +			    trigger_flags); +			break; +		} +		spin_unlock_irqrestore(&mrioc->trigger_lock, flags); +	} +} + +/** + * mpi3mr_event_trigger - MPI event HDB trigger handler + * @mrioc: Adapter instance reference + * @event: MPI Event + * + * This function compares event trigger values with driver page + * 2 values and calls generic trigger handler to release + * HDBs if match found. + * + * Return: Nothing + */ +void mpi3mr_event_trigger(struct mpi3mr_ioc *mrioc, u8 event) +{ +	struct mpi3_driver2_trigger_event *event_trigger = NULL; +	u64 i = 0; +	unsigned long flags; +	u8 num_triggers, trigger_flags; + +	if (mrioc->event_trigger_present) { +		spin_lock_irqsave(&mrioc->trigger_lock, flags); +		event_trigger = (struct mpi3_driver2_trigger_event *) +			mrioc->driver_pg2->trigger; +		num_triggers = mrioc->driver_pg2->num_triggers; + +		for (i = 0; i < num_triggers; i++, event_trigger++) { +			if (event_trigger->type != +			    MPI3_DRIVER2_TRIGGER_TYPE_EVENT) +				continue; +			if (event_trigger->event != event) +				continue; +			trigger_flags = event_trigger->flags; +			mpi3mr_process_trigger(mrioc, +			    MPI3MR_HDB_TRIGGER_TYPE_ELEMENT, +			    (union mpi3mr_trigger_data *)event_trigger, +			    trigger_flags); +			break; +		} +		spin_unlock_irqrestore(&mrioc->trigger_lock, flags); +	} +} + +/** + * mpi3mr_reply_trigger - MPI Reply HDB trigger handler + * @mrioc: Adapter instance reference + * @ioc_status: Masked value of IOC Status from MPI Reply + * @ioc_loginfo: IOC Log Info from MPI Reply + * + * This function compares IOC status and IOC log info trigger + * values with driver page 2 values and calls generic trigger + * handler to release HDBs if match found. + * + * Return: Nothing + */ +void mpi3mr_reply_trigger(struct mpi3mr_ioc *mrioc, u16 ioc_status, +	u32 ioc_loginfo) +{ +	struct mpi3_driver2_trigger_reply *reply_trigger = NULL; +	u64 i = 0; +	unsigned long flags; +	u8 num_triggers, trigger_flags; + +	if (mrioc->reply_trigger_present) { +		spin_lock_irqsave(&mrioc->trigger_lock, flags); +		reply_trigger = (struct mpi3_driver2_trigger_reply *) +			mrioc->driver_pg2->trigger; +		num_triggers = mrioc->driver_pg2->num_triggers; +		for (i = 0; i < num_triggers; i++, reply_trigger++) { +			if (reply_trigger->type != +			    MPI3_DRIVER2_TRIGGER_TYPE_REPLY) +				continue; +			if ((le16_to_cpu(reply_trigger->ioc_status) != +			     ioc_status) +			    && (le16_to_cpu(reply_trigger->ioc_status) != +			    MPI3_DRIVER2_TRIGGER_REPLY_IOCSTATUS_MATCH_ALL)) +				continue; +			if ((le32_to_cpu(reply_trigger->ioc_log_info) != +			    (le32_to_cpu(reply_trigger->ioc_log_info_mask) & +			     ioc_loginfo))) +				continue; +			trigger_flags = reply_trigger->flags; +			mpi3mr_process_trigger(mrioc, +			    MPI3MR_HDB_TRIGGER_TYPE_ELEMENT, +			    (union mpi3mr_trigger_data *)reply_trigger, +			    trigger_flags); +			break; +		} +		spin_unlock_irqrestore(&mrioc->trigger_lock, flags); +	} +} + +/** + * mpi3mr_get_num_trigger - Gets number of HDB triggers + * @mrioc: Adapter instance reference + * @num_triggers: Number of triggers + * @page_action: Page action + * + * This function reads number of triggers by reading driver page + * 2 + * + * Return: 0 on success and proper error codes on failure + */ +static int mpi3mr_get_num_trigger(struct mpi3mr_ioc *mrioc, u8 *num_triggers, +	u8 page_action) +{ +	struct mpi3_driver_page2 drvr_page2; +	int retval = 0; + +	*num_triggers = 0; + +	retval = mpi3mr_cfg_get_driver_pg2(mrioc, &drvr_page2, +	    sizeof(struct mpi3_driver_page2), page_action); + +	if (retval) { +		dprint_init(mrioc, "%s: driver page 2 read failed\n", __func__); +		return retval; +	} +	*num_triggers = drvr_page2.num_triggers; +	return retval; +} + +/** + * mpi3mr_refresh_trigger - Handler for Refresh trigger BSG + * @mrioc: Adapter instance reference + * @page_action: Page action + * + * This function caches the driver page 2 in the driver's memory + * by reading driver page 2 from the controller for a given page + * type and updates the HDB trigger values + * + * Return: 0 on success and proper error codes on failure + */ +int mpi3mr_refresh_trigger(struct mpi3mr_ioc *mrioc, u8 page_action) +{ +	u16 pg_sz = sizeof(struct mpi3_driver_page2); +	struct mpi3_driver_page2 *drvr_page2 = NULL; +	u8 trigger_type, num_triggers; +	int retval; +	int i = 0; +	unsigned long flags; + +	retval = mpi3mr_get_num_trigger(mrioc, &num_triggers, page_action); + +	if (retval) +		goto out; + +	pg_sz = offsetof(struct mpi3_driver_page2, trigger) + +		(num_triggers * sizeof(union mpi3_driver2_trigger_element)); +	drvr_page2 = kzalloc(pg_sz, GFP_KERNEL); +	if (!drvr_page2) { +		retval = -ENOMEM; +		goto out; +	} + +	retval = mpi3mr_cfg_get_driver_pg2(mrioc, drvr_page2, pg_sz, page_action); +	if (retval) { +		dprint_init(mrioc, "%s: driver page 2 read failed\n", __func__); +		kfree(drvr_page2); +		goto out; +	} +	spin_lock_irqsave(&mrioc->trigger_lock, flags); +	kfree(mrioc->driver_pg2); +	mrioc->driver_pg2 = drvr_page2; +	mrioc->reply_trigger_present = false; +	mrioc->event_trigger_present = false; +	mrioc->scsisense_trigger_present = false; + +	for (i = 0; (i < mrioc->driver_pg2->num_triggers); i++) { +		trigger_type = mrioc->driver_pg2->trigger[i].event.type; +		switch (trigger_type) { +		case MPI3_DRIVER2_TRIGGER_TYPE_REPLY: +			mrioc->reply_trigger_present = true; +			break; +		case MPI3_DRIVER2_TRIGGER_TYPE_EVENT: +			mrioc->event_trigger_present = true; +			break; +		case MPI3_DRIVER2_TRIGGER_TYPE_SCSI_SENSE: +			mrioc->scsisense_trigger_present = true; +			break; +		default: +			break; +		} +	} +	spin_unlock_irqrestore(&mrioc->trigger_lock, flags); +out: +	return retval; +} + +/** + * mpi3mr_release_diag_bufs - Release diag buffers + * @mrioc: Adapter instance reference + * @skip_rel_action: Skip release action and set buffer state + * + * This function calls helper function to release both trace and + * firmware buffers from the controller. + * + * Return: None + */ +void mpi3mr_release_diag_bufs(struct mpi3mr_ioc *mrioc, u8 skip_rel_action) +{ +	u8 i; +	struct diag_buffer_desc *diag_buffer; + +	for (i = 0; i < MPI3MR_MAX_NUM_HDB; i++) { +		diag_buffer = &mrioc->diag_buffers[i]; +		if (!(diag_buffer->addr)) +			continue; +		if (diag_buffer->status == MPI3MR_HDB_BUFSTATUS_RELEASED) +			continue; +		if (!skip_rel_action) +			mpi3mr_issue_diag_buf_release(mrioc, diag_buffer); +		diag_buffer->status = MPI3MR_HDB_BUFSTATUS_RELEASED; +		atomic64_inc(&event_counter); +	} +} + +/** + * mpi3mr_set_trigger_data_in_hdb - Updates HDB trigger type and + * trigger data + * + * @hdb: HDB pointer + * @type: Trigger type + * @data: Trigger data + * @force: Trigger overwrite flag + * @trigger_data: Pointer to trigger data information + * + * Updates trigger type and trigger data based on parameter + * passed to this function + * + * Return: Nothing + */ +void mpi3mr_set_trigger_data_in_hdb(struct diag_buffer_desc *hdb, +	u8 type, union mpi3mr_trigger_data *trigger_data, bool force) +{ +	if ((!force) && (hdb->trigger_type != MPI3MR_HDB_TRIGGER_TYPE_UNKNOWN)) +		return; +	hdb->trigger_type = type; +	if (!trigger_data) +		memset(&hdb->trigger_data, 0, sizeof(*trigger_data)); +	else +		memcpy(&hdb->trigger_data, trigger_data, sizeof(*trigger_data)); +} + +/** + * mpi3mr_set_trigger_data_in_all_hdb - Updates HDB trigger type + * and trigger data for all HDB + * + * @mrioc: Adapter instance reference + * @type: Trigger type + * @data: Trigger data + * @force: Trigger overwrite flag + * @trigger_data: Pointer to trigger data information + * + * Updates trigger type and trigger data based on parameter + * passed to this function + * + * Return: Nothing + */ +void mpi3mr_set_trigger_data_in_all_hdb(struct mpi3mr_ioc *mrioc, +	u8 type, union mpi3mr_trigger_data *trigger_data, bool force) +{ +	struct diag_buffer_desc *hdb = NULL; + +	hdb = mpi3mr_diag_buffer_for_type(mrioc, MPI3_DIAG_BUFFER_TYPE_TRACE); +	if (hdb) +		mpi3mr_set_trigger_data_in_hdb(hdb, type, trigger_data, force); +	hdb = mpi3mr_diag_buffer_for_type(mrioc, MPI3_DIAG_BUFFER_TYPE_FW); +	if (hdb) +		mpi3mr_set_trigger_data_in_hdb(hdb, type, trigger_data, force); +} + +/** + * mpi3mr_hdbstatuschg_evt_th - HDB status change evt tophalf + * @mrioc: Adapter instance reference + * @event_reply: event data + * + * Modifies the status of the applicable diag buffer descriptors + * + * Return: Nothing + */ +void mpi3mr_hdbstatuschg_evt_th(struct mpi3mr_ioc *mrioc, +	struct mpi3_event_notification_reply *event_reply) +{ +	struct mpi3_event_data_diag_buffer_status_change *evtdata; +	struct diag_buffer_desc *diag_buffer; + +	evtdata = (struct mpi3_event_data_diag_buffer_status_change *) +	    event_reply->event_data; + +	diag_buffer = mpi3mr_diag_buffer_for_type(mrioc, evtdata->type); +	if (!diag_buffer) +		return; +	if ((diag_buffer->status != MPI3MR_HDB_BUFSTATUS_POSTED_UNPAUSED) && +	    (diag_buffer->status != MPI3MR_HDB_BUFSTATUS_POSTED_PAUSED)) +		return; +	switch (evtdata->reason_code) { +	case MPI3_EVENT_DIAG_BUFFER_STATUS_CHANGE_RC_RELEASED: +	{ +		diag_buffer->status = MPI3MR_HDB_BUFSTATUS_RELEASED; +		mpi3mr_set_trigger_data_in_hdb(diag_buffer, +		    MPI3MR_HDB_TRIGGER_TYPE_FW_RELEASED, NULL, 0); +		atomic64_inc(&event_counter); +		break; +	} +	case MPI3_EVENT_DIAG_BUFFER_STATUS_CHANGE_RC_RESUMED: +	{ +		diag_buffer->status = MPI3MR_HDB_BUFSTATUS_POSTED_UNPAUSED; +		break; +	} +	case MPI3_EVENT_DIAG_BUFFER_STATUS_CHANGE_RC_PAUSED: +	{ +		diag_buffer->status = MPI3MR_HDB_BUFSTATUS_POSTED_PAUSED; +		break; +	} +	default: +		dprint_event_th(mrioc, "%s: unknown reason_code(%d)\n", +		    __func__, evtdata->reason_code); +		break; +	} +} + +/** + * mpi3mr_diag_buffer_for_type - returns buffer desc for type + * @mrioc: Adapter instance reference + * @buf_type: Diagnostic buffer type + * + * Identifies matching diag descriptor from mrioc for given diag + * buffer type. + * + * Return: diag buffer descriptor on success, NULL on failures. + */ + +struct diag_buffer_desc * +mpi3mr_diag_buffer_for_type(struct mpi3mr_ioc *mrioc, u8 buf_type) +{ +	u8 i; + +	for (i = 0; i < MPI3MR_MAX_NUM_HDB; i++) { +		if (mrioc->diag_buffers[i].type == buf_type) +			return &mrioc->diag_buffers[i]; +	} +	return NULL; +} + +/**   * mpi3mr_bsg_pel_abort - sends PEL abort request   * @mrioc: Adapter instance reference   * @@ -31,7 +846,7 @@ static int mpi3mr_bsg_pel_abort(struct mpi3mr_ioc *mrioc)  		dprint_bsg_err(mrioc, "%s: reset in progress\n", __func__);  		return -1;  	} -	if (mrioc->stop_bsgs) { +	if (mrioc->stop_bsgs || mrioc->block_on_pci_err) {  		dprint_bsg_err(mrioc, "%s: bsgs are blocked\n", __func__);  		return -1;  	} @@ -126,6 +941,259 @@ static struct mpi3mr_ioc *mpi3mr_bsg_verify_adapter(int ioc_number)  }  /** + * mpi3mr_bsg_refresh_hdb_triggers - Refresh HDB trigger data + * @mrioc: Adapter instance reference + * @job: BSG Job pointer + * + * This function reads the controller trigger config page as + * defined by the input page type and refreshes the driver's + * local trigger information structures with the controller's + * config page data. + * + * Return: 0 on success and proper error codes on failure + */ +static long +mpi3mr_bsg_refresh_hdb_triggers(struct mpi3mr_ioc *mrioc, +				struct bsg_job *job) +{ +	struct mpi3mr_bsg_out_refresh_hdb_triggers refresh_triggers; +	uint32_t data_out_sz; +	u8 page_action; +	long rval = -EINVAL; + +	data_out_sz = job->request_payload.payload_len; + +	if (data_out_sz != sizeof(refresh_triggers)) { +		dprint_bsg_err(mrioc, "%s: invalid size argument\n", +		    __func__); +		return rval; +	} + +	if (mrioc->unrecoverable) { +		dprint_bsg_err(mrioc, "%s: unrecoverable controller\n", +		    __func__); +		return -EFAULT; +	} +	if (mrioc->reset_in_progress) { +		dprint_bsg_err(mrioc, "%s: reset in progress\n", __func__); +		return -EAGAIN; +	} + +	sg_copy_to_buffer(job->request_payload.sg_list, +	    job->request_payload.sg_cnt, +	    &refresh_triggers, sizeof(refresh_triggers)); + +	switch (refresh_triggers.page_type) { +	case MPI3MR_HDB_REFRESH_TYPE_CURRENT: +		page_action = MPI3_CONFIG_ACTION_READ_CURRENT; +		break; +	case MPI3MR_HDB_REFRESH_TYPE_DEFAULT: +		page_action = MPI3_CONFIG_ACTION_READ_DEFAULT; +		break; +	case MPI3MR_HDB_HDB_REFRESH_TYPE_PERSISTENT: +		page_action = MPI3_CONFIG_ACTION_READ_PERSISTENT; +		break; +	default: +		dprint_bsg_err(mrioc, +		    "%s: unsupported refresh trigger, page_type %d\n", +		    __func__, refresh_triggers.page_type); +		return rval; +	} +	rval = mpi3mr_refresh_trigger(mrioc, page_action); + +	return rval; +} + +/** + * mpi3mr_bsg_upload_hdb - Upload a specific HDB to user space + * @mrioc: Adapter instance reference + * @job: BSG Job pointer + * + * Return: 0 on success and proper error codes on failure + */ +static long mpi3mr_bsg_upload_hdb(struct mpi3mr_ioc *mrioc, +				  struct bsg_job *job) +{ +	struct mpi3mr_bsg_out_upload_hdb upload_hdb; +	struct diag_buffer_desc *diag_buffer; +	uint32_t data_out_size; +	uint32_t data_in_size; + +	data_out_size = job->request_payload.payload_len; +	data_in_size = job->reply_payload.payload_len; + +	if (data_out_size != sizeof(upload_hdb)) { +		dprint_bsg_err(mrioc, "%s: invalid size argument\n", +		    __func__); +		return -EINVAL; +	} + +	sg_copy_to_buffer(job->request_payload.sg_list, +			  job->request_payload.sg_cnt, +			  &upload_hdb, sizeof(upload_hdb)); + +	if ((!upload_hdb.length) || (data_in_size != upload_hdb.length)) { +		dprint_bsg_err(mrioc, "%s: invalid length argument\n", +		    __func__); +		return -EINVAL; +	} +	diag_buffer = mpi3mr_diag_buffer_for_type(mrioc, upload_hdb.buf_type); +	if ((!diag_buffer) || (!diag_buffer->addr)) { +		dprint_bsg_err(mrioc, "%s: invalid buffer type %d\n", +		    __func__, upload_hdb.buf_type); +		return -EINVAL; +	} + +	if ((diag_buffer->status != MPI3MR_HDB_BUFSTATUS_RELEASED) && +	    (diag_buffer->status != MPI3MR_HDB_BUFSTATUS_POSTED_PAUSED)) { +		dprint_bsg_err(mrioc, +		    "%s: invalid buffer status %d for type %d\n", +		    __func__, diag_buffer->status, upload_hdb.buf_type); +		return -EINVAL; +	} + +	if ((upload_hdb.start_offset + upload_hdb.length) > diag_buffer->size) { +		dprint_bsg_err(mrioc, +		    "%s: invalid start offset %d, length %d for type %d\n", +		    __func__, upload_hdb.start_offset, upload_hdb.length, +		    upload_hdb.buf_type); +		return -EINVAL; +	} +	sg_copy_from_buffer(job->reply_payload.sg_list, +			    job->reply_payload.sg_cnt, +	    (diag_buffer->addr + upload_hdb.start_offset), +	    data_in_size); +	return 0; +} + +/** + * mpi3mr_bsg_repost_hdb - Re-post HDB + * @mrioc: Adapter instance reference + * @job: BSG job pointer + * + * This function retrieves the HDB descriptor corresponding to a + * given buffer type and if the HDB is in released status then + * posts the HDB with the firmware. + * + * Return: 0 on success and proper error codes on failure + */ +static long mpi3mr_bsg_repost_hdb(struct mpi3mr_ioc *mrioc, +				  struct bsg_job *job) +{ +	struct mpi3mr_bsg_out_repost_hdb repost_hdb; +	struct diag_buffer_desc *diag_buffer; +	uint32_t data_out_sz; + +	data_out_sz = job->request_payload.payload_len; + +	if (data_out_sz != sizeof(repost_hdb)) { +		dprint_bsg_err(mrioc, "%s: invalid size argument\n", +		    __func__); +		return -EINVAL; +	} +	if (mrioc->unrecoverable) { +		dprint_bsg_err(mrioc, "%s: unrecoverable controller\n", +		    __func__); +		return -EFAULT; +	} +	if (mrioc->reset_in_progress) { +		dprint_bsg_err(mrioc, "%s: reset in progress\n", __func__); +		return -EAGAIN; +	} + +	sg_copy_to_buffer(job->request_payload.sg_list, +			  job->request_payload.sg_cnt, +			  &repost_hdb, sizeof(repost_hdb)); + +	diag_buffer = mpi3mr_diag_buffer_for_type(mrioc, repost_hdb.buf_type); +	if ((!diag_buffer) || (!diag_buffer->addr)) { +		dprint_bsg_err(mrioc, "%s: invalid buffer type %d\n", +		    __func__, repost_hdb.buf_type); +		return -EINVAL; +	} + +	if (diag_buffer->status != MPI3MR_HDB_BUFSTATUS_RELEASED) { +		dprint_bsg_err(mrioc, +		    "%s: invalid buffer status %d for type %d\n", +		    __func__, diag_buffer->status, repost_hdb.buf_type); +		return -EINVAL; +	} + +	if (mpi3mr_issue_diag_buf_post(mrioc, diag_buffer)) { +		dprint_bsg_err(mrioc, "%s: post failed for type %d\n", +		    __func__, repost_hdb.buf_type); +		return -EFAULT; +	} +	mpi3mr_set_trigger_data_in_hdb(diag_buffer, +	    MPI3MR_HDB_TRIGGER_TYPE_UNKNOWN, NULL, 1); + +	return 0; +} + +/** + * mpi3mr_bsg_query_hdb - Handler for query HDB command + * @mrioc: Adapter instance reference + * @job: BSG job pointer + * + * This function prepares and copies the host diagnostic buffer + * entries to the user buffer. + * + * Return: 0 on success and proper error codes on failure + */ +static long mpi3mr_bsg_query_hdb(struct mpi3mr_ioc *mrioc, +				 struct bsg_job *job) +{ +	long rval = 0; +	struct mpi3mr_bsg_in_hdb_status *hbd_status; +	struct mpi3mr_hdb_entry *hbd_status_entry; +	u32 length, min_length; +	u8 i; +	struct diag_buffer_desc *diag_buffer; +	uint32_t data_in_sz = 0; + +	data_in_sz = job->request_payload.payload_len; + +	length = (sizeof(*hbd_status) + ((MPI3MR_MAX_NUM_HDB - 1) * +		    sizeof(*hbd_status_entry))); +	hbd_status = kmalloc(length, GFP_KERNEL); +	if (!hbd_status) +		return -ENOMEM; +	hbd_status_entry = &hbd_status->entry[0]; + +	hbd_status->num_hdb_types = MPI3MR_MAX_NUM_HDB; +	for (i = 0; i < MPI3MR_MAX_NUM_HDB; i++) { +		diag_buffer = &mrioc->diag_buffers[i]; +		hbd_status_entry->buf_type = diag_buffer->type; +		hbd_status_entry->status = diag_buffer->status; +		hbd_status_entry->trigger_type = diag_buffer->trigger_type; +		memcpy(&hbd_status_entry->trigger_data, +		    &diag_buffer->trigger_data, +		    sizeof(hbd_status_entry->trigger_data)); +		hbd_status_entry->size = (diag_buffer->size / 1024); +		hbd_status_entry++; +	} +	hbd_status->element_trigger_format = +		MPI3MR_HDB_QUERY_ELEMENT_TRIGGER_FORMAT_DATA; + +	if (data_in_sz < 4) { +		dprint_bsg_err(mrioc, "%s: invalid size passed\n", __func__); +		rval = -EINVAL; +		goto out; +	} +	min_length = min(data_in_sz, length); +	if (job->request_payload.payload_len >= min_length) { +		sg_copy_from_buffer(job->request_payload.sg_list, +				    job->request_payload.sg_cnt, +				    hbd_status, min_length); +		rval = 0; +	} +out: +	kfree(hbd_status); +	return rval; +} + + +/**   * mpi3mr_enable_logdata - Handler for log data enable   * @mrioc: Adapter instance reference   * @job: BSG job reference @@ -424,6 +1492,9 @@ static long mpi3mr_bsg_adp_reset(struct mpi3mr_ioc *mrioc,  		goto out;  	} +	if (mrioc->unrecoverable || mrioc->block_on_pci_err) +		return -EINVAL; +  	sg_copy_to_buffer(job->request_payload.sg_list,  			  job->request_payload.sg_cnt,  			  &adpreset, sizeof(adpreset)); @@ -553,6 +1624,18 @@ static long mpi3mr_bsg_process_drv_cmds(struct bsg_job *job)  	case MPI3MR_DRVBSG_OPCODE_PELENABLE:  		rval = mpi3mr_bsg_pel_enable(mrioc, job);  		break; +	case MPI3MR_DRVBSG_OPCODE_QUERY_HDB: +		rval = mpi3mr_bsg_query_hdb(mrioc, job); +		break; +	case MPI3MR_DRVBSG_OPCODE_REPOST_HDB: +		rval = mpi3mr_bsg_repost_hdb(mrioc, job); +		break; +	case MPI3MR_DRVBSG_OPCODE_UPLOAD_HDB: +		rval = mpi3mr_bsg_upload_hdb(mrioc, job); +		break; +	case MPI3MR_DRVBSG_OPCODE_REFRESH_HDB_TRIGGERS: +		rval = mpi3mr_bsg_refresh_hdb_triggers(mrioc, job); +		break;  	case MPI3MR_DRVBSG_OPCODE_UNKNOWN:  	default:  		pr_err("%s: unsupported driver command opcode %d\n", @@ -1495,7 +2578,7 @@ static long mpi3mr_bsg_process_mpt_cmds(struct bsg_job *job)  		mutex_unlock(&mrioc->bsg_cmds.mutex);  		goto out;  	} -	if (mrioc->stop_bsgs) { +	if (mrioc->stop_bsgs || mrioc->block_on_pci_err) {  		dprint_bsg_err(mrioc, "%s: bsgs are blocked\n", __func__);  		rval = -EAGAIN;  		mutex_unlock(&mrioc->bsg_cmds.mutex); @@ -2028,7 +3111,8 @@ adp_state_show(struct device *dev, struct device_attribute *attr,  	ioc_state = mpi3mr_get_iocstate(mrioc);  	if (ioc_state == MRIOC_STATE_UNRECOVERABLE)  		adp_state = MPI3MR_BSG_ADPSTATE_UNRECOVERABLE; -	else if ((mrioc->reset_in_progress) || (mrioc->stop_bsgs)) +	else if (mrioc->reset_in_progress || mrioc->stop_bsgs || +		 mrioc->block_on_pci_err)  		adp_state = MPI3MR_BSG_ADPSTATE_IN_RESET;  	else if (ioc_state == MRIOC_STATE_FAULT)  		adp_state = MPI3MR_BSG_ADPSTATE_FAULT; @@ -2163,10 +3247,72 @@ persistent_id_show(struct device *dev, struct device_attribute *attr,  }  static DEVICE_ATTR_RO(persistent_id); +/** + * sas_ncq_prio_supported_show - Indicate if device supports NCQ priority + * @dev: pointer to embedded device + * @attr: sas_ncq_prio_supported attribute descriptor + * @buf: the buffer returned + * + * A sysfs 'read-only' sdev attribute, only works with SATA devices + */ +static ssize_t +sas_ncq_prio_supported_show(struct device *dev, +			    struct device_attribute *attr, char *buf) +{ +	struct scsi_device *sdev = to_scsi_device(dev); + +	return sysfs_emit(buf, "%d\n", sas_ata_ncq_prio_supported(sdev)); +} +static DEVICE_ATTR_RO(sas_ncq_prio_supported); + +/** + * sas_ncq_prio_enable_show - send prioritized io commands to device + * @dev: pointer to embedded device + * @attr: sas_ncq_prio_enable attribute descriptor + * @buf: the buffer returned + * + * A sysfs 'read/write' sdev attribute, only works with SATA devices + */ +static ssize_t +sas_ncq_prio_enable_show(struct device *dev, +				 struct device_attribute *attr, char *buf) +{ +	struct scsi_device *sdev = to_scsi_device(dev); +	struct mpi3mr_sdev_priv_data *sdev_priv_data =  sdev->hostdata; + +	if (!sdev_priv_data) +		return 0; + +	return sysfs_emit(buf, "%d\n", sdev_priv_data->ncq_prio_enable); +} + +static ssize_t +sas_ncq_prio_enable_store(struct device *dev, +				  struct device_attribute *attr, +				  const char *buf, size_t count) +{ +	struct scsi_device *sdev = to_scsi_device(dev); +	struct mpi3mr_sdev_priv_data *sdev_priv_data =  sdev->hostdata; +	bool ncq_prio_enable = 0; + +	if (kstrtobool(buf, &ncq_prio_enable)) +		return -EINVAL; + +	if (!sas_ata_ncq_prio_supported(sdev)) +		return -EINVAL; + +	sdev_priv_data->ncq_prio_enable = ncq_prio_enable; + +	return strlen(buf); +} +static DEVICE_ATTR_RW(sas_ncq_prio_enable); +  static struct attribute *mpi3mr_dev_attrs[] = {  	&dev_attr_sas_address.attr,  	&dev_attr_device_handle.attr,  	&dev_attr_persistent_id.attr, +	&dev_attr_sas_ncq_prio_supported.attr, +	&dev_attr_sas_ncq_prio_enable.attr,  	NULL,  }; |