UFS發生命令超時處理流程

1. 在UFS的上層發送命令下來,如果上層30s沒有收到ufs devices的response, 就會出現超時,此時會去查詢命令是否真正的發送以及是否需要Abort 超時的命令

首先  我們先看數據傳輸是驅動程序的本質目的,通過數據的傳輸,來完成作爲存儲介質的使命,read & write,在read流程中,ufs嚮應用程序提供數據,在write流程中,應用程序向ufs存放數據。

本節分三個階段關注數據的流向,分別是:系統調用數據到bio的組成,bio到電梯隊列request的組成,request到controller的數據傳輸。主要圍繞buffer和存儲地址兩個數據流要素,根據讀流程描述各階段這兩個數據流要素的數據結構。

sys_read->submit_bio的流程

 

bio生成後,就plug到進程的plug_list裏面,如果失敗,則進電梯merge,如果依然失敗,則只能等待新的request被釋放出來,兩次蓄流的目的是爲了儘可能的讓bio請求得到merge,減少往磁盤驅動下發的頻度,提升綜合性能

bio層->request的流程

 

當plug_list達到設定閾值或進程主動發起或設定時間已到或手機將要關機等,則會進行泄流。從elevator的queue裏面取出一個request請求,下發到底層UFS驅動,這個流程從request轉化爲scsi協議包,最後封裝在UFS的協議包UPIU包裏面,於是就完成了一個請求的host內存準備,最後通過置這個請求對應的UFSHCI的doorbell寄存器中的mask,實現請求往驅動層的發送。

2. 接下來我們看看超時的具體流程和操作

```c
/**
 * scsi_alloc_sdev - allocate and setup a scsi_Device
 * @starget: which target to allocate a &scsi_device for
 * @lun: which lun
 * @hostdata: usually NULL and set by ->slave_alloc instead
 *
 * Description:
 *     Allocate, initialize for io, and return a pointer to a scsi_Device.
 *     Stores the @shost, @channel, @id, and @lun in the scsi_Device, and
 *     adds scsi_Device to the appropriate list.
 *
 * Return value:
 *     scsi_Device pointer, or NULL on failure.
 **/
 
```

```c
scsi_alloc_sdev
   |
   
 if (shost_use_blk_mq(shost))
  sdev->request_queue = scsi_mq_alloc_queue(sdev);
 else
  sdev->request_queue = scsi_old_alloc_queue(sdev);
  |
 blk_queue_rq_timed_out(q, scsi_times_out);
  |
scsi_abort_command
  |
 queue_delayed_work(shost->tmf_work_q, &scmd->abort_work, HZ / 100);
  |
 scmd_eh_abort_handler
  |
 scsi_try_to_abort_cmd
  |
 return hostt->eh_abort_handler(scmd);
  |
 .eh_abort_handler = ufshcd_abort
  |
  ufshcd_abort
```

/**
 * ufshcd_abort - abort a specific command
 * @cmd: SCSI command pointer
 *
 * Abort the pending command in device by sending UFS_ABORT_TASK task management
 * command, and in host controller by clearing the door-bell register. There can
 * be race between controller sending the command to the device while abort is
 * issued. To avoid that, first issue UFS_QUERY_TASK to check if the command is
 * really issued and then try to abort it.
 *
 * Returns SUCCESS/FAILED
 */
static int ufshcd_abort(struct scsi_cmnd *cmd)
{
    struct Scsi_Host *host;
    struct ufs_hba *hba;
    unsigned long flags;
    unsigned int tag;
    int err = 0;
    int poll_cnt;
    u8 resp = 0xF;
    struct ufshcd_lrb *lrbp;
    u32 reg;

    host = cmd->device->host;
    hba = shost_priv(host);
    tag = cmd->request->tag;
    lrbp = &hba->lrb[tag];
    if (!ufshcd_valid_tag(hba, tag)) {
        dev_err(hba->dev,
            "%s: invalid command tag %d: cmd=0x%p, cmd->request=0x%p",
            __func__, tag, cmd, cmd->request);
        BUG();
    }

    /*
     * Task abort to the device W-LUN is illegal. When this command
     * will fail, due to spec violation, scsi err handling next step
     * will be to send LU reset which, again, is a spec violation.
     * To avoid these unnecessary/illegal step we skip to the last error
     * handling stage: reset and restore.
     */
    if (lrbp->lun == UFS_UPIU_UFS_DEVICE_WLUN)
        return ufshcd_eh_host_reset_handler(cmd);

    ufshcd_hold(hba, false);
    reg = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
    /* If command is already aborted/completed, return SUCCESS */
    if (!(test_bit(tag, &hba->outstanding_reqs))) {
        dev_err(hba->dev,
            "%s: cmd at tag %d already completed, outstanding=0x%lx, doorbell=0x%x\n",
            __func__, tag, hba->outstanding_reqs, reg);
        goto out;
    }

    if (!(reg & (1 << tag))) {
        dev_err(hba->dev,
        "%s: cmd was completed, but without a notifying intr, tag = %d",
        __func__, tag);
    }

    /* Print Transfer Request of aborted task */
    dev_err(hba->dev, "%s: Device abort task at tag %d\n", __func__, tag);

    /*
     * Print detailed info about aborted request.
     * As more than one request might get aborted at the same time,
     * print full information only for the first aborted request in order
     * to reduce repeated printouts. For other aborted requests only print
     * basic details.
     */
    scsi_print_command(hba->lrb[tag].cmd);
    if (!hba->req_abort_count) {
        ufshcd_print_host_regs(hba);
        ufshcd_print_host_state(hba);
        ufshcd_print_pwr_info(hba);
        ufshcd_print_trs(hba, 1 << tag, true);
    } else {
        ufshcd_print_trs(hba, 1 << tag, false);
    }
    hba->req_abort_count++;

    /* Skip task abort in case previous aborts failed and report failure */
    if (lrbp->req_abort_skip) {
        err = -EIO;
        goto out;
    }

    for (poll_cnt = 100; poll_cnt; poll_cnt--) {
        err = ufshcd_issue_tm_cmd(hba, lrbp->lun, lrbp->task_tag,
                UFS_QUERY_TASK, &resp);
        if (!err && resp == UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED) {
            /* cmd pending in the device */
            dev_err(hba->dev, "%s: cmd pending in the device. tag = %d\n",
                __func__, tag);
            break;
        } else if (!err && resp == UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
            /*
             * cmd not pending in the device, check if it is
             * in transition.
             */
            dev_err(hba->dev, "%s: cmd at tag %d not pending in the device.\n",
                __func__, tag);
            reg = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
            if (reg & (1 << tag)) {
                /* sleep for max. 200us to stabilize */
                usleep_range(100, 200);
                continue;
            }
            /* command completed already */
            dev_err(hba->dev, "%s: cmd at tag %d successfully cleared from DB.\n",
                __func__, tag);
            goto out;
        } else {
            dev_err(hba->dev,
                "%s: no response from device. tag = %d, err %d\n",
                __func__, tag, err);
            if (!err)
                err = resp; /* service response error */
            goto out;
        }
    }

    if (!poll_cnt) {
        err = -EBUSY;
        goto out;
    }

    err = ufshcd_issue_tm_cmd(hba, lrbp->lun, lrbp->task_tag,
            UFS_ABORT_TASK, &resp);
    if (err || resp != UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
        if (!err) {
            err = resp; /* service response error */
            dev_err(hba->dev, "%s: issued. tag = %d, err %d\n",
                __func__, tag, err);
        }
        goto out;
    }

    err = ufshcd_clear_cmd(hba, tag);
    if (err) {
        dev_err(hba->dev, "%s: Failed clearing cmd at tag %d, err %d\n",
            __func__, tag, err);
        goto out;
    }

    scsi_dma_unmap(cmd);

    spin_lock_irqsave(host->host_lock, flags);
    ufshcd_outstanding_req_clear(hba, tag);
    hba->lrb[tag].cmd = NULL;
    spin_unlock_irqrestore(host->host_lock, flags);

    clear_bit_unlock(tag, &hba->lrb_in_use);
    wake_up(&hba->dev_cmd.tag_wq);

out:
    if (!err) {
        err = SUCCESS;
    } else {
        dev_err(hba->dev, "%s: failed with err %d\n", __func__, err);
        ufshcd_set_req_abort_skip(hba, hba->outstanding_reqs);
        err = FAILED;
    }

    /*
     * This ufshcd_release() corresponds to the original scsi cmd that got
     * aborted here (as we won't get any IRQ for it).
     */
    ufshcd_release(hba);
    return err;
}

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章