|
@@ -260,6 +260,21 @@ ufshcd_get_rsp_upiu_result(struct utp_upiu_rsp *ucd_rsp_ptr)
|
|
|
return be32_to_cpu(ucd_rsp_ptr->header.dword_1) & MASK_RSP_UPIU_RESULT;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ufshcd_is_exception_event - Check if the device raised an exception event
|
|
|
+ * @ucd_rsp_ptr: pointer to response UPIU
|
|
|
+ *
|
|
|
+ * The function checks if the device raised an exception event indicated in
|
|
|
+ * the Device Information field of response UPIU.
|
|
|
+ *
|
|
|
+ * Returns true if exception is raised, false otherwise.
|
|
|
+ */
|
|
|
+static inline bool ufshcd_is_exception_event(struct utp_upiu_rsp *ucd_rsp_ptr)
|
|
|
+{
|
|
|
+ return be32_to_cpu(ucd_rsp_ptr->header.dword_2) &
|
|
|
+ MASK_RSP_EXCEPTION_EVENT ? true : false;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ufshcd_config_int_aggr - Configure interrupt aggregation values.
|
|
|
* Currently there is no use case where we want to configure
|
|
@@ -1106,6 +1121,77 @@ out_unlock:
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ufshcd_query_attr - API function for sending attribute requests
|
|
|
+ * hba: per-adapter instance
|
|
|
+ * opcode: attribute opcode
|
|
|
+ * idn: attribute idn to access
|
|
|
+ * index: index field
|
|
|
+ * selector: selector field
|
|
|
+ * attr_val: the attribute value after the query request completes
|
|
|
+ *
|
|
|
+ * Returns 0 for success, non-zero in case of failure
|
|
|
+*/
|
|
|
+int ufshcd_query_attr(struct ufs_hba *hba, enum query_opcode opcode,
|
|
|
+ enum attr_idn idn, u8 index, u8 selector, u32 *attr_val)
|
|
|
+{
|
|
|
+ struct ufs_query_req *request;
|
|
|
+ struct ufs_query_res *response;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ BUG_ON(!hba);
|
|
|
+
|
|
|
+ if (!attr_val) {
|
|
|
+ dev_err(hba->dev, "%s: attribute value required for opcode 0x%x\n",
|
|
|
+ __func__, opcode);
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_lock(&hba->dev_cmd.lock);
|
|
|
+ request = &hba->dev_cmd.query.request;
|
|
|
+ response = &hba->dev_cmd.query.response;
|
|
|
+ memset(request, 0, sizeof(struct ufs_query_req));
|
|
|
+ memset(response, 0, sizeof(struct ufs_query_res));
|
|
|
+
|
|
|
+ switch (opcode) {
|
|
|
+ case UPIU_QUERY_OPCODE_WRITE_ATTR:
|
|
|
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST;
|
|
|
+ request->upiu_req.value = *attr_val;
|
|
|
+ break;
|
|
|
+ case UPIU_QUERY_OPCODE_READ_ATTR:
|
|
|
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ dev_err(hba->dev, "%s: Expected query attr opcode but got = 0x%.2x\n",
|
|
|
+ __func__, opcode);
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out_unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ request->upiu_req.opcode = opcode;
|
|
|
+ request->upiu_req.idn = idn;
|
|
|
+ request->upiu_req.index = index;
|
|
|
+ request->upiu_req.selector = selector;
|
|
|
+
|
|
|
+ /* Send query request */
|
|
|
+ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY,
|
|
|
+ QUERY_REQ_TIMEOUT);
|
|
|
+
|
|
|
+ if (err) {
|
|
|
+ dev_err(hba->dev, "%s: opcode 0x%.2x for idn %d failed, err = %d\n",
|
|
|
+ __func__, opcode, idn, err);
|
|
|
+ goto out_unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ *attr_val = response->upiu_res.value;
|
|
|
+
|
|
|
+out_unlock:
|
|
|
+ mutex_unlock(&hba->dev_cmd.lock);
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ufshcd_memory_alloc - allocate memory for host memory space data structures
|
|
|
* @hba: per adapter instance
|
|
@@ -1772,6 +1858,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
|
|
|
*/
|
|
|
scsi_status = result & MASK_SCSI_STATUS;
|
|
|
result = ufshcd_scsi_cmd_status(lrbp, scsi_status);
|
|
|
+
|
|
|
+ if (ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
|
|
|
+ schedule_work(&hba->eeh_work);
|
|
|
break;
|
|
|
case UPIU_TRANSACTION_REJECT_UPIU:
|
|
|
/* TODO: handle Reject UPIU Response */
|
|
@@ -1874,6 +1963,230 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba)
|
|
|
ufshcd_config_int_aggr(hba, INT_AGGR_RESET);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ufshcd_disable_ee - disable exception event
|
|
|
+ * @hba: per-adapter instance
|
|
|
+ * @mask: exception event to disable
|
|
|
+ *
|
|
|
+ * Disables exception event in the device so that the EVENT_ALERT
|
|
|
+ * bit is not set.
|
|
|
+ *
|
|
|
+ * Returns zero on success, non-zero error value on failure.
|
|
|
+ */
|
|
|
+static int ufshcd_disable_ee(struct ufs_hba *hba, u16 mask)
|
|
|
+{
|
|
|
+ int err = 0;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ if (!(hba->ee_ctrl_mask & mask))
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ val = hba->ee_ctrl_mask & ~mask;
|
|
|
+ val &= 0xFFFF; /* 2 bytes */
|
|
|
+ err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
|
|
|
+ QUERY_ATTR_IDN_EE_CONTROL, 0, 0, &val);
|
|
|
+ if (!err)
|
|
|
+ hba->ee_ctrl_mask &= ~mask;
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ufshcd_enable_ee - enable exception event
|
|
|
+ * @hba: per-adapter instance
|
|
|
+ * @mask: exception event to enable
|
|
|
+ *
|
|
|
+ * Enable corresponding exception event in the device to allow
|
|
|
+ * device to alert host in critical scenarios.
|
|
|
+ *
|
|
|
+ * Returns zero on success, non-zero error value on failure.
|
|
|
+ */
|
|
|
+static int ufshcd_enable_ee(struct ufs_hba *hba, u16 mask)
|
|
|
+{
|
|
|
+ int err = 0;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ if (hba->ee_ctrl_mask & mask)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ val = hba->ee_ctrl_mask | mask;
|
|
|
+ val &= 0xFFFF; /* 2 bytes */
|
|
|
+ err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
|
|
|
+ QUERY_ATTR_IDN_EE_CONTROL, 0, 0, &val);
|
|
|
+ if (!err)
|
|
|
+ hba->ee_ctrl_mask |= mask;
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ufshcd_enable_auto_bkops - Allow device managed BKOPS
|
|
|
+ * @hba: per-adapter instance
|
|
|
+ *
|
|
|
+ * Allow device to manage background operations on its own. Enabling
|
|
|
+ * this might lead to inconsistent latencies during normal data transfers
|
|
|
+ * as the device is allowed to manage its own way of handling background
|
|
|
+ * operations.
|
|
|
+ *
|
|
|
+ * Returns zero on success, non-zero on failure.
|
|
|
+ */
|
|
|
+static int ufshcd_enable_auto_bkops(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ int err = 0;
|
|
|
+
|
|
|
+ if (hba->auto_bkops_enabled)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ err = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_SET_FLAG,
|
|
|
+ QUERY_FLAG_IDN_BKOPS_EN, NULL);
|
|
|
+ if (err) {
|
|
|
+ dev_err(hba->dev, "%s: failed to enable bkops %d\n",
|
|
|
+ __func__, err);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ hba->auto_bkops_enabled = true;
|
|
|
+
|
|
|
+ /* No need of URGENT_BKOPS exception from the device */
|
|
|
+ err = ufshcd_disable_ee(hba, MASK_EE_URGENT_BKOPS);
|
|
|
+ if (err)
|
|
|
+ dev_err(hba->dev, "%s: failed to disable exception event %d\n",
|
|
|
+ __func__, err);
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ufshcd_disable_auto_bkops - block device in doing background operations
|
|
|
+ * @hba: per-adapter instance
|
|
|
+ *
|
|
|
+ * Disabling background operations improves command response latency but
|
|
|
+ * has drawback of device moving into critical state where the device is
|
|
|
+ * not-operable. Make sure to call ufshcd_enable_auto_bkops() whenever the
|
|
|
+ * host is idle so that BKOPS are managed effectively without any negative
|
|
|
+ * impacts.
|
|
|
+ *
|
|
|
+ * Returns zero on success, non-zero on failure.
|
|
|
+ */
|
|
|
+static int ufshcd_disable_auto_bkops(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ int err = 0;
|
|
|
+
|
|
|
+ if (!hba->auto_bkops_enabled)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If host assisted BKOPs is to be enabled, make sure
|
|
|
+ * urgent bkops exception is allowed.
|
|
|
+ */
|
|
|
+ err = ufshcd_enable_ee(hba, MASK_EE_URGENT_BKOPS);
|
|
|
+ if (err) {
|
|
|
+ dev_err(hba->dev, "%s: failed to enable exception event %d\n",
|
|
|
+ __func__, err);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_CLEAR_FLAG,
|
|
|
+ QUERY_FLAG_IDN_BKOPS_EN, NULL);
|
|
|
+ if (err) {
|
|
|
+ dev_err(hba->dev, "%s: failed to disable bkops %d\n",
|
|
|
+ __func__, err);
|
|
|
+ ufshcd_disable_ee(hba, MASK_EE_URGENT_BKOPS);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ hba->auto_bkops_enabled = false;
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ufshcd_force_reset_auto_bkops - force enable of auto bkops
|
|
|
+ * @hba: per adapter instance
|
|
|
+ *
|
|
|
+ * After a device reset the device may toggle the BKOPS_EN flag
|
|
|
+ * to default value. The s/w tracking variables should be updated
|
|
|
+ * as well. Do this by forcing enable of auto bkops.
|
|
|
+ */
|
|
|
+static void ufshcd_force_reset_auto_bkops(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ hba->auto_bkops_enabled = false;
|
|
|
+ hba->ee_ctrl_mask |= MASK_EE_URGENT_BKOPS;
|
|
|
+ ufshcd_enable_auto_bkops(hba);
|
|
|
+}
|
|
|
+
|
|
|
+static inline int ufshcd_get_bkops_status(struct ufs_hba *hba, u32 *status)
|
|
|
+{
|
|
|
+ return ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
|
|
|
+ QUERY_ATTR_IDN_BKOPS_STATUS, 0, 0, status);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ufshcd_urgent_bkops - handle urgent bkops exception event
|
|
|
+ * @hba: per-adapter instance
|
|
|
+ *
|
|
|
+ * Enable fBackgroundOpsEn flag in the device to permit background
|
|
|
+ * operations.
|
|
|
+ */
|
|
|
+static int ufshcd_urgent_bkops(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ int err;
|
|
|
+ u32 status = 0;
|
|
|
+
|
|
|
+ err = ufshcd_get_bkops_status(hba, &status);
|
|
|
+ if (err) {
|
|
|
+ dev_err(hba->dev, "%s: failed to get BKOPS status %d\n",
|
|
|
+ __func__, err);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ status = status & 0xF;
|
|
|
+
|
|
|
+ /* handle only if status indicates performance impact or critical */
|
|
|
+ if (status >= BKOPS_STATUS_PERF_IMPACT)
|
|
|
+ err = ufshcd_enable_auto_bkops(hba);
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static inline int ufshcd_get_ee_status(struct ufs_hba *hba, u32 *status)
|
|
|
+{
|
|
|
+ return ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
|
|
|
+ QUERY_ATTR_IDN_EE_STATUS, 0, 0, status);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ufshcd_exception_event_handler - handle exceptions raised by device
|
|
|
+ * @work: pointer to work data
|
|
|
+ *
|
|
|
+ * Read bExceptionEventStatus attribute from the device and handle the
|
|
|
+ * exception event accordingly.
|
|
|
+ */
|
|
|
+static void ufshcd_exception_event_handler(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct ufs_hba *hba;
|
|
|
+ int err;
|
|
|
+ u32 status = 0;
|
|
|
+ hba = container_of(work, struct ufs_hba, eeh_work);
|
|
|
+
|
|
|
+ err = ufshcd_get_ee_status(hba, &status);
|
|
|
+ if (err) {
|
|
|
+ dev_err(hba->dev, "%s: failed to get exception status %d\n",
|
|
|
+ __func__, err);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ status &= hba->ee_ctrl_mask;
|
|
|
+ if (status & MASK_EE_URGENT_BKOPS) {
|
|
|
+ err = ufshcd_urgent_bkops(hba);
|
|
|
+ if (err)
|
|
|
+ dev_err(hba->dev, "%s: failed to handle urgent bkops %d\n",
|
|
|
+ __func__, err);
|
|
|
+ }
|
|
|
+out:
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ufshcd_fatal_err_handler - handle fatal errors
|
|
|
* @hba: per adapter instance
|
|
@@ -2185,6 +2498,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
|
|
|
if (ret)
|
|
|
goto out;
|
|
|
|
|
|
+ ufshcd_force_reset_auto_bkops(hba);
|
|
|
scsi_scan_host(hba->host);
|
|
|
out:
|
|
|
return;
|
|
@@ -2249,6 +2563,34 @@ int ufshcd_resume(struct ufs_hba *hba)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(ufshcd_resume);
|
|
|
|
|
|
+int ufshcd_runtime_suspend(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ if (!hba)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The device is idle with no requests in the queue,
|
|
|
+ * allow background operations.
|
|
|
+ */
|
|
|
+ return ufshcd_enable_auto_bkops(hba);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(ufshcd_runtime_suspend);
|
|
|
+
|
|
|
+int ufshcd_runtime_resume(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ if (!hba)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ return ufshcd_disable_auto_bkops(hba);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(ufshcd_runtime_resume);
|
|
|
+
|
|
|
+int ufshcd_runtime_idle(struct ufs_hba *hba)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(ufshcd_runtime_idle);
|
|
|
+
|
|
|
/**
|
|
|
* ufshcd_remove - de-allocate SCSI host and host memory space
|
|
|
* data structure memory
|
|
@@ -2339,6 +2681,7 @@ int ufshcd_init(struct device *dev, struct ufs_hba **hba_handle,
|
|
|
|
|
|
/* Initialize work queues */
|
|
|
INIT_WORK(&hba->feh_workq, ufshcd_fatal_err_handler);
|
|
|
+ INIT_WORK(&hba->eeh_work, ufshcd_exception_event_handler);
|
|
|
|
|
|
/* Initialize UIC command mutex */
|
|
|
mutex_init(&hba->uic_cmd_mutex);
|