RE: [PATCH V3 3/4] scsi: ufs: Fix device and host reset methods

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, July 24, 2013, Sujit Reddy Thumma wrote:
> On 7/23/2013 1:57 PM, Seungwon Jeon wrote:
> > On Sat, July 20, 2013, Sujit Reddy Thumma wrote:
> >> On 7/19/2013 7:27 PM, Seungwon Jeon wrote:
> >>> On Tue, July 09, 2013, Sujit Reddy Thumma wrote:
> >>>> As of now SCSI initiated error handling is broken because,
> >>>> the reset APIs don't try to bring back the device initialized and
> >>>> ready for further transfers.
> >>>>
> >>>> In case of timeouts, the scsi error handler takes care of handling aborts
> >>>> and resets. Improve the error handling in such scenario by resetting the
> >>>> device and host and re-initializing them in proper manner.
> >>>>
> >>>> Signed-off-by: Sujit Reddy Thumma <sthumma@xxxxxxxxxxxxxx>
> >>>> ---
> >>>>    drivers/scsi/ufs/ufshcd.c |  467 +++++++++++++++++++++++++++++++++++++++------
> >>>>    drivers/scsi/ufs/ufshcd.h |    2 +
> >>>>    2 files changed, 411 insertions(+), 58 deletions(-)
> >>>>
> >>>> diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
> >>>> index 51ce096..b4c9910 100644
> >>>> --- a/drivers/scsi/ufs/ufshcd.c
> >>>> +++ b/drivers/scsi/ufs/ufshcd.c
> >>>> @@ -69,9 +69,15 @@ enum {
> >>>>
> >>>>    /* UFSHCD states */
> >>>>    enum {
> >>>> -	UFSHCD_STATE_OPERATIONAL,
> >>>>    	UFSHCD_STATE_RESET,
> >>>>    	UFSHCD_STATE_ERROR,
> >>>> +	UFSHCD_STATE_OPERATIONAL,
> >>>> +};
> >>>> +
> >>>> +/* UFSHCD error handling flags */
> >>>> +enum {
> >>>> +	UFSHCD_EH_HOST_RESET_PENDING = (1 << 0),
> >>>> +	UFSHCD_EH_DEVICE_RESET_PENDING = (1 << 1),
> >>>>    };
> >>>>
> >>>>    /* Interrupt configuration options */
> >>>> @@ -87,6 +93,22 @@ enum {
> >>>>    	INT_AGGR_CONFIG,
> >>>>    };
> >>>>
> >>>> +#define ufshcd_set_device_reset_pending(h) \
> >>>> +	(h->eh_flags |= UFSHCD_EH_DEVICE_RESET_PENDING)
> >>>> +#define ufshcd_set_host_reset_pending(h) \
> >>>> +	(h->eh_flags |= UFSHCD_EH_HOST_RESET_PENDING)
> >>>> +#define ufshcd_device_reset_pending(h) \
> >>>> +	(h->eh_flags & UFSHCD_EH_DEVICE_RESET_PENDING)
> >>>> +#define ufshcd_host_reset_pending(h) \
> >>>> +	(h->eh_flags & UFSHCD_EH_HOST_RESET_PENDING)
> >>>> +#define ufshcd_clear_device_reset_pending(h) \
> >>>> +	(h->eh_flags &= ~UFSHCD_EH_DEVICE_RESET_PENDING)
> >>>> +#define ufshcd_clear_host_reset_pending(h) \
> >>>> +	(h->eh_flags &= ~UFSHCD_EH_HOST_RESET_PENDING)
> >>>> +
> >>>> +static void ufshcd_tmc_handler(struct ufs_hba *hba);
> >>>> +static void ufshcd_async_scan(void *data, async_cookie_t cookie);
> >>>> +
> >>>>    /*
> >>>>     * ufshcd_wait_for_register - wait for register value to change
> >>>>     * @hba - per-adapter interface
> >>>> @@ -851,9 +873,22 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
> >>>>
> >>>>    	tag = cmd->request->tag;
> >>>>
> >>>> -	if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) {
> >>>> +	switch (hba->ufshcd_state) {
> >>> Lock is no needed for ufshcd_state?
> > Please check?
> 
> Yes, it is needed. Thanks for catching this.
> 
> >
> >>>
> >>>> +	case UFSHCD_STATE_OPERATIONAL:
> >>>> +		break;
> >>>> +	case UFSHCD_STATE_RESET:
> >>>>    		err = SCSI_MLQUEUE_HOST_BUSY;
> >>>>    		goto out;
> >>>> +	case UFSHCD_STATE_ERROR:
> >>>> +		set_host_byte(cmd, DID_ERROR);
> >>>> +		cmd->scsi_done(cmd);
> >>>> +		goto out;
> >>>> +	default:
> >>>> +		dev_WARN_ONCE(hba->dev, 1, "%s: invalid state %d\n",
> >>>> +				__func__, hba->ufshcd_state);
> >>>> +		set_host_byte(cmd, DID_BAD_TARGET);
> >>>> +		cmd->scsi_done(cmd);
> >>>> +		goto out;
> >>>>    	}
> >>>>
> >>>>    	/* acquire the tag to make sure device cmds don't use it */
> >>>> @@ -1573,8 +1608,6 @@ static int ufshcd_make_hba_operational(struct ufs_hba *hba)
> >>>>    	if (hba->ufshcd_state == UFSHCD_STATE_RESET)
> >>>>    		scsi_unblock_requests(hba->host);
> >>>>
> >>>> -	hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;
> >>>> -
> >>>>    out:
> >>>>    	return err;
> >>>>    }
> >>>> @@ -2273,6 +2306,106 @@ out:
> >>>>    }
> >>>>
> >>>>    /**
> >>>> + * ufshcd_utrl_is_rsr_enabled - check if run-stop register is enabled
> >>>> + * @hba: per-adapter instance
> >>>> + */
> >>>> +static bool ufshcd_utrl_is_rsr_enabled(struct ufs_hba *hba)
> >>>> +{
> >>>> +	return ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_LIST_RUN_STOP) & 0x1;
> >>>> +}
> >>>> +
> >>>> +/**
> >>>> + * ufshcd_utmrl_is_rsr_enabled - check if run-stop register is enabled
> >>>> + * @hba: per-adapter instance
> >>>> + */
> >>>> +static bool ufshcd_utmrl_is_rsr_enabled(struct ufs_hba *hba)
> >>>> +{
> >>>> +	return ufshcd_readl(hba, REG_UTP_TASK_REQ_LIST_RUN_STOP) & 0x1;
> >>>> +}
> >>>> +
> >>>> +/**
> >>>> + * ufshcd_complete_pending_tasks - complete outstanding tasks
> >>>> + * @hba: per adapter instance
> >>>> + *
> >>>> + * Abort in-progress task management commands and wakeup
> >>>> + * waiting threads.
> >>>> + *
> >>>> + * Returns non-zero error value when failed to clear all the commands.
> >>>> + */
> >>>> +static int ufshcd_complete_pending_tasks(struct ufs_hba *hba)
> >>>> +{
> >>>> +	u32 reg;
> >>>> +	int err = 0;
> >>>> +	unsigned long flags;
> >>>> +
> >>>> +	if (!hba->outstanding_tasks)
> >>>> +		goto out;
> >>>> +
> >>>> +	/* Clear UTMRL only when run-stop is enabled */
> >>>> +	if (ufshcd_utmrl_is_rsr_enabled(hba))
> >>>> +		ufshcd_writel(hba, ~hba->outstanding_tasks,
> >>>> +				REG_UTP_TASK_REQ_LIST_CLEAR);
> >>>> +
> >>>> +	/* poll for max. 1 sec to clear door bell register by h/w */
> >>>> +	reg = ufshcd_wait_for_register(hba,
> >>>> +			REG_UTP_TASK_REQ_DOOR_BELL,
> >>>> +			hba->outstanding_tasks, 0, 1000, 1000);
> >>>> +	if (reg & hba->outstanding_tasks)
> >>>> +		err = -ETIMEDOUT;
> >>>> +
> >>>> +	spin_lock_irqsave(hba->host->host_lock, flags);
> >>>> +	/* complete commands that were cleared out */
> >>>> +	ufshcd_tmc_handler(hba);
> >>>> +	spin_unlock_irqrestore(hba->host->host_lock, flags);
> >>>> +out:
> >>>> +	if (err)
> >>>> +		dev_err(hba->dev, "%s: failed, still pending = 0x%.8x\n",
> >>>> +				__func__, reg);
> >>>> +	return err;
> >>>> +}
> >>>> +
> >>>> +/**
> >>>> + * ufshcd_complete_pending_reqs - complete outstanding requests
> >>>> + * @hba: per adapter instance
> >>>> + *
> >>>> + * Abort in-progress transfer request commands and return them to SCSI.
> >>>> + *
> >>>> + * Returns non-zero error value when failed to clear all the commands.
> >>>> + */
> >>>> +static int ufshcd_complete_pending_reqs(struct ufs_hba *hba)
> >>>> +{
> >>>> +	u32 reg;
> >>>> +	int err = 0;
> >>>> +	unsigned long flags;
> >>>> +
> >>>> +	/* check if we completed all of them */
> >>>> +	if (!hba->outstanding_reqs)
> >>>> +		goto out;
> >>>> +
> >>>> +	/* Clear UTRL only when run-stop is enabled */
> >>>> +	if (ufshcd_utrl_is_rsr_enabled(hba))
> >>>> +		ufshcd_writel(hba, ~hba->outstanding_reqs,
> >>>> +				REG_UTP_TRANSFER_REQ_LIST_CLEAR);
> >>>> +
> >>>> +	/* poll for max. 1 sec to clear door bell register by h/w */
> >>>> +	reg = ufshcd_wait_for_register(hba,
> >>>> +			REG_UTP_TRANSFER_REQ_DOOR_BELL,
> >>>> +			hba->outstanding_reqs, 0, 1000, 1000);
> >>>> +	if (reg & hba->outstanding_reqs)
> >>>> +		err = -ETIMEDOUT;
> >>>> +
> >>>> +	spin_lock_irqsave(hba->host->host_lock, flags);
> >>>> +	/* complete commands that were cleared out */
> >>>> +	ufshcd_transfer_req_compl(hba);
> >>>> +	spin_unlock_irqrestore(hba->host->host_lock, flags);
> >>>> +out:
> >>>> +	if (err)
> >>>> +		dev_err(hba->dev, "%s: failed, still pending = 0x%.8x\n",
> >>>> +				__func__, reg);
> >>>> +	return err;
> >>>> +}
> >>>> +
> >>>> +/**
> >>>>     * ufshcd_fatal_err_handler - handle fatal errors
> >>>>     * @hba: per adapter instance
> >>>>     */
> >>>> @@ -2306,8 +2439,12 @@ static void ufshcd_err_handler(struct ufs_hba *hba)
> >>>>    	}
> >>>>    	return;
> >>>>    fatal_eh:
> >>>> -	hba->ufshcd_state = UFSHCD_STATE_ERROR;
> >>>> -	schedule_work(&hba->feh_workq);
> >>>> +	/* handle fatal errors only when link is functional */
> >>>> +	if (hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) {
> >>>> +		/* block commands at driver layer until error is handled */
> >>>> +		hba->ufshcd_state = UFSHCD_STATE_ERROR;
> >>> Locking omitted for ufshcd_state?
> >> This is called in interrupt context with spin_lock held.
> > Right, I missed it.
> >
> >>
> >>>
> >>>> +		schedule_work(&hba->feh_workq);
> >>>> +	}
> >>>>    }
> >>>>
> >>>>    /**
> >>>> @@ -2475,75 +2612,155 @@ static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int
> >> task_id,
> >>>>    }
> >>>>
> >>>>    /**
> >>>> - * ufshcd_device_reset - reset device and abort all the pending commands
> >>>> - * @cmd: SCSI command pointer
> >>>> + * ufshcd_dme_end_point_reset - Notify device Unipro to perform reset
> >>>> + * @hba: per adapter instance
> >>>>     *
> >>>> - * Returns SUCCESS/FAILED
> >>>> + * UIC_CMD_DME_END_PT_RST resets the UFS device completely, the UFS flags,
> >>>> + * attributes and descriptors are reset to default state. Callers are
> >>>> + * expected to initialize the whole device again after this.
> >>>> + *
> >>>> + * Returns zero on success, non-zero on failure
> >>>>     */
> >>>> -static int ufshcd_device_reset(struct scsi_cmnd *cmd)
> >>>> +static int ufshcd_dme_end_point_reset(struct ufs_hba *hba)
> >>>>    {
> >>>> -	struct Scsi_Host *host;
> >>>> -	struct ufs_hba *hba;
> >>>> -	unsigned int tag;
> >>>> -	u32 pos;
> >>>> -	int err;
> >>>> -	u8 resp;
> >>>> -	struct ufshcd_lrb *lrbp;
> >>>> +	struct uic_command uic_cmd = {0};
> >>>> +	int ret;
> >>>>
> >>>> -	host = cmd->device->host;
> >>>> -	hba = shost_priv(host);
> >>>> -	tag = cmd->request->tag;
> >>>> +	uic_cmd.command = UIC_CMD_DME_END_PT_RST;
> >>>>
> >>>> -	lrbp = &hba->lrb[tag];
> >>>> -	err = ufshcd_issue_tm_cmd(hba, lrbp->lun, lrbp->task_tag,
> >>>> -			UFS_LOGICAL_RESET, &resp);
> >>>> -	if (err || resp != UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
> >>>> -		err = FAILED;
> >>>> +	ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
> >>>> +	if (ret)
> >>>> +		dev_err(hba->dev, "%s: error code %d\n", __func__, ret);
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +/**
> >>>> + * ufshcd_dme_reset - Local UniPro reset
> >>>> + * @hba: per adapter instance
> >>>> + *
> >>>> + * Returns zero on success, non-zero on failure
> >>>> + */
> >>>> +static int ufshcd_dme_reset(struct ufs_hba *hba)
> >>>> +{
> >>>> +	struct uic_command uic_cmd = {0};
> >>>> +	int ret;
> >>>> +
> >>>> +	uic_cmd.command = UIC_CMD_DME_RESET;
> >>>> +
> >>>> +	ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
> >>>> +	if (ret)
> >>>> +		dev_err(hba->dev, "%s: error code %d\n", __func__, ret);
> >>>> +
> >>>> +	return ret;
> >>>> +
> >>>> +}
> >>>> +
> >>>> +/**
> >>>> + * ufshcd_dme_enable - Local UniPro DME Enable
> >>>> + * @hba: per adapter instance
> >>>> + *
> >>>> + * Returns zero on success, non-zero on failure
> >>>> + */
> >>>> +static int ufshcd_dme_enable(struct ufs_hba *hba)
> >>>> +{
> >>>> +	struct uic_command uic_cmd = {0};
> >>>> +	int ret;
> >>>> +	uic_cmd.command = UIC_CMD_DME_ENABLE;
> >>>> +
> >>>> +	ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
> >>>> +	if (ret)
> >>>> +		dev_err(hba->dev, "%s: error code %d\n", __func__, ret);
> >>>> +
> >>>> +	return ret;
> >>>> +
> >>>> +}
> >>>> +
> >>>> +/**
> >>>> + * ufshcd_device_reset_and_restore - reset and restore device
> >>>> + * @hba: per-adapter instance
> >>>> + *
> >>>> + * Note that the device reset issues DME_END_POINT_RESET which
> >>>> + * may reset entire device and restore device attributes to
> >>>> + * default state.
> >>>> + *
> >>>> + * Returns zero on success, non-zero on failure
> >>>> + */
> >>>> +static int ufshcd_device_reset_and_restore(struct ufs_hba *hba)
> >>>> +{
> >>>> +	int err = 0;
> >>>> +	u32 reg;
> >>>> +
> >>>> +	err = ufshcd_dme_end_point_reset(hba);
> >>>> +	if (err)
> >>>> +		goto out;
> >>>> +
> >>>> +	/* restore communication with the device */
> >>>> +	err = ufshcd_dme_reset(hba);
> >>>> +	if (err)
> >>>>    		goto out;
> >>>> -	} else {
> >>>> -		err = SUCCESS;
> >>>> -	}
> >>>>
> >>>> -	for (pos = 0; pos < hba->nutrs; pos++) {
> >>>> -		if (test_bit(pos, &hba->outstanding_reqs) &&
> >>>> -		    (hba->lrb[tag].lun == hba->lrb[pos].lun)) {
> >>>> +	err = ufshcd_dme_enable(hba);
> >>>> +	if (err)
> >>>> +		goto out;
> >>>>
> >>>> -			/* clear the respective UTRLCLR register bit */
> >>>> -			ufshcd_utrl_clear(hba, pos);
> >>>> +	err = ufshcd_dme_link_startup(hba);
> >>> UFS_LOGICAL_RESET is no more used?
> >>
> >> Yes, I don't see any use for this as of now (given that we are using
> >> dme_end_point_reset, refer to figure. 7.4 of UFS 1.1 spec). Also, the
> >> UFS spec. error handling section doesn't mention anything about
> >> LOGICAL_RESET. If you know a valid use case where we need to have LUN
> >> reset, please let me know I will bring it back.
> > As refered the scsi-mid layer and other host's implementation,
> > eh_device_reset_handler(= ufshcd_eh_device_reset_handler) may
> > have a role of LOGICAL_RESET for specific lun.
> 
> I am still not convinced why we need LOGICAL_RESET. Just because other
> SCSI host drivers have it do we really need it for UFS?
> 
> > I found that ENDPOINT_RESET is recommended with IS.DFES in spec.
> 
> Here in this case, a command hang (scsi timeout) is considered as Device
> Fatal Error. If there are some LUN failures the response would still be
> transferred but with Unit-Attention condition with sense data. However,
> if the command itself hangs, there is something seriously wrong with the
> device or the communication. So we first try to reset the device and
> then the host. Unlike most of other SCSI HBAs, UFS is point-to-point
> (host <--> device) link and if something goes wrong and caused a hang,
> mostly would be a serious error and logical unit reset wouldn't help
> much.
As far as UFS follows the SAM-5 model, LOGICAL_RESET should be considered.
LOGICAL_RESET would be handled in 'eh_device_reset_handler' as I see it.
And it looks like actual device reset is close to 'eh_target_reset_handler'.

> 
> 
> >
> > Let me add some comments additionally.
> > Both 'ufshcd_eh_device_reset_handler' and 'ufshcd_host_reset_and_restore' do almost same things.
> > At a glance, it's confused about their role and It is mixed.
> > 'ufshcd_reset_and_restore' is eventually called, which is actual part of reset functionality; Once
> device reset is failed, then
> > host reset is tried.
> > Actually, that is being handled for each level of error recovery in scsi mid-layer. Please chekc
> 'drivers/scsi/scsi_error.c'.
> > [scsi_eh_ready_devs, scsi_abort_eh_cmnd]
> > In this stage, each reset functionality could be separated obviously.
> 
> Yes, in that case we are optimistically doing the host reset twice,
> just a hope that it recovers before SCSI layer choke and mark the
> device as OFFLINE. If you think that this shouldn't be the case and
> have a valid reason for not doing so, I will return appropriate error
> in the case device reset fails.
The two are much the same actually.
To simplify implementation in host driver, leaving it to scsi mid-layer would be better.
Eventually, the controlling of callback function is from upper layer.

> 
> >
> >>
> >>> ufshcd_device_reset_and_restore have a role of device reset.
> >>> Both ufshcd_dme_reset and ufshcd_dme_enable are valid for local one, not for remote.
> >>> Should we do those for host including link-startup here?
> >>
> >> Yes, it is needed. After DME_ENDPOINT_RESET the remote link goes into link down state.
> > I want to know more related description. I didn't find it. Could you point that?
> 
> Please refer to "Table 121 DME_SAP restrictions" of MIPI Uni-Pro spec.
> The spec. doesn't mention about this explicitly but here is the logic
> that is derived from the spec.
> 1) The DME_LINKSTARTUP can be sent only when the link is in down state,
> in all other states DME_LINKSTARTUP is ignored.
> 2) So if we are sending DME_ENDPOINT_RESET then that must ensure that
> remote link is in down state, and hence it can receive linkstartup and
> establish the communication.
> 
> >
> >> To initialize the link, the host needs to send
> >> DME_LINKSTARTUP, but according to Uni-Pro spec. the link-startup can
> >> only be sent when the local uni-pro is in link-down state. So first
> > If it's right you mentioned above, uni-pro state is already in link-down after DME_ENDPOINT_RESET.
> > Then, DME_RESET isn't needed.
> 
> You are getting confused here -
Yeah, I'm mixed up with link-down of remote side you mentioned.
There is no special saying about link state after receiving DME_ENDPOINT_RESET in spec.
I just found the point that link startup is initiated.
It means that link startup is triggered from remote device after DME_ENDPOINT_RESET.
In that case, host will detect ULSS(UIC Link Startup Status) interrupt.
After that, host shall start link startup procedure with DME_RESET.
Of course, your approach could be acceptable.

> 
> - State1: before sending DME_ENDPOINT_RESET
> 	Local Unipro (host) - Link-UP
> 	Remote Unipro (device) - Link-Up
> 
> - State2: after sending DME_ENDPOINT_RESET
> 	Local Unipro (host) - Link-UP
> 	Remote Unipro (device) - Link-Down
> 
> - State3: After sending DME_RESET+DME_ENABLE
> 	Local Unipro (host) - Link-Down
> 	Remote Unipro (device) - Link-Down
> 
> - State4: After sending DME_LINKSTARTUP
> 	Local Unipro(host) - Link-up
> 	Remote Unipro (device) - Link-up
> 
> The local unipro ignores the DME_LINKSTARTUP if we send it before
> DME_RESET.
> 
> >
> >> we need to get the local unipro from link-up to disabled to link-down
> >> using the DME_RESET and DME_ENABLE commands and then issue
> >> DME_LINKSTARTUP to re-initialize the link.
> > 'ufshcd_hba_enable' can be used instead of both if these are really needed.
> > This will do dme_reset and dme_enable.
> >
> 
> The only reason for this is that in some implementations the HCE reset
> also resets UTP layer in addition to Uni-Pro layer. There is no need
> of UTP layer reset for device reset. So explicit DME_RESET and
> DME_ENABLE is used. For those implementations which don't do UTP layer
> reset then the advantage is instead of wasting CPU cycles in polling for
> HCE=1 we depend on UIC interrupts.
HCE reset can involve the additional unipro configurations, depending
host controller implementation. 
As considering that unipro stack is reset with DME_RESET, 
usage of individual DME_RESET might be inappropriate during link startup.

Thanks,
Seungwon Jeon

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux