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
        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
                         */
                        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 */
                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
        if (ret)
                goto out;
 
+       ufshcd_force_reset_auto_bkops(hba);
        scsi_scan_host(hba->host);
 out:
        return;
 }
 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
 
        /* 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);