| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Universal Flash Storage Feature Support |
| * |
| * Copyright (C) 2017-2018 Samsung Electronics Co., Ltd. |
| * |
| * Authors: |
| * Yongmyung Lee <ymhungry.lee@samsung.com> |
| * Jinyoung Choi <j-young.choi@samsung.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * See the COPYING file in the top-level directory or visit |
| * <http://www.gnu.org/licenses/gpl-2.0.html> |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * This program is provided "AS IS" and "WITH ALL FAULTS" and |
| * without warranty of any kind. You are solely responsible for |
| * determining the appropriateness of using and distributing |
| * the program and assume all risks associated with your exercise |
| * of rights with respect to the program, including but not limited |
| * to infringement of third party rights, the risks and costs of |
| * program errors, damage to or loss of data, programs or equipment, |
| * and unavailability or interruption of operations. Under no |
| * circumstances will the contributor of this Program be liable for |
| * any damages of any kind arising from your use or distribution of |
| * this program. |
| * |
| * The Linux Foundation chooses to take subject only to the GPLv2 |
| * license terms, and distributes only under these terms. |
| */ |
| |
| #include "ufsfeature.h" |
| #include "ufshcd.h" |
| #include "ufs-mediatek.h" |
| |
| 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; |
| |
| pm_runtime_get_sync(hba->dev); |
| |
| 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); |
| |
| 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]; |
| 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]; |
| INFO_MSG("device lu count %d", ufsf->num_lu); |
| |
| INFO_MSG("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_UFS_FEAT], |
| desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP+2], |
| desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP+3]); |
| |
| INFO_MSG("Driver Feature Version : (%.6X%s)", UFSFEATURE_DD_VER, |
| UFSFEATURE_DD_VER_POST); |
| |
| #if defined(CONFIG_UFSSHPB) |
| ufsshpb_get_dev_info(ufsf, desc_buf); |
| #endif |
| #if defined(CONFIG_UFSTW) |
| ufstw_get_dev_info(ufsf, desc_buf); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| ufshid_get_dev_info(ufsf, desc_buf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| ufsringbuf_get_dev_info(ufsf, 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_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_NEED_INIT) |
| ufsshpb_get_geo_info(ufsf, geo_buf); |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_NEED_INIT) |
| ufstw_get_geo_info(ufsf, geo_buf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_NEED_INIT) |
| ufsringbuf_get_geo_info(ufsf, geo_buf); |
| #endif |
| return 0; |
| } |
| |
| static void ufsf_read_unit_desc(struct ufsf_feature *ufsf, 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; |
| |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_NEED_INIT) |
| ufsshpb_get_lu_info(ufsf, lun, unit_buf); |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_NEED_INIT) |
| ufstw_alloc_lu(ufsf, lun, unit_buf); |
| #endif |
| out: |
| return; |
| } |
| |
| void ufsf_device_check(struct ufs_hba *hba) |
| { |
| struct ufsf_feature *ufsf = ufs_mtk_get_ufsf(hba); |
| int ret, lun; |
| u32 status; |
| |
| ufshcd_query_attr(ufsf->hba, UPIU_QUERY_OPCODE_READ_ATTR, |
| QUERY_ATTR_IDN_SUP_VENDOR_OPTIONS, 0, 0, &status); |
| INFO_MSG("UFS FEATURE SELECTOR Dev %d - D/D %d", status, |
| UFSFEATURE_SELECTOR); |
| |
| ret = ufsf_read_dev_desc(ufsf, UFSFEATURE_SELECTOR); |
| if (ret) |
| return; |
| |
| ret = ufsf_read_geo_desc(ufsf, UFSFEATURE_SELECTOR); |
| if (ret) |
| return; |
| |
| seq_scan_lu(lun) |
| ufsf_read_unit_desc(ufsf, lun, UFSFEATURE_SELECTOR); |
| } |
| |
| static int ufsf_execute_dev_ctx_req(struct ufsf_feature *ufsf, |
| int lun, unsigned char *cdb, |
| void *buf, int len) |
| { |
| struct scsi_sense_hdr sshdr; |
| struct scsi_device *sdev; |
| int ret = 0; |
| |
| sdev = ufsf->sdev_ufs_lu[lun]; |
| if (!sdev) { |
| WARN_MSG("cannot find scsi_device"); |
| return -ENODEV; |
| } |
| |
| ufsf->issue_ioctl = true; |
| ret = scsi_execute(sdev, cdb, DMA_FROM_DEVICE, buf, len, NULL, &sshdr, |
| msecs_to_jiffies(30000), 3, 0, 0, NULL); |
| ufsf->issue_ioctl = false; |
| |
| return ret; |
| } |
| |
| static inline void ufsf_set_read_dev_ctx(unsigned char *cdb, int lba, int len) |
| { |
| cdb[0] = READ_10; |
| cdb[1] = 0x02; |
| cdb[2] = GET_BYTE_3(lba); |
| cdb[3] = GET_BYTE_2(lba); |
| cdb[4] = GET_BYTE_1(lba); |
| cdb[5] = GET_BYTE_0(lba); |
| cdb[6] = GET_BYTE_2(len); |
| cdb[7] = GET_BYTE_1(len); |
| cdb[8] = GET_BYTE_0(len); |
| } |
| |
| int ufsf_issue_req_dev_ctx(struct ufsf_feature *ufsf, int lun, |
| unsigned char *buf, int buf_len) |
| { |
| unsigned char cdb[10] = { 0 }; |
| int cmd_len = buf_len >> OS_PAGE_SHIFT; |
| int ret = 0; |
| |
| ufsf_set_read_dev_ctx(cdb, READ10_DEBUG_LBA, cmd_len); |
| |
| ret = ufsf_execute_dev_ctx_req(ufsf, lun, cdb, buf, buf_len); |
| |
| if (ret < 0) |
| ERR_MSG("failed with err %d", ret); |
| |
| return ret; |
| } |
| |
| static void ufsf_print_query_buf(unsigned char *field, int size) |
| { |
| unsigned char buf[255]; |
| 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; |
| } |
| |
| static inline void ufsf_set_read10_debug_cmd(unsigned char *cdb, int lba, |
| int len) |
| { |
| cdb[0] = READ_10; |
| cdb[1] = 0x02; |
| cdb[2] = GET_BYTE_3(lba); |
| cdb[3] = GET_BYTE_2(lba); |
| cdb[4] = GET_BYTE_1(lba); |
| cdb[5] = GET_BYTE_0(lba); |
| cdb[6] = GET_BYTE_2(len); |
| cdb[7] = GET_BYTE_1(len); |
| cdb[8] = GET_BYTE_0(len); |
| } |
| |
| int ufsf_query_ioctl(struct ufsf_feature *ufsf, int lun, void __user *buffer, |
| struct ufs_ioctl_query_data *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; |
| |
| 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), |
| 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(&ufsf->hba->dev_info, lun, 0)) { |
| 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 (!ufs_is_valid_unit_desc_lun(&ufsf->hba->dev_info, lun, 0)) { |
| ERR_MSG("No unit descriptor for lun 0x%x", lun); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| err = ufsf_issue_req_dev_ctx(ufsf, lun, kernel_buf, |
| ioctl_data->buf_size); |
| if (err < 0) |
| goto out_release_mem; |
| |
| goto copy_buffer; |
| 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; |
| |
| copy_buffer: |
| if (opcode == UPIU_QUERY_OPCODE_READ_DESC) { |
| err = copy_to_user(buffer, ioctl_data, |
| sizeof(struct ufs_ioctl_query_data)); |
| if (err) |
| ERR_MSG("Failed copying back to user."); |
| |
| err = copy_to_user(buffer + sizeof(struct ufs_ioctl_query_data), |
| 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; |
| } |
| |
| bool ufsf_upiu_check_for_ccd(struct ufshcd_lrb *lrbp) |
| { |
| unsigned char *cdb = lrbp->cmd->cmnd; |
| int data_seg_len, sense_data_len; |
| |
| if (cdb[0] != VENDOR_OP || cdb[1] != VENDOR_CCD) |
| return false; |
| |
| data_seg_len = be32_to_cpu(lrbp->ucd_rsp_ptr->header.dword_2) & |
| MASK_RSP_UPIU_DATA_SEG_LEN; |
| sense_data_len = be16_to_cpu(lrbp->ucd_rsp_ptr->sr.sense_data_len); |
| |
| if (data_seg_len != CCD_DATA_SEG_LEN || |
| sense_data_len != CCD_SENSE_DATA_LEN) { |
| WARN_MSG("CCD info is wrong. so check it."); |
| WARN_MSG("CCD data_seg_len = %d, sense_data_len %d", |
| data_seg_len, sense_data_len); |
| } else { |
| INFO_MSG("CCD info is correct!!"); |
| } |
| |
| /* |
| * sense_len will be not set as Descriptor Type isn't 0x70 |
| * if not set sense_len, sense will not be able to copy |
| * in sg_scsi_ioctl() |
| */ |
| scsi_req(lrbp->cmd->request)->sense_len = CCD_SENSE_DATA_LEN; |
| |
| return true; |
| } |
| |
| inline int ufsf_get_scsi_device(struct ufs_hba *hba, struct scsi_device *sdev) |
| { |
| unsigned long flags; |
| int ret; |
| |
| spin_lock_irqsave(hba->host->host_lock, flags); |
| ret = scsi_device_get(sdev); |
| if (!ret && !scsi_device_online(sdev)) { |
| spin_unlock_irqrestore(hba->host->host_lock, flags); |
| scsi_device_put(sdev); |
| ERR_MSG("scsi_device_get failed.(%d)", ret); |
| return -ENODEV; |
| } |
| spin_unlock_irqrestore(hba->host->host_lock, flags); |
| |
| return ret; |
| } |
| |
| inline bool ufsf_is_valid_lun(int lun) |
| { |
| return lun < UFS_UPIU_MAX_GENERAL_LUN; |
| } |
| |
| inline void ufsf_slave_configure(struct ufsf_feature *ufsf, |
| struct scsi_device *sdev) |
| { |
| struct ufs_hba *hba = shost_priv(sdev->host); |
| |
| ufsf->hba = hba; |
| if (ufsf_is_valid_lun(sdev->lun)) { |
| ufsf->sdev_ufs_lu[sdev->lun] = sdev; |
| ufsf->slave_conf_cnt++; |
| INFO_MSG("lun[%d] sdev(%p) q(%p) slave_conf_cnt(%d)", |
| (int)sdev->lun, sdev, sdev->request_queue, |
| ufsf->slave_conf_cnt); |
| |
| } |
| schedule_work(&ufsf->device_check_work); |
| } |
| |
| inline int ufsf_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) |
| { |
| int ret = 0; |
| |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_PRESENT && |
| ufsf->issue_ioctl == false) { |
| ret = ufsshpb_prep_fn(ufsf, lrbp); |
| if (ret == -EAGAIN) |
| return ret; |
| } |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_PRESENT) |
| ufstw_prep_fn(ufsf, lrbp); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_PRESENT || |
| ufsringbuf_get_state(ufsf) == RINGBUF_RESET) |
| ufsringbuf_prep_fn(ufsf, lrbp); |
| #endif |
| |
| return ret; |
| } |
| |
| inline void ufsf_prepare_reset_lu(struct ufsf_feature *ufsf) |
| { |
| #if defined(CONFIG_UFSTW) |
| INFO_MSG("run reset_lu.. tw_state(%d) -> TW_PREPARE_RESET", |
| ufstw_get_state(ufsf)); |
| ufstw_set_state(ufsf, TW_PREPARE_RESET); |
| #endif |
| } |
| |
| inline void ufsf_reset_lu(struct ufsf_feature *ufsf) |
| { |
| #if defined(CONFIG_UFSTW) |
| ufsf->reset_lu_pos = ufsf->hba->ufs_stats.event[UFS_EVT_DEV_RESET].pos; |
| schedule_work(&ufsf->reset_lu_work); |
| #endif |
| } |
| |
| inline void ufsf_reset_lu_handler(struct work_struct *work) |
| { |
| #if defined(CONFIG_UFSTW) |
| struct ufsf_feature *ufsf; |
| int retry; |
| |
| ufsf = container_of(work, struct ufsf_feature, reset_lu_work); |
| |
| retry = ufsf->hba->nutrs; |
| |
| if (ufstw_get_state(ufsf) != TW_PREPARE_RESET) |
| return; |
| |
| // check pos diff |
| while (ufsf->reset_lu_pos == ufsf->hba->ufs_stats.event[UFS_EVT_DEV_RESET].pos) { |
| if (--retry < 0) |
| break; |
| |
| // In ufshcd_clear_cmd(), it waits 1s for each doorbell. |
| msleep(1000); |
| } |
| |
| if ((ufsf->reset_lu_pos == ufsf->hba->ufs_stats.event[UFS_EVT_DEV_RESET].pos) || |
| (ufsf->hba->ufs_stats.event[UFS_EVT_DEV_RESET].val[ufsf->reset_lu_pos] != 0)) { |
| /* |
| * TW is not reset in this LU |
| * We just restore its state as PRESENT |
| */ |
| ufstw_set_state(ufsf, TW_PRESENT); |
| return; |
| } |
| |
| INFO_MSG("run reset_lu.. tw_state(%d) -> TW_RESET", |
| ufstw_get_state(ufsf)); |
| ufstw_set_state(ufsf, TW_RESET); |
| ufstw_reset(ufsf); |
| #endif |
| } |
| |
| /* |
| * called by ufshcd_vops_device_reset() |
| */ |
| inline void ufsf_reset_host(struct ufsf_feature *ufsf) |
| { |
| struct ufs_hba *hba = ufsf->hba; |
| struct Scsi_Host *host = hba->host; |
| unsigned long flags; |
| u32 eh_flags; |
| |
| if (!ufsf->check_init) |
| return; |
| |
| /* |
| * Check if it is error handling(eh) context. |
| * |
| * In the following cases, we can enter here even though it is not in eh |
| * context. |
| * - when ufshcd_is_link_off() is true in ufshcd_resume() |
| * - when ufshcd_vops_suspend() fails in ufshcd_suspend() |
| */ |
| spin_lock_irqsave(host->host_lock, flags); |
| eh_flags = ufshcd_eh_in_progress(hba); |
| spin_unlock_irqrestore(host->host_lock, flags); |
| if (!eh_flags) |
| return; |
| |
| #if defined(CONFIG_UFSSHPB) |
| INFO_MSG("run reset_host.. hpb_state(%d) -> HPB_RESET", |
| ufsshpb_get_state(ufsf)); |
| if (ufsshpb_get_state(ufsf) == HPB_PRESENT) |
| ufsshpb_reset_host(ufsf); |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| INFO_MSG("run reset_host.. tw_state(%d) -> TW_RESET", |
| ufstw_get_state(ufsf)); |
| if (ufstw_get_state(ufsf) == TW_PRESENT) |
| ufstw_reset_host(ufsf); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| INFO_MSG("run reset_host.. hid_state(%d) -> HID_RESET", |
| ufshid_get_state(ufsf)); |
| if (ufshid_get_state(ufsf) == HID_PRESENT) |
| ufshid_reset_host(ufsf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| INFO_MSG("run reset_host.. ringbuf_state(%d) -> RINGBUF_RESET", |
| ufsringbuf_get_state(ufsf)); |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_PRESENT) |
| ufsringbuf_reset_host(ufsf); |
| #endif |
| |
| schedule_work(&ufsf->reset_wait_work); |
| } |
| |
| inline void ufsf_init(struct ufsf_feature *ufsf) |
| { |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_NEED_INIT) { |
| INFO_MSG("init start.. hpb_state (%d)", HPB_NEED_INIT); |
| schedule_work(&ufsf->hpb_init_work); |
| } |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_NEED_INIT) |
| ufstw_init(ufsf); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| if (ufshid_get_state(ufsf) == HID_NEED_INIT) |
| ufshid_init(ufsf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_NEED_INIT) |
| ufsringbuf_init(ufsf); |
| #endif |
| |
| ufsf->check_init = true; |
| } |
| |
| inline void ufsf_reset(struct ufsf_feature *ufsf) |
| { |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_RESET) { |
| INFO_MSG("reset start.. hpb_state %d", HPB_RESET); |
| ufsshpb_reset(ufsf); |
| } |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_RESET) { |
| INFO_MSG("reset start.. tw_state %d", |
| ufstw_get_state(ufsf)); |
| ufstw_reset(ufsf); |
| } |
| #endif |
| #if defined(CONFIG_UFSHID) |
| if (ufshid_get_state(ufsf) == HID_RESET) |
| ufshid_reset(ufsf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_RESET) |
| ufsringbuf_reset(ufsf); |
| #endif |
| } |
| |
| inline void ufsf_remove(struct ufsf_feature *ufsf) |
| { |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_PRESENT) |
| ufsshpb_remove(ufsf, HPB_NEED_INIT); |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_PRESENT) |
| ufstw_remove(ufsf); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| if (ufshid_get_state(ufsf) == HID_PRESENT) |
| ufshid_remove(ufsf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_PRESENT) |
| ufsringbuf_remove(ufsf); |
| #endif |
| } |
| |
| static void ufsf_device_check_work_handler(struct work_struct *work) |
| { |
| struct ufsf_feature *ufsf; |
| |
| ufsf = container_of(work, struct ufsf_feature, device_check_work); |
| |
| mutex_lock(&ufsf->device_check_lock); |
| if (!ufsf->check_init) { |
| ufsf_device_check(ufsf->hba); |
| ufsf_init(ufsf); |
| } |
| |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsf->check_init && ufsf->num_lu == ufsf->slave_conf_cnt) { |
| if (ufsshpb_get_state(ufsf) == HPB_WAIT_INIT) { |
| INFO_MSG("wakeup ufsshpb_init_handler"); |
| wake_up(&ufsf->hpb_wait); |
| } |
| } |
| #endif |
| mutex_unlock(&ufsf->device_check_lock); |
| |
| } |
| /* |
| * worker to change the feature state to present after processing the error handler. |
| */ |
| static void ufsf_reset_wait_work_handler(struct work_struct *work) |
| { |
| struct ufsf_feature *ufsf; |
| struct ufs_hba *hba; |
| struct Scsi_Host *host; |
| u32 ufshcd_state; |
| unsigned long flags; |
| |
| ufsf = container_of(work, struct ufsf_feature, reset_wait_work); |
| hba = ufsf->hba; |
| host = hba->host; |
| |
| /* |
| * Wait completion of hba->eh_work. |
| * |
| * reset_wait_work is scheduled at ufsf_reset_host(), |
| * so it can be waken up before eh_work is completed. |
| * |
| * ufsf_reset must be called after eh_work has completed. |
| */ |
| flush_work(&hba->eh_work); |
| |
| spin_lock_irqsave(host->host_lock, flags); |
| ufshcd_state = hba->ufshcd_state; |
| spin_unlock_irqrestore(host->host_lock, flags); |
| |
| if (ufshcd_state == UFSHCD_STATE_OPERATIONAL) |
| ufsf_reset(ufsf); |
| } |
| |
| #if defined(CONFIG_UFSHID) |
| static void ufsf_on_idle(struct work_struct *work) |
| { |
| struct ufsf_feature *ufsf; |
| |
| ufsf = container_of(work, struct ufsf_feature, on_idle_work); |
| if (ufshid_get_state(ufsf) == HID_PRESENT && |
| !ufsf->hba->outstanding_reqs) |
| ufshid_on_idle(ufsf); |
| } |
| #endif |
| |
| inline void ufsf_set_init_state(struct ufsf_feature *ufsf) |
| { |
| ufsf->slave_conf_cnt = 0; |
| ufsf->issue_ioctl = false; |
| |
| mutex_init(&ufsf->device_check_lock); |
| INIT_WORK(&ufsf->device_check_work, ufsf_device_check_work_handler); |
| INIT_WORK(&ufsf->reset_wait_work, ufsf_reset_wait_work_handler); |
| INIT_WORK(&ufsf->reset_lu_work, ufsf_reset_lu_handler); |
| |
| #if defined(CONFIG_UFSSHPB) |
| ufsshpb_set_state(ufsf, HPB_NEED_INIT); |
| INIT_WORK(&ufsf->hpb_init_work, ufsshpb_init_handler); |
| init_waitqueue_head(&ufsf->hpb_wait); |
| #endif |
| #if defined(CONFIG_UFSTW) |
| ufstw_set_state(ufsf, TW_NEED_INIT); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| INIT_WORK(&ufsf->on_idle_work, ufsf_on_idle); |
| ufshid_set_state(ufsf, HID_NEED_INIT); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| ufsringbuf_set_state(ufsf, RINGBUF_NEED_INIT); |
| #endif |
| } |
| |
| inline void ufsf_suspend(struct ufsf_feature *ufsf) |
| { |
| /* |
| * Wait completion of reset_wait_work. |
| * |
| * When suspend occurrs immediately after reset |
| * and reset_wait_work is executed late, |
| * we can enter here before ufsf_reset() cleans up the feature's reset sequence. |
| */ |
| flush_work(&ufsf->reset_wait_work); |
| |
| #if defined(CONFIG_UFSSHPB) |
| /* |
| * if suspend failed, pm could call the suspend function again, |
| * in this case, ufsshpb state already had been changed to SUSPEND state. |
| * so, we will not call ufsshpb_suspend. |
| */ |
| if (ufsshpb_get_state(ufsf) == HPB_PRESENT) |
| ufsshpb_suspend(ufsf); |
| #endif |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_PRESENT) |
| ufstw_suspend(ufsf); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| if (ufshid_get_state(ufsf) == HID_PRESENT) |
| ufshid_suspend(ufsf); |
| #endif |
| } |
| |
| inline void ufsf_resume(struct ufsf_feature *ufsf, bool is_link_off) |
| { |
| #if defined(CONFIG_UFSSHPB) |
| if (ufsshpb_get_state(ufsf) == HPB_SUSPEND || |
| ufsshpb_get_state(ufsf) == HPB_PRESENT) { |
| if (ufsshpb_get_state(ufsf) == HPB_PRESENT) |
| WARN_MSG("warning.. hpb state PRESENT in resuming"); |
| ufsshpb_resume(ufsf); |
| } |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufstw_get_state(ufsf) == TW_SUSPEND) |
| ufstw_resume(ufsf, is_link_off); |
| #endif |
| #if defined(CONFIG_UFSHID) |
| if (ufshid_get_state(ufsf) == HID_SUSPEND) |
| ufshid_resume(ufsf); |
| #endif |
| #if defined(CONFIG_UFSRINGBUF) |
| if (ufsringbuf_get_state(ufsf) == RINGBUF_PRESENT) |
| ufsringbuf_resume(ufsf); |
| #endif |
| } |
| |
| inline void ufsf_change_lun(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) |
| { |
| int ctx_lba = LI_EN_32(lrbp->cmd->cmnd + 2); |
| |
| if (unlikely(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); |
| } |
| } |
| |
| /* |
| * Wrapper functions for ufsshpb. |
| */ |
| #if defined(CONFIG_UFSSHPB) |
| inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) |
| { |
| if (ufsshpb_get_state(ufsf) == HPB_PRESENT) |
| ufsshpb_rsp_upiu(ufsf, lrbp); |
| } |
| |
| #else |
| inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) {} |
| #endif |
| |
| /* |
| * Wrapper functions for ufshid. |
| */ |
| #if defined(CONFIG_UFSHID) && defined(CONFIG_UFSHID_DEBUG) |
| inline void ufsf_hid_acc_io_stat(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) |
| { |
| if (ufshid_get_state(ufsf) == HID_PRESENT) |
| ufshid_acc_io_stat_during_trigger(ufsf, lrbp); |
| } |
| #else |
| inline void ufsf_hid_acc_io_stat(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) {} |
| #endif |
| |
| MODULE_LICENSE("GPL v2"); |