// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2017-2018 Samsung Electronics Co., Ltd. */ #include "ufsfeature.h" #include "ufshcd.h" #include "ufs_quirks.h" #if defined(CONFIG_SCSI_UFS_HPB) #include "ufshpb.h" #endif #define QUERY_REQ_TIMEOUT 1500 /* msec */ static inline void ufsf_init_query(struct ufs_hba *hba, struct ufs_query_req **request, struct ufs_query_res **response, enum query_opcode opcode, u8 idn, u8 index, u8 selector) { *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)); (*request)->upiu_req.opcode = opcode; (*request)->upiu_req.idn = idn; (*request)->upiu_req.index = index; (*request)->upiu_req.selector = selector; } /* * ufs feature common functions. */ int ufsf_query_flag(struct ufs_hba *hba, enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res) { struct ufs_query_req *request = NULL; struct ufs_query_res *response = NULL; u8 selector; int err; BUG_ON(!hba); ufshcd_hold(hba, false); mutex_lock(&hba->dev_cmd.lock); if (hba->dev_info.wmanufacturerid == UFS_VENDOR_SAMSUNG || hba->dev_info.wmanufacturerid == UFS_VENDOR_MICRON) selector = UFSFEATURE_SELECTOR; else selector = 0; /* * Init the query response and request parameters */ ufsf_init_query(hba, &request, &response, opcode, idn, index, selector); switch (opcode) { case UPIU_QUERY_OPCODE_SET_FLAG: case UPIU_QUERY_OPCODE_CLEAR_FLAG: case UPIU_QUERY_OPCODE_TOGGLE_FLAG: request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST; break; case UPIU_QUERY_OPCODE_READ_FLAG: request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST; if (!flag_res) { /* No dummy reads */ dev_err(hba->dev, "%s: Invalid argument for read request\n", __func__); err = -EINVAL; goto out_unlock; } break; default: dev_err(hba->dev, "%s: Expected query flag opcode but got = %d\n", __func__, opcode); err = -EINVAL; goto out_unlock; } /* Send query request */ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, QUERY_REQ_TIMEOUT); if (err) { dev_err(hba->dev, "%s: Sending flag query for idn %d failed, err = %d\n", __func__, idn, err); goto out_unlock; } if (flag_res) *flag_res = (be32_to_cpu(response->upiu_res.value) & MASK_QUERY_UPIU_FLAG_LOC) & 0x1; out_unlock: mutex_unlock(&hba->dev_cmd.lock); ufshcd_release(hba); return err; } int ufsf_query_flag_retry(struct ufs_hba *hba, enum query_opcode opcode, enum flag_idn idn, u8 idx, bool *flag_res) { int ret; int retries; for (retries = 0; retries < UFSF_QUERY_REQ_RETRIES; retries++) { ret = ufsf_query_flag(hba, opcode, idn, idx, flag_res); if (ret) dev_dbg(hba->dev, "%s: failed with error %d, retries %d\n", __func__, ret, retries); else break; } if (ret) dev_err(hba->dev, "%s: query flag, opcode %d, idn %d, failed with error %d after %d retires\n", __func__, opcode, idn, ret, retries); return ret; } int ufsf_query_attr_retry(struct ufs_hba *hba, enum query_opcode opcode, enum attr_idn idn, u8 idx, u32 *attr_val) { int ret; int retries; u8 selector; if (hba->dev_info.wmanufacturerid == UFS_VENDOR_SAMSUNG || hba->dev_info.wmanufacturerid == UFS_VENDOR_MICRON) selector = UFSFEATURE_SELECTOR; else selector = 0; for (retries = 0; retries < UFSF_QUERY_REQ_RETRIES; retries++) { ret = ufshcd_query_attr(hba, opcode, idn, idx, selector, attr_val); if (ret) dev_dbg(hba->dev, "%s: failed with error %d, retries %d\n", __func__, ret, retries); else break; } if (ret) dev_err(hba->dev, "%s: query attr, opcode %d, idn %d, failed with error %d after %d retires\n", __func__, opcode, idn, ret, retries); return ret; } static int ufsf_read_desc(struct ufs_hba *hba, u8 desc_id, u8 desc_index, u8 selector, u8 *desc_buf, u32 size) { int err = 0; bool pm_resumed = false; if (!hba->pm_op_in_progress) { pm_runtime_get_sync(hba->dev); pm_resumed = true; } err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, desc_id, desc_index, selector, desc_buf, &size); if (err) ERR_MSG("reading Device Desc failed. err = %d", err); if (pm_resumed) pm_runtime_put_sync(hba->dev); return err; } static int ufsf_read_dev_desc(struct ufsf_feature *ufsf, u8 selector) { u8 desc_buf[UFSF_QUERY_DESC_DEVICE_MAX_SIZE] = {0}; int ret; ret = ufsf_read_desc(ufsf->hba, QUERY_DESC_IDN_DEVICE, 0, selector, desc_buf, UFSF_QUERY_DESC_DEVICE_MAX_SIZE); if (ret) return ret; ufsf->num_lu = desc_buf[DEVICE_DESC_PARAM_NUM_LU]; INIT_INFO("device lu count %d", ufsf->num_lu); INIT_INFO("sel=%u length=%u(0x%x) bSupport=0x%.2x, extend=0x%.2x_%.2x", selector, desc_buf[DEVICE_DESC_PARAM_LEN], desc_buf[DEVICE_DESC_PARAM_LEN], desc_buf[DEVICE_DESC_PARAM_FEAT_SUP], desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP+2], desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP+3]); #if defined(CONFIG_SCSI_UFS_HPB) ufshpb_get_dev_info(&ufsf->hpb_dev_info, desc_buf); #endif #if defined(CONFIG_SCSI_UFS_TW) ufstw_get_dev_info(&ufsf->tw_dev_info, desc_buf); #endif return 0; } static int ufsf_read_geo_desc(struct ufsf_feature *ufsf, u8 selector) { u8 geo_buf[UFSF_QUERY_DESC_GEOMETRY_MAX_SIZE]; int ret; ret = ufsf_read_desc(ufsf->hba, QUERY_DESC_IDN_GEOMETRY, 0, selector, geo_buf, UFSF_QUERY_DESC_GEOMETRY_MAX_SIZE); if (ret) return ret; #if defined(CONFIG_SCSI_UFS_HPB) if (ufsf->hpb_dev_info.hpb_device) ufshpb_get_geo_info(&ufsf->hpb_dev_info, geo_buf); #endif #if defined(CONFIG_SCSI_UFS_TW) if (ufsf->tw_dev_info.tw_device) ufstw_get_geo_info(&ufsf->tw_dev_info, geo_buf); #endif return 0; } static int ufsf_read_unit_desc(struct ufsf_feature *ufsf, unsigned int lun, u8 selector) { u8 unit_buf[UFSF_QUERY_DESC_UNIT_MAX_SIZE]; int lu_enable, ret = 0; ret = ufsf_read_desc(ufsf->hba, QUERY_DESC_IDN_UNIT, lun, selector, unit_buf, UFSF_QUERY_DESC_UNIT_MAX_SIZE); if (ret) { ERR_MSG("read unit desc failed. ret %d", ret); goto out; } lu_enable = unit_buf[UNIT_DESC_PARAM_LU_ENABLE]; if (!lu_enable) return 0; #if defined(CONFIG_SCSI_UFS_HPB) if (ufsf->hpb_dev_info.hpb_device) { ret = ufshpb_get_lu_info(ufsf, lun, unit_buf); if (ret == -ENOMEM) goto out; } #endif #if defined(CONFIG_SCSI_UFS_TW) if (ufsf->tw_dev_info.tw_device) { ret = ufstw_get_lu_info(ufsf, lun, unit_buf); if (ret == -ENOMEM) goto out; } #endif out: return ret; } void ufsf_device_check(struct ufs_hba *hba) { struct ufsf_feature *ufsf = &hba->ufsf; int ret; unsigned int lun; u8 selector = 0; ufsf->slave_conf_cnt = 0; ufsf->hba = hba; if (hba->dev_info.wmanufacturerid == UFS_VENDOR_SAMSUNG || hba->dev_info.wmanufacturerid == UFS_VENDOR_MICRON) selector = UFSFEATURE_SELECTOR; ret = ufsf_read_dev_desc(ufsf, selector); if (ret) return; ret = ufsf_read_geo_desc(ufsf, selector); if (ret) return; seq_scan_lu(lun) { ret = ufsf_read_unit_desc(ufsf, lun, selector); if (ret == -ENOMEM) goto out_free_mem; } return; out_free_mem: #if defined(CONFIG_SCSI_UFS_HPB) seq_scan_lu(lun) kfree(ufsf->ufshpb_lup[lun]); /* don't call init handler */ ufsf->ufshpb_state = HPB_NOT_SUPPORTED; #endif #if defined(CONFIG_SCSI_UFS_TW) seq_scan_lu(lun) kfree(ufsf->tw_lup[lun]); ufsf->tw_dev_info.tw_device = false; atomic_set(&ufsf->tw_state, TW_NOT_SUPPORTED); #endif return; } static void ufsf_print_query_buf(unsigned char *field, int size) { unsigned char buf[255]; unsigned int count = 0; int i; count += snprintf(buf, 8, "(0x00):"); for (i = 0; i < size; i++) { count += snprintf(buf + count, 4, " %.2X", field[i]); if ((i + 1) % 16 == 0) { buf[count] = '\n'; buf[count + 1] = '\0'; printk(buf); count = 0; count += snprintf(buf, 8, "(0x%.2X):", i + 1); } else if ((i + 1) % 4 == 0) count += snprintf(buf + count, 3, " :"); } buf[count] = '\n'; buf[count + 1] = '\0'; printk(buf); } inline int ufsf_check_query(__u32 opcode) { return (opcode & 0xffff0000) >> 16 == UFSFEATURE_QUERY_OPCODE; } int ufsf_query_ioctl(struct ufsf_feature *ufsf, unsigned int lun, void __user *buffer, struct ufs_ioctl_query_data_hpb *ioctl_data, u8 selector) { unsigned char *kernel_buf; int opcode; int err = 0; int index = 0; int length = 0; int buf_len = 0; opcode = ioctl_data->opcode & 0xffff; INFO_MSG("op %u idn %u sel %u size %u(0x%X)", opcode, ioctl_data->idn, selector, ioctl_data->buf_size, ioctl_data->buf_size); buf_len = (ioctl_data->idn == QUERY_DESC_IDN_STRING) ? IOCTL_DEV_CTX_MAX_SIZE : QUERY_DESC_MAX_SIZE; if (ioctl_data->buf_size > buf_len) { err = -EINVAL; goto out; } kernel_buf = kzalloc(buf_len, GFP_KERNEL); if (!kernel_buf) { err = -ENOMEM; goto out; } switch (opcode) { case UPIU_QUERY_OPCODE_WRITE_DESC: err = copy_from_user(kernel_buf, buffer + sizeof(struct ufs_ioctl_query_data_hpb), ioctl_data->buf_size); INFO_MSG("buf size %d", ioctl_data->buf_size); ufsf_print_query_buf(kernel_buf, ioctl_data->buf_size); if (err) goto out_release_mem; break; case UPIU_QUERY_OPCODE_READ_DESC: switch (ioctl_data->idn) { case QUERY_DESC_IDN_UNIT: if (!ufs_is_valid_unit_desc_lun(lun)) { ERR_MSG("No unit descriptor for lun 0x%x", lun); err = -EINVAL; goto out_release_mem; } index = lun; INFO_MSG("read lu desc lun: %d", index); break; case QUERY_DESC_IDN_STRING: #if defined(CONFIG_SCSI_UFS_HPB) if (!ufs_is_valid_unit_desc_lun(lun)) { ERR_MSG("No unit descriptor for lun 0x%x", lun); err = -EINVAL; goto out_release_mem; } err = ufshpb_issue_req_dev_ctx(ufsf->ufshpb_lup[lun], kernel_buf, ioctl_data->buf_size); if (err < 0) goto out_release_mem; goto copy_buffer; #endif case QUERY_DESC_IDN_DEVICE: case QUERY_DESC_IDN_GEOMETRY: case QUERY_DESC_IDN_CONFIGURATION: break; default: ERR_MSG("invalid idn %d", ioctl_data->idn); err = -EINVAL; goto out_release_mem; } break; default: ERR_MSG("invalid opcode %d", opcode); err = -EINVAL; goto out_release_mem; } length = ioctl_data->buf_size; err = ufshcd_query_descriptor_retry(ufsf->hba, opcode, ioctl_data->idn, index, selector, kernel_buf, &length); if (err) goto out_release_mem; #if defined(CONFIG_SCSI_UFS_HPB) copy_buffer: #endif if (opcode == UPIU_QUERY_OPCODE_READ_DESC) { err = copy_to_user(buffer, ioctl_data, sizeof(struct ufs_ioctl_query_data_hpb)); if (err) ERR_MSG("Failed copying back to user."); err = copy_to_user(buffer + sizeof(struct ufs_ioctl_query_data_hpb), kernel_buf, ioctl_data->buf_size); if (err) ERR_MSG("Fail: copy rsp_buffer to user space."); } out_release_mem: kfree(kernel_buf); out: return err; } inline bool ufsf_is_valid_lun(int lun) { return lun < UFS_UPIU_MAX_GENERAL_LUN; } inline int ufsf_get_ee_status(struct ufs_hba *hba, u32 *status) { return ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, QUERY_ATTR_IDN_EE_STATUS, 0, status); } /* * Wrapper functions for ufshpb. */ #if defined(CONFIG_SCSI_UFS_HPB) inline int ufsf_hpb_prepare_pre_req(struct ufsf_feature *ufsf, struct scsi_cmnd *cmd, int lun) { if (ufsf->ufshpb_state == HPB_PRESENT) return ufshpb_prepare_pre_req(ufsf, cmd, lun); return -ENODEV; } inline int ufsf_hpb_prepare_add_lrbp(struct ufsf_feature *ufsf, int add_tag) { if (ufsf->ufshpb_state == HPB_PRESENT) return ufshpb_prepare_add_lrbp(ufsf, add_tag); return -ENODEV; } inline void ufsf_hpb_end_pre_req(struct ufsf_feature *ufsf, struct request *req) { ufshpb_end_pre_req(ufsf, req); } inline void ufsf_hpb_change_lun(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { int ctx_lba = LI_EN_32(lrbp->cmd->cmnd + 2); if (ufsf->ufshpb_state == HPB_PRESENT && ufsf->issue_ioctl == true && ctx_lba == READ10_DEBUG_LBA) { lrbp->lun = READ10_DEBUG_LUN; INFO_MSG("lun 0x%X lba 0x%X", lrbp->lun, ctx_lba); } } inline void ufsf_hpb_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { if (ufsf->ufshpb_state == HPB_PRESENT && ufsf->issue_ioctl == false) ufshpb_prep_fn(ufsf, lrbp); } inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { if (ufsf->ufshpb_state == HPB_PRESENT) ufshpb_rsp_upiu(ufsf, lrbp); } inline void ufsf_hpb_reset_lu(struct ufsf_feature *ufsf) { ufsf->ufshpb_state = HPB_RESET; schedule_work(&ufsf->ufshpb_reset_work); } inline void ufsf_hpb_reset_host(struct ufsf_feature *ufsf) { if (ufsf->ufshpb_state == HPB_PRESENT) ufsf->ufshpb_state = HPB_RESET; } inline void ufsf_hpb_init(struct ufsf_feature *ufsf) { if (ufsf->hpb_dev_info.hpb_device && ufsf->ufshpb_state == HPB_NEED_INIT) { INIT_WORK(&ufsf->ufshpb_init_work, ufshpb_init_handler); schedule_work(&ufsf->ufshpb_init_work); } } inline void ufsf_hpb_reset(struct ufsf_feature *ufsf) { if (ufsf->hpb_dev_info.hpb_device && ufsf->ufshpb_state == HPB_RESET) schedule_work(&ufsf->ufshpb_reset_work); } inline void ufsf_hpb_suspend(struct ufsf_feature *ufsf) { if (ufsf->ufshpb_state == HPB_PRESENT) ufshpb_suspend(ufsf); } inline void ufsf_hpb_resume(struct ufsf_feature *ufsf) { if (ufsf->ufshpb_state == HPB_PRESENT) ufshpb_resume(ufsf); } inline void ufsf_hpb_release(struct ufsf_feature *ufsf) { ufshpb_release(ufsf, HPB_NEED_INIT); } inline void ufsf_hpb_set_init_state(struct ufsf_feature *ufsf) { ufsf->ufshpb_state = HPB_NEED_INIT; } #else inline int ufsf_hpb_prepare_pre_req(struct ufsf_feature *ufsf, struct scsi_cmnd *cmd, int lun) { return 0; } inline int ufsf_hpb_prepare_add_lrbp(struct ufsf_feature *ufsf, int add_tag) { return 0; } inline void ufsf_hpb_end_pre_req(struct ufsf_feature *ufsf, struct request *req) {} inline void ufsf_hpb_change_lun(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) {} inline void ufsf_hpb_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) {} inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) {} inline void ufsf_hpb_reset_lu(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_reset_host(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_init(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_reset(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_suspend(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_resume(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_release(struct ufsf_feature *ufsf) {} inline void ufsf_hpb_set_init_state(struct ufsf_feature *ufsf) {} #endif /* * Wrapper functions for ufstw. */ #if defined(CONFIG_SCSI_UFS_TW) inline void ufsf_tw_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { ufstw_prep_fn(ufsf, lrbp); } inline void ufsf_tw_init(struct ufsf_feature *ufsf) { INIT_INFO("init start.. tw_state %d\n", atomic_read(&ufsf->tw_state)); if (ufsf->tw_dev_info.tw_device && atomic_read(&ufsf->tw_state) == TW_NEED_INIT) { INIT_WORK(&ufsf->tw_init_work, ufstw_init_work_fn); schedule_work(&ufsf->tw_init_work); } } inline void ufsf_tw_reset(struct ufsf_feature *ufsf) { INIT_INFO("reset start.. tw_state %d\n", atomic_read(&ufsf->tw_state)); if (ufsf->tw_dev_info.tw_device && atomic_read(&ufsf->tw_state) == TW_RESET) schedule_work(&ufsf->tw_reset_work); } inline void ufsf_tw_suspend(struct ufsf_feature *ufsf) { if (atomic_read(&ufsf->tw_state) == TW_PRESENT) ufstw_suspend(ufsf); } inline void ufsf_tw_resume(struct ufsf_feature *ufsf) { if (atomic_read(&ufsf->tw_state) == TW_PRESENT) ufstw_resume(ufsf); } inline void ufsf_tw_release(struct ufsf_feature *ufsf) { ufstw_release(&ufsf->tw_kref); } inline void ufsf_tw_set_init_state(struct ufsf_feature *ufsf) { atomic_set(&ufsf->tw_state, TW_NEED_INIT); } inline void ufsf_tw_reset_lu(struct ufsf_feature *ufsf) { INFO_MSG("run reset_lu.. tw_state(%d) -> TW_RESET", atomic_read(&ufsf->tw_state)); atomic_set(&ufsf->tw_state, TW_RESET); if (ufsf->tw_dev_info.tw_device) schedule_work(&ufsf->tw_reset_work); } inline void ufsf_tw_reset_host(struct ufsf_feature *ufsf) { INFO_MSG("run reset_host.. tw_state(%d) -> TW_RESET", atomic_read(&ufsf->tw_state)); if (atomic_read(&ufsf->tw_state) == TW_PRESENT) atomic_set(&ufsf->tw_state, TW_RESET); } inline void ufsf_tw_ee_handler(struct ufsf_feature *ufsf) { u32 status = 0; int err; if (ufsf->tw_debug && (atomic_read(&ufsf->tw_state) != TW_PRESENT)) { ERR_MSG("tw_state %d", atomic_read(&ufsf->tw_state)); return; } if ((atomic_read(&ufsf->tw_state) == TW_PRESENT) && (ufsf->tw_ee_mode == TW_EE_MODE_AUTO)) { err = ufsf_get_ee_status(ufsf->hba, &status); if (err) { dev_err(ufsf->hba->dev, "%s: failed to get tw ee status %d\n", __func__, err); return; } if (status & MASK_EE_TW) ufstw_ee_handler(ufsf); } } #else inline void ufsf_tw_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) {} inline void ufsf_tw_init(struct ufsf_feature *ufsf) {} inline void ufsf_tw_reset(struct ufsf_feature *ufsf) {} inline void ufsf_tw_suspend(struct ufsf_feature *ufsf) {} inline void ufsf_tw_resume(struct ufsf_feature *ufsf) {} inline void ufsf_tw_release(struct ufsf_feature *ufsf) {} inline void ufsf_tw_set_init_state(struct ufsf_feature *ufsf) {} inline void ufsf_tw_reset_lu(struct ufsf_feature *ufsf) {} inline void ufsf_tw_reset_host(struct ufsf_feature *ufsf) {} inline void ufsf_tw_ee_handler(struct ufsf_feature *ufsf) {} #endif