| /* |
| * Universal Flash Storage Turbo Write |
| * |
| * 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 "ufshcd.h" |
| #include "ufstw.h" |
| #include "ufs_quirks.h" |
| |
| #define UFS_MTK_TW_AWAYS_ON |
| |
| static int ufstw_create_sysfs(struct ufsf_feature *ufsf, struct ufstw_lu *tw); |
| static int ufstw_clear_lu_flag(struct ufstw_lu *tw, u8 idn, bool *flag_res); |
| static int ufstw_read_lu_attr(struct ufstw_lu *tw, u8 idn, u32 *attr_val); |
| |
| /* huangjianan@TECH.Storage.UFS, 2019/12/09, Add for UFS+ RUS */ |
| static int create_wbfn_enable(void); |
| static void remove_wbfn_enable(void); |
| |
| static inline void ufstw_lu_get(struct ufstw_lu *tw) |
| { |
| kref_get(&tw->ufsf->tw_kref); |
| } |
| |
| static inline void ufstw_lu_put(struct ufstw_lu *tw) |
| { |
| kref_put(&tw->ufsf->tw_kref, ufstw_release); |
| } |
| |
| static inline bool ufstw_is_write_lrbp(struct ufshcd_lrb *lrbp) |
| { |
| if (lrbp->cmd->cmnd[0] == WRITE_10 || lrbp->cmd->cmnd[0] == WRITE_16) |
| return true; |
| |
| return false; |
| } |
| |
| static int ufstw_switch_mode(struct ufstw_lu *tw, int tw_mode) |
| { |
| int ret = 0; |
| |
| atomic_set(&tw->tw_mode, tw_mode); |
| if (tw->tw_enable) |
| ret = ufstw_clear_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, |
| &tw->tw_enable); |
| return ret; |
| } |
| |
| static void ufstw_switch_disable_mode(struct ufstw_lu *tw) |
| { |
| WARNING_MSG("dTurboWriteBUfferLifeTImeEst 0x%X", tw->tw_lifetime_est); |
| WARNING_MSG("tw-mode will change to disable-mode.."); |
| |
| mutex_lock(&tw->mode_lock); |
| ufstw_switch_mode(tw, TW_MODE_DISABLED); |
| mutex_unlock(&tw->mode_lock); |
| } |
| |
| static void ufstw_lifetime_work_fn(struct work_struct *work) |
| { |
| struct ufstw_lu *tw; |
| |
| tw = container_of(work, struct ufstw_lu, tw_lifetime_work); |
| |
| ufstw_lu_get(tw); |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| INFO_MSG("tw_state != TW_PRESENT (%d)", |
| atomic_read(&tw->ufsf->tw_state)); |
| goto out; |
| } |
| |
| if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST, |
| &tw->tw_lifetime_est)) |
| goto out; |
| |
| #if defined(CONFIG_UFSTW_IGNORE_GUARANTEE_BIT) |
| if (tw->tw_lifetime_est & MASK_UFSTW_LIFETIME_NOT_GUARANTEE) { |
| WARNING_MSG("warn: lun %d - dTurboWriteBufferLifeTimeEst[31] == 1", tw->lun); |
| WARNING_MSG("Device not guarantee the lifetime of Turbo Write Buffer"); |
| WARNING_MSG("but we will ignore them for PoC"); |
| } |
| #else |
| if (tw->tw_lifetime_est & MASK_UFSTW_LIFETIME_NOT_GUARANTEE) { |
| WARNING_MSG("warn: lun %d - dTurboWriteBufferLifeTimeEst[31] == 1", tw->lun); |
| WARNING_MSG("Device not guarantee the lifetime of Turbo Write Buffer"); |
| WARNING_MSG("So tw_mode change to disable_mode"); |
| goto tw_disable; |
| } |
| #endif |
| if ((tw->tw_lifetime_est & ~MASK_UFSTW_LIFETIME_NOT_GUARANTEE) |
| < UFSTW_MAX_LIFETIME_VALUE) |
| goto out; |
| else |
| goto tw_disable; |
| tw_disable: |
| ufstw_switch_disable_mode(tw); |
| out: |
| ufstw_lu_put(tw); |
| } |
| |
| void ufstw_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) |
| { |
| struct ufstw_lu *tw; |
| |
| if (!lrbp || !ufsf_is_valid_lun(lrbp->lun)) |
| return; |
| |
| if (!ufstw_is_write_lrbp(lrbp)) |
| return; |
| |
| tw = ufsf->tw_lup[lrbp->lun]; |
| if (!tw) |
| return; |
| |
| if (atomic_read(&tw->tw_mode) == TW_MODE_DISABLED) |
| return; |
| |
| if (!tw->tw_enable) |
| return; |
| |
| spin_lock_bh(&tw->lifetime_lock); |
| tw->stat_write_sec += blk_rq_sectors(lrbp->cmd->request); |
| |
| if (tw->stat_write_sec > UFSTW_LIFETIME_SECT) { |
| tw->stat_write_sec = 0; |
| spin_unlock_bh(&tw->lifetime_lock); |
| schedule_work(&tw->tw_lifetime_work); |
| return; |
| } |
| |
| blk_add_trace_msg(tw->ufsf->sdev_ufs_lu[tw->lun]->request_queue, |
| "%s:%d tw_lifetime_work %u", |
| __func__, __LINE__, tw->stat_write_sec); |
| spin_unlock_bh(&tw->lifetime_lock); |
| } |
| |
| static u8 ufstw_get_query_idx(struct ufstw_lu *tw) |
| { |
| u8 idx; |
| |
| /* Share buffer type only use idx 0 */ |
| if (tw->ufsf->tw_dev_info.tw_buf_type == WB_SINGLE_SHARE_BUFFER_TYPE) |
| idx = 0; |
| else |
| idx = (u8)tw->lun; |
| |
| return idx; |
| } |
| |
| static int ufstw_read_lu_attr(struct ufstw_lu *tw, u8 idn, u32 *attr_val) |
| { |
| struct ufs_hba *hba = tw->ufsf->hba; |
| int err; |
| u32 val; |
| u8 idx; |
| |
| pm_runtime_get_sync(hba->dev); |
| |
| ufstw_lu_get(tw); |
| |
| idx = ufstw_get_query_idx(tw); |
| err = ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, |
| idx, &val); |
| if (err) { |
| ERR_MSG("read attr [0x%.2X] failed...err %d", idn, err); |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| return err; |
| } |
| |
| *attr_val = val; |
| |
| blk_add_trace_msg(tw->ufsf->sdev_ufs_lu[tw->lun]->request_queue, |
| "%s:%d IDN %s (%d)", __func__, __LINE__, |
| idn == QUERY_ATTR_IDN_TW_FLUSH_STATUS ? "TW_FLUSH_STATUS" : |
| idn == QUERY_ATTR_IDN_TW_BUF_SIZE ? "TW_BUF_SIZE" : |
| idn == QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST ? "TW_BUF_LIFETIME_EST" : |
| "UNKNOWN", idn); |
| |
| TW_DEBUG(tw->ufsf, "tw_attr LUN(%d) [0x%.2X] %u", tw->lun, idn, |
| *attr_val); |
| |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| |
| switch(idn){ |
| case QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST: |
| ufsf_para.tw_lifetime = val; |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int ufstw_set_lu_flag(struct ufstw_lu *tw, u8 idn, bool *flag_res) |
| { |
| struct ufs_hba *hba = tw->ufsf->hba; |
| int err; |
| u8 idx; |
| |
| pm_runtime_get_sync(hba->dev); |
| ufstw_lu_get(tw); |
| |
| idx = ufstw_get_query_idx(tw); |
| err = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_SET_FLAG, idn, |
| idx, NULL); |
| if (err) { |
| ERR_MSG("set flag [0x%.2X] failed...err %d", idn, err); |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| return err; |
| } |
| |
| *flag_res = true; |
| blk_add_trace_msg(tw->ufsf->sdev_ufs_lu[tw->lun]->request_queue, |
| "%s:%d IDN %s (%d)", __func__, __LINE__, |
| idn == QUERY_FLAG_IDN_TW_EN ? "TW_EN" : |
| idn == QUERY_FLAG_IDN_TW_BUF_FLUSH_EN ? "FLUSH_EN" : |
| idn == QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN ? |
| "HIBERN_EN" : "UNKNOWN", idn); |
| |
| TW_DEBUG(tw->ufsf, "tw_flag LUN(%d) [0x%.2X] %u", tw->lun, idn, |
| *flag_res); |
| |
| switch(idn){ |
| case QUERY_FLAG_IDN_TW_EN: |
| ufsf_para.tw_enable = true; |
| break; |
| default: |
| break; |
| } |
| |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| |
| return 0; |
| } |
| |
| static int ufstw_clear_lu_flag(struct ufstw_lu *tw, u8 idn, bool *flag_res) |
| { |
| struct ufs_hba *hba = tw->ufsf->hba; |
| int err; |
| u8 idx; |
| |
| pm_runtime_get_sync(hba->dev); |
| ufstw_lu_get(tw); |
| |
| idx = ufstw_get_query_idx(tw); |
| err = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_CLEAR_FLAG, idn, |
| idx, NULL); |
| if (err) { |
| ERR_MSG("clear flag [0x%.2X] failed...err%d", idn, err); |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| return err; |
| } |
| |
| *flag_res = false; |
| |
| blk_add_trace_msg(tw->ufsf->sdev_ufs_lu[tw->lun]->request_queue, |
| "%s:%d IDN %s (%d)", __func__, __LINE__, |
| idn == QUERY_FLAG_IDN_TW_EN ? "TW_EN" : |
| idn == QUERY_FLAG_IDN_TW_BUF_FLUSH_EN ? "FLUSH_EN" : |
| idn == QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN ? "HIBERN_EN" : |
| "UNKNOWN", idn); |
| |
| TW_DEBUG(tw->ufsf, "tw_flag LUN(%d) [0x%.2X] %u", tw->lun, idn, |
| *flag_res); |
| |
| switch(idn){ |
| case QUERY_FLAG_IDN_TW_EN: |
| ufsf_para.tw_enable = false; |
| break; |
| default: |
| break; |
| } |
| |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| return 0; |
| } |
| |
| static inline int ufstw_read_lu_flag(struct ufstw_lu *tw, u8 idn, |
| bool *flag_res) |
| { |
| struct ufs_hba *hba = tw->ufsf->hba; |
| int err; |
| bool val = 0; |
| u8 idx; |
| |
| pm_runtime_get_sync(hba->dev); |
| ufstw_lu_get(tw); |
| |
| idx = ufstw_get_query_idx(tw); |
| err = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG, idn, |
| idx, &val); |
| if (err) { |
| ERR_MSG("read flag [0x%.2X] failed...err%d", idn, err); |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| return err; |
| } |
| |
| *flag_res = val; |
| |
| TW_DEBUG(tw->ufsf, "tw_flag LUN(%d) [0x%.2X] %u", tw->lun, idn, |
| *flag_res); |
| |
| ufstw_lu_put(tw); |
| pm_runtime_put_sync(hba->dev); |
| return 0; |
| |
| } |
| |
| /* device level (ufsf) */ |
| static int ufstw_auto_ee(struct ufsf_feature *ufsf) |
| { |
| struct ufs_hba *hba = ufsf->hba; |
| u16 mask = MASK_EE_TW; |
| u32 val; |
| int err = 0; |
| |
| pm_runtime_get_sync(hba->dev); |
| |
| if (hba->ee_ctrl_mask & mask) |
| goto out; |
| |
| val = hba->ee_ctrl_mask | mask; |
| val &= 0xFFFF; /* 2 bytes */ |
| err = ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, |
| QUERY_ATTR_IDN_EE_CONTROL, 0, &val); |
| if (err) { |
| ERR_MSG("failed to enable exception event err%d", err); |
| goto out; |
| } |
| |
| hba->ee_ctrl_mask |= mask; |
| ufsf->tw_ee_mode = true; |
| |
| TW_DEBUG(ufsf, "turbo_write_exception_event_enable"); |
| out: |
| pm_runtime_put_sync(hba->dev); |
| return err; |
| } |
| |
| /* device level (ufsf) */ |
| static int ufstw_disable_ee(struct ufsf_feature *ufsf) |
| { |
| struct ufs_hba *hba = ufsf->hba; |
| u16 mask = MASK_EE_TW; |
| int err = 0; |
| u32 val; |
| |
| pm_runtime_get_sync(hba->dev); |
| |
| if (!(hba->ee_ctrl_mask & mask)) |
| goto out; |
| |
| val = hba->ee_ctrl_mask & ~mask; |
| val &= 0xFFFF; /* 2 bytes */ |
| err = ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, |
| QUERY_ATTR_IDN_EE_CONTROL, 0, &val); |
| if (err) { |
| ERR_MSG("failed to disable exception event err%d", err); |
| goto out; |
| } |
| |
| hba->ee_ctrl_mask &= ~mask; |
| ufsf->tw_ee_mode = false; |
| |
| TW_DEBUG(ufsf, "turbo_write_exeception_event_disable"); |
| out: |
| pm_runtime_put_sync(hba->dev); |
| return err; |
| } |
| |
| static void ufstw_flush_work_fn(struct work_struct *dwork) |
| { |
| struct ufs_hba *hba; |
| struct ufstw_lu *tw; |
| bool need_resched = false; |
| |
| tw = container_of(dwork, struct ufstw_lu, tw_flush_work.work); |
| |
| TW_DEBUG(tw->ufsf, "start flush worker"); |
| |
| ufstw_lu_get(tw); |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| ERR_MSG("tw_state != TW_PRESENT (%d)", |
| atomic_read(&tw->ufsf->tw_state)); |
| ufstw_lu_put(tw); |
| return; |
| } |
| |
| hba = tw->ufsf->hba; |
| if (tw->next_q && time_before(jiffies, tw->next_q)) { |
| if (schedule_delayed_work(&tw->tw_flush_work, |
| tw->next_q - jiffies)) |
| pm_runtime_get_noresume(hba->dev); |
| ufstw_lu_put(tw); |
| return; |
| } |
| |
| pm_runtime_get_sync(hba->dev); |
| if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_SIZE, |
| &tw->tw_available_buffer_size)) |
| goto error_put; |
| |
| mutex_lock(&tw->flush_lock); |
| |
| if (tw->tw_flush_during_hibern_enter && |
| tw->tw_available_buffer_size >= tw->flush_th_max) { |
| TW_DEBUG(tw->ufsf, "flush_disable QR (%d, %d)", |
| tw->lun, tw->tw_available_buffer_size); |
| |
| if (ufstw_clear_lu_flag(tw, |
| QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) |
| goto error_unlock; |
| tw->next_q = 0; |
| need_resched = false; |
| } else if (tw->tw_available_buffer_size < tw->flush_th_max) { |
| if (tw->tw_flush_during_hibern_enter) { |
| need_resched = true; |
| } else if (tw->tw_available_buffer_size < tw->flush_th_min) { |
| TW_DEBUG(tw->ufsf, "flush_enable QR (%d, %d)", |
| tw->lun, tw->tw_available_buffer_size); |
| if (ufstw_set_lu_flag(tw, |
| QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) |
| goto error_unlock; |
| need_resched = true; |
| } else { |
| need_resched = false; |
| } |
| } |
| mutex_unlock(&tw->flush_lock); |
| |
| pm_runtime_put_noidle(hba->dev); |
| pm_runtime_put(hba->dev); |
| |
| if (need_resched) { |
| tw->next_q = |
| jiffies + msecs_to_jiffies(UFSTW_FLUSH_CHECK_PERIOD_MS); |
| if (schedule_delayed_work(&tw->tw_flush_work, |
| msecs_to_jiffies(UFSTW_FLUSH_CHECK_PERIOD_MS))) |
| pm_runtime_get_noresume(hba->dev); |
| } |
| ufstw_lu_put(tw); |
| return; |
| error_unlock: |
| mutex_unlock(&tw->flush_lock); |
| error_put: |
| pm_runtime_put_noidle(hba->dev); |
| pm_runtime_put(hba->dev); |
| |
| if (tw->next_q) { |
| tw->next_q = |
| jiffies + msecs_to_jiffies(UFSTW_FLUSH_CHECK_PERIOD_MS); |
| if (schedule_delayed_work(&tw->tw_flush_work, |
| msecs_to_jiffies(UFSTW_FLUSH_CHECK_PERIOD_MS))) |
| pm_runtime_get_noresume(hba->dev); |
| } |
| ufstw_lu_put(tw); |
| } |
| |
| void ufstw_error_handler(struct ufsf_feature *ufsf) |
| { |
| ERR_MSG("tw_state : %d -> %d", atomic_read(&ufsf->tw_state), TW_FAILED); |
| atomic_set(&ufsf->tw_state, TW_FAILED); |
| dump_stack(); |
| kref_put(&ufsf->tw_kref, ufstw_release); |
| } |
| |
| void ufstw_ee_handler(struct ufsf_feature *ufsf) |
| { |
| struct ufs_hba *hba; |
| int lun; |
| |
| hba = ufsf->hba; |
| |
| if (ufsf->tw_debug) |
| atomic64_inc(&ufsf->tw_debug_ee_count); |
| |
| seq_scan_lu(lun) { |
| if (!ufsf->tw_lup[lun]) |
| continue; |
| |
| if (!ufsf->sdev_ufs_lu[lun]) { |
| WARNING_MSG("warn: lun %d don't have scsi_device", lun); |
| continue; |
| } |
| |
| ufstw_lu_get(ufsf->tw_lup[lun]); |
| if (!delayed_work_pending(&ufsf->tw_lup[lun]->tw_flush_work)) { |
| ufsf->tw_lup[lun]->next_q = jiffies; |
| if (schedule_delayed_work(&ufsf->tw_lup[lun]->tw_flush_work, |
| msecs_to_jiffies(0))) |
| pm_runtime_get_noresume(hba->dev); |
| } |
| ufstw_lu_put(ufsf->tw_lup[lun]); |
| } |
| } |
| |
| static void ufstw_flush_h8_work_fn(struct work_struct *dwork) |
| { |
| struct ufs_hba *hba; |
| struct ufstw_lu *tw; |
| |
| tw = container_of(dwork, struct ufstw_lu, tw_flush_h8_work.work); |
| hba = tw->ufsf->hba; |
| |
| /* Exit runtime suspend and do flush in hibern 1 sec */ |
| pm_runtime_get_sync(hba->dev); |
| msleep(1000); |
| |
| /* Check again, if still need flush reschedule itself */ |
| if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_SIZE, |
| &tw->tw_available_buffer_size)) { |
| ERR_MSG("ERROR: get available tw buffer size error"); |
| goto out; |
| } |
| if (tw->tw_available_buffer_size < tw->flush_th_max) |
| schedule_delayed_work(&tw->tw_flush_h8_work, 0); |
| |
| out: |
| pm_runtime_put_sync(hba->dev); |
| } |
| |
| bool ufstw_need_flush(struct ufsf_feature *ufsf) |
| { |
| struct ufs_hba *hba = ufsf->hba; |
| struct ufstw_lu *tw; |
| bool need_flush = false; |
| u8 idx; |
| |
| if (!ufsf->tw_lup[2]) |
| goto out; |
| |
| if (!ufsf->sdev_ufs_lu[2]) { |
| WARNING_MSG("warn: lun 2 don't have scsi_device"); |
| goto out; |
| } |
| |
| tw = ufsf->tw_lup[2]; |
| |
| if (atomic_read(&tw->tw_mode) == TW_MODE_DISABLED) |
| goto out; |
| |
| if (!tw->tw_enable) |
| goto out; |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) |
| goto out; |
| |
| ufstw_lu_get(tw); |
| |
| /* |
| * No need check again, let ufstw_flush_h8_work_fn finish is enough. |
| * Only return need_flush to break runtime/system suspend. |
| */ |
| if (work_busy(&tw->tw_flush_h8_work.work)) { |
| need_flush = true; |
| goto out_put; |
| } |
| |
| idx = ufstw_get_query_idx(tw); |
| if (ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, |
| QUERY_ATTR_IDN_TW_BUF_SIZE, |
| idx, |
| &tw->tw_available_buffer_size)) { |
| ERR_MSG("ERROR: get available tw buffer size error"); |
| goto out_put; |
| } |
| |
| /* No need flush */ |
| if (tw->tw_available_buffer_size >= tw->flush_th_max) |
| goto out_put; |
| |
| /* Need flush, check device flush method */ |
| if (hba->dev_quirks & UFS_DEVICE_QUIRK_WRITE_BOOSETER_FLUSH) { |
| /* Toshiba device recover WB by toggle fWriteBoosterEn */ |
| if (ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_CLEAR_FLAG, |
| QUERY_FLAG_IDN_TW_EN, idx, NULL)) { |
| ERR_MSG("ERROR: disable tw error"); |
| goto out_put; |
| } |
| |
| if (ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_SET_FLAG, |
| QUERY_FLAG_IDN_TW_EN, idx, NULL)) { |
| ERR_MSG("ERROR: enable tw error"); |
| goto out_put; |
| } |
| } else { |
| /* Other device recover WB by hibernate */ |
| schedule_delayed_work(&tw->tw_flush_h8_work, 0); |
| need_flush = true; |
| } |
| |
| out_put: |
| if (need_flush) { |
| INFO_MSG("UFS TW available buffer size(%d) < %d0%%", |
| tw->tw_available_buffer_size, tw->flush_th_max); |
| } |
| |
| ufstw_lu_put(tw); |
| out: |
| return need_flush; |
| } |
| |
| static inline void ufstw_init_dev_jobs(struct ufsf_feature *ufsf) |
| { |
| INIT_INFO("INIT_WORK(tw_reset_work)"); |
| INIT_WORK(&ufsf->tw_reset_work, ufstw_reset_work_fn); |
| } |
| |
| static inline void ufstw_init_lu_jobs(struct ufstw_lu *tw) |
| { |
| INIT_INFO("INIT_DELAYED_WORK(tw_flush_work) ufstw_lu%d", tw->lun); |
| INIT_DELAYED_WORK(&tw->tw_flush_work, ufstw_flush_work_fn); |
| INIT_DELAYED_WORK(&tw->tw_flush_h8_work, ufstw_flush_h8_work_fn); |
| INIT_INFO("INIT_WORK(tw_lifetime_work)"); |
| INIT_WORK(&tw->tw_lifetime_work, ufstw_lifetime_work_fn); |
| } |
| |
| static inline void ufstw_cancel_lu_jobs(struct ufstw_lu *tw) |
| { |
| int ret; |
| |
| ret = cancel_delayed_work_sync(&tw->tw_flush_work); |
| ret = cancel_work_sync(&tw->tw_lifetime_work); |
| } |
| |
| void ufstw_get_dev_info(struct ufstw_dev_info *tw_dev_info, u8 *desc_buf) |
| { |
| struct ufsf_feature *ufsf; |
| struct ufs_hba *hba; |
| |
| ufsf = container_of(tw_dev_info, struct ufsf_feature, tw_dev_info); |
| hba = ufsf->hba; |
| |
| tw_dev_info->tw_device = false; |
| |
| if (UFSF_EFS_TURBO_WRITE |
| & LI_EN_32(&desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP])) |
| INIT_INFO("bUFSExFeaturesSupport: TW support"); |
| else { |
| INIT_INFO("bUFSExFeaturesSupport: TW not support"); |
| return; |
| } |
| tw_dev_info->tw_buf_no_reduct = |
| desc_buf[DEVICE_DESC_PARAM_TW_RETURN_TO_USER]; |
| tw_dev_info->tw_buf_type = desc_buf[DEVICE_DESC_PARAM_TW_BUF_TYPE]; |
| |
| /* Set TW device if TW support */ |
| tw_dev_info->tw_device = true; |
| |
| INFO_MSG("tw_dev [53] bTurboWriteBufferNoUserSpaceReductionEn %u", |
| tw_dev_info->tw_buf_no_reduct); |
| INFO_MSG("tw_dev [54] bTurboWriteBufferType %u", |
| tw_dev_info->tw_buf_type); |
| } |
| |
| void ufstw_get_geo_info(struct ufstw_dev_info *tw_dev_info, u8 *geo_buf) |
| { |
| tw_dev_info->tw_number_lu = geo_buf[GEOMETRY_DESC_TW_NUMBER_LU]; |
| if (tw_dev_info->tw_number_lu == 0) { |
| ERR_MSG("Turbo Write is not supported"); |
| tw_dev_info->tw_device = false; |
| return; |
| } |
| |
| INFO_MSG("tw_geo [4F:52] dTurboWriteBufferMaxNAllocUnits %u", |
| LI_EN_32(&geo_buf[GEOMETRY_DESC_TW_MAX_SIZE])); |
| INFO_MSG("tw_geo [53] bDeviceMaxTurboWriteLUs %u", |
| tw_dev_info->tw_number_lu); |
| INFO_MSG("tw_geo [54] bTurboWriteBufferCapAdjFac %u", |
| geo_buf[GEOMETRY_DESC_TW_CAP_ADJ_FAC]); |
| INFO_MSG("tw_geo [55] bSupportedTurboWriteBufferUserSpaceReductionTypes %u", |
| geo_buf[GEOMETRY_DESC_TW_SUPPORT_USER_REDUCTION_TYPES]); |
| INFO_MSG("tw_geo [56] bSupportedTurboWriteBufferTypes %u", |
| geo_buf[GEOMETRY_DESC_TW_SUPPORT_BUF_TYPE]); |
| } |
| |
| int ufstw_get_lu_info(struct ufsf_feature *ufsf, unsigned int lun, u8 *lu_buf) |
| { |
| struct ufsf_lu_desc lu_desc; |
| struct ufstw_lu *tw; |
| |
| lu_desc.tw_lu_buf_size = |
| LI_EN_32(&lu_buf[UNIT_DESC_TW_LU_MAX_BUF_SIZE]); |
| |
| ufsf->tw_lup[lun] = NULL; |
| |
| /* MTK: tw_buf_type = 0(LU base), 1(Single share) */ |
| if ((lu_desc.tw_lu_buf_size) || |
| ((ufsf->tw_dev_info.tw_buf_type == WB_SINGLE_SHARE_BUFFER_TYPE) |
| && (lun == 2))) { |
| ufsf->tw_lup[lun] = |
| kzalloc(sizeof(struct ufstw_lu), GFP_KERNEL); |
| if (!ufsf->tw_lup[lun]) |
| return -ENOMEM; |
| |
| tw = ufsf->tw_lup[lun]; |
| tw->ufsf = ufsf; |
| tw->lun = lun; |
| ufsf_para.buffer_size = lu_desc.tw_lu_buf_size; |
| INIT_INFO("tw_lu LUN(%d) [29:2C] dLUNumTurboWriteBufferAllocUnits %u", |
| lun, lu_desc.tw_lu_buf_size); |
| } else { |
| INIT_INFO("tw_lu LUN(%d) [29:2C] dLUNumTurboWriteBufferAllocUnits %u", |
| lun, lu_desc.tw_lu_buf_size); |
| INIT_INFO("===== LUN(%d) is TurboWrite-disabled.", lun); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static inline void ufstw_print_lu_flag_attr(struct ufstw_lu *tw) |
| { |
| INFO_MSG("tw_flag LUN(%d) [%u] fTurboWriteEn %u", tw->lun, |
| QUERY_FLAG_IDN_TW_EN, tw->tw_enable); |
| INFO_MSG("tw_flag LUN(%d) [%u] fTurboWriteBufferFlushEn %u", tw->lun, |
| QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, tw->tw_flush_enable); |
| INFO_MSG("tw_flag LUN(%d) [%u] fTurboWriteBufferFlushDuringHibernateEnter %u", |
| tw->lun, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| tw->tw_flush_during_hibern_enter); |
| |
| INFO_MSG("tw_attr LUN(%d) [%u] flush_status %u", tw->lun, |
| QUERY_ATTR_IDN_TW_FLUSH_STATUS, tw->tw_flush_status); |
| INFO_MSG("tw_attr LUN(%d) [%u] buffer_size %u", tw->lun, |
| QUERY_ATTR_IDN_TW_BUF_SIZE, tw->tw_available_buffer_size); |
| INFO_MSG("tw_attr LUN(%d) [%d] bufffer_lifetime %u(0x%X)", |
| tw->lun, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST, |
| tw->tw_lifetime_est, tw->tw_lifetime_est); |
| } |
| |
| static inline void ufstw_lu_update(struct ufstw_lu *tw) |
| { |
| ufstw_lu_get(tw); |
| |
| /* Flag */ |
| if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, &tw->tw_enable)) |
| goto error_put; |
| |
| if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, |
| &tw->tw_flush_enable)) |
| goto error_put; |
| |
| if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) |
| goto error_put; |
| |
| /* Attribute */ |
| if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_FLUSH_STATUS, |
| &tw->tw_flush_status)) |
| goto error_put; |
| |
| if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_SIZE, |
| &tw->tw_available_buffer_size)) |
| goto error_put; |
| |
| ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST, |
| &tw->tw_lifetime_est); |
| error_put: |
| ufstw_lu_put(tw); |
| } |
| |
| static void ufstw_lu_init(struct ufsf_feature *ufsf, unsigned int lun) |
| { |
| struct ufstw_lu *tw = ufsf->tw_lup[lun]; |
| |
| ufstw_lu_get(tw); |
| tw->ufsf = ufsf; |
| |
| mutex_init(&tw->flush_lock); |
| mutex_init(&tw->mode_lock); |
| spin_lock_init(&tw->lifetime_lock); |
| |
| tw->stat_write_sec = 0; |
| atomic_set(&tw->active_cnt, 0); |
| |
| tw->flush_th_min = UFSTW_FLUSH_WORKER_TH_MIN; |
| tw->flush_th_max = UFSTW_FLUSH_WORKER_TH_MAX; |
| |
| /* for Debug */ |
| ufstw_init_lu_jobs(tw); |
| |
| if (ufstw_create_sysfs(ufsf, tw)) |
| INIT_INFO("sysfs init fail. but tw could run normally."); |
| |
| /* Read Flag, Attribute */ |
| ufstw_lu_update(tw); |
| |
| #if defined(CONFIG_UFSTW_IGNORE_GUARANTEE_BIT) |
| if (tw->tw_lifetime_est & MASK_UFSTW_LIFETIME_NOT_GUARANTEE) { |
| WARNING_MSG("warn: lun %d - dTurboWriteBufferLifeTimeEst[31] == 1", lun); |
| WARNING_MSG("Device not guarantee the lifetime of Turbo Write Buffer"); |
| WARNING_MSG("but we will ignore them for PoC"); |
| } |
| #else |
| if (tw->tw_lifetime_est & MASK_UFSTW_LIFETIME_NOT_GUARANTEE) { |
| WARNING_MSG("warn: lun %d - dTurboWriteBufferLifeTimeEst[31] == 1", lun); |
| WARNING_MSG("Device not guarantee the lifetime of Turbo Write Buffer"); |
| WARNING_MSG("So tw_mode change to disable_mode"); |
| goto tw_disable; |
| } |
| #endif |
| if ((tw->tw_lifetime_est & ~MASK_UFSTW_LIFETIME_NOT_GUARANTEE) |
| < UFSTW_MAX_LIFETIME_VALUE) { |
| atomic_set(&tw->tw_mode, TW_MODE_MANUAL); |
| goto out; |
| } else |
| goto tw_disable; |
| |
| tw_disable: |
| ufstw_switch_disable_mode(tw); |
| out: |
| ufstw_print_lu_flag_attr(tw); |
| ufstw_lu_put(tw); |
| } |
| |
| extern int ufsplus_tw_status; |
| void ufstw_init(struct ufsf_feature *ufsf) |
| { |
| unsigned int lun; |
| unsigned int tw_enabled_lun = 0; |
| #ifdef UFS_MTK_TW_AWAYS_ON |
| int tw_lun = 0; |
| struct ufstw_lu *tw; |
| #endif |
| |
| kref_init(&ufsf->tw_kref); |
| |
| #ifdef UFS_MTK_TW_AWAYS_ON |
| /* MTK: TW only enable in LU2, skip scan */ |
| lun = 2; |
| if (ufsf->tw_lup[lun]) { |
| ufstw_lu_init(ufsf, lun); |
| tw_lun = lun; |
| INIT_INFO("UFSTW LU %d working", lun); |
| tw_enabled_lun++; |
| ufsplus_tw_status = 1; |
| } |
| #else |
| seq_scan_lu(lun) { |
| if (!ufsf->tw_lup[lun]) |
| continue; |
| |
| if (!ufsf->sdev_ufs_lu[lun]) { |
| WARNING_MSG("warn: lun %d don't have scsi_device", lun); |
| continue; |
| } |
| |
| ufstw_lu_init(ufsf, lun); |
| |
| ufsf->sdev_ufs_lu[lun]->request_queue->turbo_write_dev = true; |
| |
| INIT_INFO("UFSTW LU %d working", lun); |
| tw_enabled_lun++; |
| if(tw_enabled_lun) |
| ufsplus_tw_status = 1; |
| } |
| #endif |
| |
| if (tw_enabled_lun == 0) { |
| ERR_MSG("ERROR: tw_enabled_lun == 0. So TW disabled."); |
| goto out_free_mem; |
| } |
| |
| if (tw_enabled_lun > ufsf->tw_dev_info.tw_number_lu) { |
| ERR_MSG("ERROR: dev_info(bDeviceMaxTurboWriteLUs) mismatch. So TW disabled."); |
| goto out; |
| } |
| |
| #ifdef UFS_MTK_TW_AWAYS_ON |
| /* MTK: Disable TW if run out lifetime */ |
| tw = ufsf->tw_lup[tw_lun]; |
| if (atomic_read(&tw->tw_mode) == TW_MODE_DISABLED) |
| goto out; |
| #endif |
| /* |
| * Initialize Device Level... |
| */ |
| ufstw_disable_ee(ufsf); |
| ufstw_init_dev_jobs(ufsf); |
| atomic64_set(&ufsf->tw_debug_ee_count, 0); |
| ufsf->tw_debug = false; |
| atomic_set(&ufsf->tw_state, TW_PRESENT); |
| |
| #ifdef UFS_MTK_TW_AWAYS_ON |
| /* MTK: Enable TW and H8 flush in Manual mode */ |
| if ((atomic_read(&tw->tw_mode) == TW_MODE_MANUAL) && (tw_lun != 0)) { |
| if (ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, &tw->tw_enable)) |
| goto out; |
| if (ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) |
| goto out; |
| } |
| #endif |
| /* huangjianan@TECH.Storage.UFS, 2019/12/09, Add for UFS+ RUS */ |
| create_wbfn_enable(); |
| return; |
| out_free_mem: |
| seq_scan_lu(lun) { |
| kfree(ufsf->tw_lup[lun]); |
| ufsf->tw_lup[lun] = NULL; |
| } |
| out: |
| /* MTK: not free because we still need querry */ |
| ufsf->tw_dev_info.tw_device = false; |
| atomic_set(&ufsf->tw_state, TW_NOT_SUPPORTED); |
| } |
| |
| static inline int ufstw_probe_lun_done(struct ufsf_feature *ufsf) |
| { |
| return (ufsf->num_lu == ufsf->slave_conf_cnt); |
| } |
| |
| void ufstw_init_work_fn(struct work_struct *work) |
| { |
| struct ufsf_feature *ufsf; |
| #ifndef UFS_MTK_TW_AWAYS_ON |
| int ret; |
| #endif |
| |
| ufsf = container_of(work, struct ufsf_feature, tw_init_work); |
| |
| #ifndef UFS_MTK_TW_AWAYS_ON |
| /* MTK: TW only enable in LU2, skip wait */ |
| init_waitqueue_head(&ufsf->tw_wait); |
| |
| ret = wait_event_timeout(ufsf->tw_wait, |
| ufstw_probe_lun_done(ufsf), |
| msecs_to_jiffies(10000)); |
| if (ret == 0) { |
| ERR_MSG("Probing LU is not fully completed."); |
| return; |
| } |
| #endif |
| |
| INIT_INFO("TW_INIT_START"); |
| |
| ufstw_init(ufsf); |
| } |
| |
| void ufstw_suspend(struct ufsf_feature *ufsf) |
| { |
| struct ufstw_lu *tw; |
| int lun; |
| #if 0 |
| int ret; |
| |
| /* |
| * MTK: No ned flush work, else deadlock may happen. |
| * flush_work ->ufstw_reset_work_fn -> ufstw_reset -> ufstw_set_lu_flag -> |
| * pm_runtime_put_sync -> ufstw_suspend -> flush_work |
| * Beside, reset work only set tw flag, it can do later after suspend. |
| */ |
| ret = flush_work(&ufsf->tw_reset_work); |
| TW_DEBUG(ufsf, "flush_work(tw_reset_work) = %d", ret); |
| #endif |
| |
| seq_scan_lu(lun) { |
| tw = ufsf->tw_lup[lun]; |
| if (!tw) |
| continue; |
| |
| ufstw_lu_get(tw); |
| ufstw_cancel_lu_jobs(tw); |
| ufstw_lu_put(tw); |
| } |
| } |
| |
| void ufstw_resume(struct ufsf_feature *ufsf) |
| { |
| struct ufstw_lu *tw; |
| int lun; |
| |
| seq_scan_lu(lun) { |
| tw = ufsf->tw_lup[lun]; |
| if (!tw) |
| continue; |
| |
| ufstw_lu_get(tw); |
| TW_DEBUG(ufsf, "ufstw_lu %d resume", lun); |
| if (tw->next_q) { |
| TW_DEBUG(ufsf, |
| "ufstw_lu %d flush_worker reschedule...", lun); |
| if (schedule_delayed_work(&tw->tw_flush_work, |
| (tw->next_q - jiffies))) |
| pm_runtime_get_noresume(ufsf->hba->dev); |
| } |
| ufstw_lu_put(tw); |
| } |
| } |
| |
| void ufstw_release(struct kref *kref) |
| { |
| struct ufsf_feature *ufsf; |
| struct ufstw_lu *tw; |
| int lun; |
| int ret; |
| |
| dump_stack(); |
| ufsf = container_of(kref, struct ufsf_feature, tw_kref); |
| RELEASE_INFO("start release"); |
| |
| RELEASE_INFO("tw_state : %d -> %d", atomic_read(&ufsf->tw_state), |
| TW_FAILED); |
| atomic_set(&ufsf->tw_state, TW_FAILED); |
| |
| RELEASE_INFO("kref count %d", |
| atomic_read(&ufsf->tw_kref.refcount.refs)); |
| |
| ret = cancel_work_sync(&ufsf->tw_reset_work); |
| RELEASE_INFO("cancel_work_sync(tw_reset_work) = %d", ret); |
| |
| /* huangjianan@TECH.Storage.UFS, 2019/12/09, Add for UFS+ RUS */ |
| remove_wbfn_enable(); |
| |
| seq_scan_lu(lun) { |
| tw = ufsf->tw_lup[lun]; |
| |
| RELEASE_INFO("ufstw_lu%d %p", lun, tw); |
| |
| ufsf->tw_lup[lun] = NULL; |
| |
| if (!tw) |
| continue; |
| |
| ufstw_cancel_lu_jobs(tw); |
| tw->next_q = 0; |
| |
| ret = kobject_uevent(&tw->kobj, KOBJ_REMOVE); |
| RELEASE_INFO("kobject error %d", ret); |
| |
| kobject_del(&tw->kobj); |
| |
| kfree(tw); |
| } |
| |
| RELEASE_INFO("end release"); |
| } |
| |
| static void ufstw_reset(struct ufsf_feature *ufsf) |
| { |
| struct ufstw_lu *tw; |
| int lun; |
| int ret; |
| |
| if (atomic_read(&ufsf->tw_state) == TW_FAILED) { |
| ERR_MSG("tw_state == TW_FAILED(%d)", |
| atomic_read(&ufsf->tw_state)); |
| return; |
| } |
| |
| seq_scan_lu(lun) { |
| tw = ufsf->tw_lup[lun]; |
| TW_DEBUG(ufsf, "reset tw[%d]=%p", lun, tw); |
| if (!tw) |
| continue; |
| |
| INFO_MSG("ufstw_lu%d reset", lun); |
| |
| ufstw_lu_get(tw); |
| ufstw_cancel_lu_jobs(tw); |
| |
| if (atomic_read(&tw->tw_mode) == TW_MODE_MANUAL && |
| tw->tw_enable) { |
| ret = ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, |
| &tw->tw_enable); |
| if (ret) |
| tw->tw_enable = false; |
| } |
| |
| if (tw->tw_flush_enable) { |
| ret = ufstw_set_lu_flag(tw, |
| QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, |
| &tw->tw_flush_enable); |
| if (ret) |
| tw->tw_flush_enable = false; |
| } |
| |
| if (tw->tw_flush_during_hibern_enter) { |
| ret = ufstw_set_lu_flag(tw, |
| QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter); |
| if (ret) |
| tw->tw_flush_during_hibern_enter = false; |
| } |
| |
| if (tw->next_q) { |
| TW_DEBUG(ufsf, |
| "ufstw_lu %d flush_worker reschedule...", lun); |
| if (schedule_delayed_work(&tw->tw_flush_work, |
| (tw->next_q - jiffies))) |
| pm_runtime_get_noresume(ufsf->hba->dev); |
| } |
| ufstw_lu_put(tw); |
| } |
| |
| if (ufsf->tw_ee_mode) |
| ufstw_auto_ee(ufsf); |
| |
| atomic_set(&ufsf->tw_state, TW_PRESENT); |
| INFO_MSG("reset complete.. tw_state %d", atomic_read(&ufsf->tw_state)); |
| } |
| |
| static inline int ufstw_wait_kref_init_value(struct ufsf_feature *ufsf) |
| { |
| return (atomic_read(&ufsf->tw_kref.refcount.refs) == 1); |
| } |
| |
| void ufstw_reset_work_fn(struct work_struct *work) |
| { |
| struct ufsf_feature *ufsf; |
| int ret; |
| |
| ufsf = container_of(work, struct ufsf_feature, tw_reset_work); |
| TW_DEBUG(ufsf, "reset tw_kref.refcount=%d", |
| atomic_read(&ufsf->tw_kref.refcount.refs)); |
| |
| init_waitqueue_head(&ufsf->tw_wait); |
| |
| ret = wait_event_timeout(ufsf->tw_wait, |
| ufstw_wait_kref_init_value(ufsf), |
| msecs_to_jiffies(15000)); |
| if (ret == 0) { |
| ERR_MSG("UFSTW kref is not init_value(=1). kref count = %d ret = %d. So, TW_RESET_FAIL", |
| atomic_read(&ufsf->tw_kref.refcount.refs), ret); |
| return; |
| } |
| |
| INIT_INFO("TW_RESET_START"); |
| |
| ufstw_reset(ufsf); |
| } |
| |
| /* protected by mutex mode_lock */ |
| static void __active_turbo_write(struct ufstw_lu *tw, int do_work) |
| { |
| if (atomic_read(&tw->tw_mode) != TW_MODE_FS) |
| return; |
| |
| blk_add_trace_msg(tw->ufsf->sdev_ufs_lu[tw->lun]->request_queue, |
| "%s:%d do_work %d active_cnt %d", |
| __func__, __LINE__, do_work, |
| atomic_read(&tw->active_cnt)); |
| |
| if (do_work == TW_FLAG_ENABLE_SET && !tw->tw_enable) |
| ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, &tw->tw_enable); |
| else if (do_work == TW_FLAG_ENABLE_CLEAR && tw->tw_enable) |
| ufstw_clear_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, &tw->tw_enable); |
| } |
| |
| static void ufstw_active_turbo_write(struct request_queue *q, bool on) |
| { |
| struct scsi_device *sdev = q->queuedata; |
| struct Scsi_Host *shost; |
| struct ufs_hba *hba; |
| struct ufstw_lu *tw; |
| int do_work = TW_FLAG_ENABLE_NONE; |
| u64 lun; |
| |
| lun = sdev->lun; |
| if (lun >= UFS_UPIU_MAX_GENERAL_LUN) |
| return; |
| |
| shost = sdev->host; |
| hba = shost_priv(shost); |
| tw = hba->ufsf.tw_lup[lun]; |
| if (!tw) |
| return; |
| |
| ufstw_lu_get(tw); |
| if (on) { |
| if (atomic_inc_return(&tw->active_cnt) == 1) |
| do_work = TW_FLAG_ENABLE_SET; |
| } else { |
| if (atomic_dec_return(&tw->active_cnt) == 0) |
| do_work = TW_FLAG_ENABLE_CLEAR; |
| } |
| |
| blk_add_trace_msg(q, "%s:%d on %d active cnt %d do_work %d state %d mode %d", |
| __func__, __LINE__, on, atomic_read(&tw->active_cnt), |
| do_work, atomic_read(&tw->ufsf->tw_state), |
| atomic_read(&tw->tw_mode)); |
| |
| if (!do_work) |
| goto out; |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| WARNING_MSG("tw_state %d.. cannot enable turbo_write..", |
| atomic_read(&tw->ufsf->tw_state)); |
| goto out; |
| } |
| |
| if (atomic_read(&tw->tw_mode) != TW_MODE_FS) |
| goto out; |
| |
| mutex_lock(&tw->mode_lock); |
| __active_turbo_write(tw, do_work); |
| mutex_unlock(&tw->mode_lock); |
| out: |
| ufstw_lu_put(tw); |
| } |
| |
| void bdev_set_turbo_write(struct block_device *bdev) |
| { |
| struct request_queue *q = bdev->bd_queue; |
| |
| blk_add_trace_msg(q, "%s:%d turbo_write_dev %d\n", |
| __func__, __LINE__, q->turbo_write_dev); |
| |
| if (q->turbo_write_dev) |
| ufstw_active_turbo_write(bdev->bd_queue, true); |
| } |
| |
| void bdev_clear_turbo_write(struct block_device *bdev) |
| { |
| struct request_queue *q = bdev->bd_queue; |
| |
| blk_add_trace_msg(q, "%s:%d turbo_write_dev %d\n", |
| __func__, __LINE__, q->turbo_write_dev); |
| |
| if (q->turbo_write_dev) |
| ufstw_active_turbo_write(bdev->bd_queue, false); |
| } |
| |
| /* sysfs function */ |
| static ssize_t ufstw_sysfs_show_ee_mode(struct ufstw_lu *tw, char *buf) |
| { |
| SYSFS_INFO("TW_ee_mode %d", tw->ufsf->tw_ee_mode); |
| |
| return snprintf(buf, PAGE_SIZE, "%d", tw->ufsf->tw_ee_mode); |
| } |
| |
| static ssize_t ufstw_sysfs_store_ee_mode(struct ufstw_lu *tw, |
| const char *buf, size_t count) |
| { |
| unsigned long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| SYSFS_INFO("ee_mode cannot change, because current state is not TW_PRESENT (%d)..", |
| atomic_read(&tw->ufsf->tw_state)); |
| return -EINVAL; |
| } |
| |
| if (val >= TW_EE_MODE_NUM) { |
| SYSFS_INFO("wrong input.. your input %lu", val); |
| return -EINVAL; |
| } |
| |
| if (val) |
| ufstw_auto_ee(tw->ufsf); |
| else |
| ufstw_disable_ee(tw->ufsf); |
| |
| SYSFS_INFO("TW_ee_mode %d", tw->ufsf->tw_ee_mode); |
| |
| return count; |
| } |
| |
| static ssize_t ufstw_sysfs_show_flush_during_hibern_enter(struct ufstw_lu *tw, |
| char *buf) |
| { |
| int ret; |
| |
| mutex_lock(&tw->flush_lock); |
| if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) { |
| mutex_unlock(&tw->flush_lock); |
| return -EINVAL; |
| } |
| |
| SYSFS_INFO("TW_flush_during_hibern_enter %d", |
| tw->tw_flush_during_hibern_enter); |
| ret = snprintf(buf, PAGE_SIZE, "%d", tw->tw_flush_during_hibern_enter); |
| |
| mutex_unlock(&tw->flush_lock); |
| return ret; |
| } |
| |
| static ssize_t ufstw_sysfs_store_flush_during_hibern_enter(struct ufstw_lu *tw, |
| const char *buf, |
| size_t count) |
| { |
| unsigned long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| SYSFS_INFO("tw_mode cannot change, because current state is not TW_PRESENT (%d)..", |
| atomic_read(&tw->ufsf->tw_state)); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&tw->flush_lock); |
| if (tw->ufsf->tw_ee_mode == TW_EE_MODE_AUTO) { |
| SYSFS_INFO("flush_during_hibern_enable cannot change on auto ee_mode"); |
| mutex_unlock(&tw->flush_lock); |
| return -EINVAL; |
| } |
| |
| if (val) { |
| if (ufstw_set_lu_flag(tw, |
| QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) { |
| mutex_unlock(&tw->flush_lock); |
| return -EINVAL; |
| } |
| } else { |
| if (ufstw_clear_lu_flag(tw, |
| QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, |
| &tw->tw_flush_during_hibern_enter)) { |
| mutex_unlock(&tw->flush_lock); |
| return -EINVAL; |
| } |
| } |
| |
| SYSFS_INFO("TW_flush_during_hibern_enter %d", |
| tw->tw_flush_during_hibern_enter); |
| mutex_unlock(&tw->flush_lock); |
| |
| return count; |
| } |
| |
| static ssize_t ufstw_sysfs_show_flush_enable(struct ufstw_lu *tw, char *buf) |
| { |
| int ret; |
| |
| if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, |
| &tw->tw_flush_enable)) |
| return -EINVAL; |
| |
| SYSFS_INFO("TW_flush_enable %d", tw->tw_flush_enable); |
| |
| ret = snprintf(buf, PAGE_SIZE, "%d", tw->tw_flush_enable); |
| |
| return ret; |
| } |
| |
| static ssize_t ufstw_sysfs_store_flush_enable(struct ufstw_lu *tw, |
| const char *buf, size_t count) |
| { |
| unsigned long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| SYSFS_INFO("tw_mode cannot change, because current tw-state is not TW_PRESENT..(state:%d)..", |
| atomic_read(&tw->ufsf->tw_state)); |
| return -EINVAL; |
| } |
| |
| if (tw->ufsf->tw_ee_mode == TW_EE_MODE_AUTO) { |
| SYSFS_INFO("flush_enable cannot change on auto ee_mode"); |
| return -EINVAL; |
| } |
| |
| if (val) { |
| if (ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, |
| &tw->tw_flush_enable)) |
| return -EINVAL; |
| } else { |
| if (ufstw_clear_lu_flag(tw, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, |
| &tw->tw_flush_enable)) |
| return -EINVAL; |
| } |
| |
| SYSFS_INFO("TW_flush_enable %d", tw->tw_flush_enable); |
| |
| return count; |
| } |
| |
| static ssize_t ufstw_sysfs_show_debug(struct ufstw_lu *tw, char *buf) |
| { |
| SYSFS_INFO("debug %d", tw->ufsf->tw_debug); |
| |
| return snprintf(buf, PAGE_SIZE, "%d", tw->ufsf->tw_debug); |
| } |
| |
| static ssize_t ufstw_sysfs_store_debug(struct ufstw_lu *tw, const char *buf, |
| size_t count) |
| { |
| unsigned long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (val) |
| tw->ufsf->tw_debug = true; |
| else |
| tw->ufsf->tw_debug = false; |
| |
| SYSFS_INFO("debug %d", tw->ufsf->tw_debug); |
| |
| return count; |
| } |
| |
| static ssize_t ufstw_sysfs_show_flush_th_min(struct ufstw_lu *tw, char *buf) |
| { |
| SYSFS_INFO("flush_th_min%d", tw->flush_th_min); |
| |
| return snprintf(buf, PAGE_SIZE, "%d", tw->flush_th_min); |
| } |
| |
| static ssize_t ufstw_sysfs_store_flush_th_min(struct ufstw_lu *tw, |
| const char *buf, size_t count) |
| { |
| unsigned long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (val < 0 || val > 10) { |
| SYSFS_INFO("input value is wrong.. your input %lu", val); |
| return -EINVAL; |
| } |
| |
| if (tw->flush_th_max <= val) { |
| SYSFS_INFO("input value could not be greater than flush_th_max.."); |
| SYSFS_INFO("your input %lu, flush_th_max %u", |
| val, tw->flush_th_max); |
| return -EINVAL; |
| } |
| |
| tw->flush_th_min = val; |
| SYSFS_INFO("flush_th_min %u", tw->flush_th_min); |
| |
| return count; |
| } |
| |
| static ssize_t ufstw_sysfs_show_flush_th_max(struct ufstw_lu *tw, char *buf) |
| { |
| SYSFS_INFO("flush_th_max %d", tw->flush_th_max); |
| |
| return snprintf(buf, PAGE_SIZE, "%d", tw->flush_th_max); |
| } |
| |
| static ssize_t ufstw_sysfs_store_flush_th_max(struct ufstw_lu *tw, |
| const char *buf, size_t count) |
| { |
| unsigned long val = 0; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (val < 0 || val > 10) { |
| SYSFS_INFO("input value is wrong.. your input %lu", val); |
| return -EINVAL; |
| } |
| |
| if (tw->flush_th_min >= val) { |
| SYSFS_INFO("input value could not be less than flush_th_min.."); |
| SYSFS_INFO("your input %lu, flush_th_min %u", |
| val, tw->flush_th_min); |
| return -EINVAL; |
| } |
| |
| tw->flush_th_max = val; |
| SYSFS_INFO("flush_th_max %u", tw->flush_th_max); |
| |
| return count; |
| } |
| |
| static ssize_t ufstw_sysfs_show_version(struct ufstw_lu *tw, char *buf) |
| { |
| SYSFS_INFO("TW version %.4X D/D version %.4X", |
| tw->ufsf->tw_dev_info.tw_ver, UFSTW_DD_VER); |
| |
| return snprintf(buf, PAGE_SIZE, "TW version %.4X DD version %.4X", |
| tw->ufsf->tw_dev_info.tw_ver, UFSTW_DD_VER); |
| } |
| |
| static ssize_t ufstw_sysfs_show_debug_active_cnt(struct ufstw_lu *tw, char *buf) |
| { |
| SYSFS_INFO("debug active cnt %d", |
| atomic_read(&tw->active_cnt)); |
| |
| return snprintf(buf, PAGE_SIZE, "active_cnt %d", |
| atomic_read(&tw->active_cnt)); |
| } |
| |
| /* SYSFS DEFINE */ |
| #define define_sysfs_ro(_name) __ATTR(_name, 0444,\ |
| ufstw_sysfs_show_##_name, NULL), |
| #define define_sysfs_rw(_name) __ATTR(_name, 0644,\ |
| ufstw_sysfs_show_##_name, \ |
| ufstw_sysfs_store_##_name), |
| |
| #define define_sysfs_attr_r_function(_name, _IDN) \ |
| static ssize_t ufstw_sysfs_show_##_name(struct ufstw_lu *tw, char *buf) \ |
| { \ |
| if (ufstw_read_lu_attr(tw, _IDN, &tw->tw_##_name))\ |
| return -EINVAL;\ |
| SYSFS_INFO("TW_"#_name" : %u (0x%X)", tw->tw_##_name, tw->tw_##_name); \ |
| return snprintf(buf, PAGE_SIZE, "%u", tw->tw_##_name); \ |
| } |
| |
| /* SYSFS FUNCTION */ |
| define_sysfs_attr_r_function(flush_status, QUERY_ATTR_IDN_TW_FLUSH_STATUS) |
| define_sysfs_attr_r_function(available_buffer_size, QUERY_ATTR_IDN_TW_BUF_SIZE) |
| define_sysfs_attr_r_function(current_tw_buffer_size, QUERY_ATTR_CUR_TW_BUF_SIZE) |
| define_sysfs_attr_r_function(lifetime_est, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST) |
| |
| static ssize_t ufstw_sysfs_show_tw_enable(struct ufstw_lu *tw, char *buf) |
| { |
| if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, &tw->tw_enable)) |
| return -EINVAL; |
| |
| SYSFS_INFO("TW_enable: %u (0x%X)", tw->tw_enable, tw->tw_enable); |
| return snprintf(buf, PAGE_SIZE, "%u", tw->tw_enable); |
| } |
| |
| static ssize_t ufstw_sysfs_store_tw_enable(struct ufstw_lu *tw, const char *buf, |
| size_t count) |
| { |
| unsigned long val = 0; |
| ssize_t ret = count; |
| |
| if (kstrtoul(buf, 0, &val)) |
| return -EINVAL; |
| |
| if (val > 2) { |
| SYSFS_INFO("wrong mode number.. your input %lu", val); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&tw->mode_lock); |
| if (atomic_read(&tw->tw_mode) == TW_MODE_DISABLED) { |
| SYSFS_INFO("all turbo write life time is exhausted.."); |
| SYSFS_INFO("you could not change this value.."); |
| goto out; |
| } |
| |
| if (atomic_read(&tw->tw_mode) != TW_MODE_MANUAL) { |
| SYSFS_INFO("cannot set tw_enable.. current %s (%d) mode..", |
| atomic_read(&tw->tw_mode) == TW_MODE_FS ? |
| "TW_MODE_FS" : "UNKNOWN", |
| atomic_read(&tw->tw_mode)); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (val) { |
| if (ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, |
| &tw->tw_enable)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| } else { |
| if (ufstw_clear_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, |
| &tw->tw_enable)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| out: |
| mutex_unlock(&tw->mode_lock); |
| SYSFS_INFO("TW_enable : %u (0x%X)", tw->tw_enable, tw->tw_enable); |
| return ret; |
| } |
| |
| static ssize_t ufstw_sysfs_show_tw_mode(struct ufstw_lu *tw, char *buf) |
| { |
| int tw_mode = atomic_read(&tw->tw_mode); |
| |
| SYSFS_INFO("TW_mode %s %d", |
| tw_mode == TW_MODE_MANUAL ? "manual" : |
| tw_mode == TW_MODE_FS ? "fs" : "unknown", tw_mode); |
| return snprintf(buf, PAGE_SIZE, "%d", tw_mode); |
| } |
| |
| static ssize_t ufstw_sysfs_store_tw_mode(struct ufstw_lu *tw, const char *buf, |
| size_t count) |
| { |
| int tw_mode = 0; |
| |
| if (kstrtouint(buf, 0, &tw_mode)) |
| return -EINVAL; |
| |
| if (atomic_read(&tw->ufsf->tw_state) != TW_PRESENT) { |
| SYSFS_INFO("tw_mode cannot change, because current state is not TW_PRESENT (%d)..", |
| atomic_read(&tw->ufsf->tw_state)); |
| return -EINVAL; |
| } |
| |
| if (tw_mode >= TW_MODE_NUM || |
| tw_mode == TW_MODE_DISABLED) { |
| SYSFS_INFO("wrong mode number.. your input %d", tw_mode); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&tw->mode_lock); |
| if (atomic_read(&tw->tw_mode) == TW_MODE_DISABLED) { |
| SYSFS_INFO("all turbo write life time is exhausted.."); |
| SYSFS_INFO("you could not change this value.."); |
| count = -EINVAL; |
| goto out; |
| } |
| |
| if (tw_mode == atomic_read(&tw->tw_mode)) |
| goto out; |
| |
| count = (ssize_t) ufstw_switch_mode(tw, tw_mode); |
| out: |
| mutex_unlock(&tw->mode_lock); |
| SYSFS_INFO("TW_mode: %d", atomic_read(&tw->tw_mode)); |
| return count; |
| } |
| |
| static struct ufstw_sysfs_entry ufstw_sysfs_entries[] = { |
| /* tw mode select */ |
| define_sysfs_rw(tw_mode) |
| |
| /* Flag */ |
| define_sysfs_rw(tw_enable) |
| define_sysfs_rw(flush_enable) |
| define_sysfs_rw(flush_during_hibern_enter) |
| |
| /* Attribute */ |
| define_sysfs_rw(ee_mode) |
| define_sysfs_ro(flush_status) |
| define_sysfs_ro(available_buffer_size) |
| define_sysfs_ro(current_tw_buffer_size) |
| define_sysfs_ro(lifetime_est) |
| |
| /* debug */ |
| define_sysfs_rw(debug) |
| define_sysfs_ro(debug_active_cnt) |
| |
| /* support */ |
| define_sysfs_rw(flush_th_max) |
| define_sysfs_rw(flush_th_min) |
| |
| /* device level */ |
| define_sysfs_ro(version) |
| __ATTR_NULL |
| }; |
| |
| static ssize_t ufstw_attr_show(struct kobject *kobj, struct attribute *attr, |
| char *page) |
| { |
| struct ufstw_sysfs_entry *entry; |
| struct ufstw_lu *tw; |
| ssize_t error; |
| |
| entry = container_of(attr, struct ufstw_sysfs_entry, attr); |
| tw = container_of(kobj, struct ufstw_lu, kobj); |
| if (!entry->show) |
| return -EIO; |
| |
| ufstw_lu_get(tw); |
| mutex_lock(&tw->sysfs_lock); |
| error = entry->show(tw, page); |
| mutex_unlock(&tw->sysfs_lock); |
| ufstw_lu_put(tw); |
| return error; |
| } |
| |
| static ssize_t ufstw_attr_store(struct kobject *kobj, struct attribute *attr, |
| const char *page, size_t length) |
| { |
| struct ufstw_sysfs_entry *entry; |
| struct ufstw_lu *tw; |
| ssize_t error; |
| |
| entry = container_of(attr, struct ufstw_sysfs_entry, attr); |
| tw = container_of(kobj, struct ufstw_lu, kobj); |
| |
| if (!entry->store) |
| return -EIO; |
| |
| ufstw_lu_get(tw); |
| mutex_lock(&tw->sysfs_lock); |
| error = entry->store(tw, page, length); |
| mutex_unlock(&tw->sysfs_lock); |
| ufstw_lu_put(tw); |
| return error; |
| } |
| |
| static const struct sysfs_ops ufstw_sysfs_ops = { |
| .show = ufstw_attr_show, |
| .store = ufstw_attr_store, |
| }; |
| |
| static struct kobj_type ufstw_ktype = { |
| .sysfs_ops = &ufstw_sysfs_ops, |
| .release = NULL, |
| }; |
| |
| static int ufstw_create_sysfs(struct ufsf_feature *ufsf, struct ufstw_lu *tw) |
| { |
| struct device *dev = ufsf->hba->dev; |
| struct ufstw_sysfs_entry *entry; |
| int err; |
| |
| ufstw_lu_get(tw); |
| tw->sysfs_entries = ufstw_sysfs_entries; |
| |
| kobject_init(&tw->kobj, &ufstw_ktype); |
| mutex_init(&tw->sysfs_lock); |
| |
| INIT_INFO("ufstw creates sysfs ufstw_lu(%d) %p dev->kobj %p", |
| tw->lun, &tw->kobj, &dev->kobj); |
| |
| err = kobject_add(&tw->kobj, kobject_get(&dev->kobj), |
| "ufstw_lu%d", tw->lun); |
| if (!err) { |
| for (entry = tw->sysfs_entries; entry->attr.name != NULL; |
| entry++) { |
| INIT_INFO("ufstw_lu%d sysfs attr creates: %s", |
| tw->lun, entry->attr.name); |
| if (sysfs_create_file(&tw->kobj, &entry->attr)) |
| break; |
| } |
| INIT_INFO("ufstw_lu%d sysfs adds uevent", tw->lun); |
| kobject_uevent(&tw->kobj, KOBJ_ADD); |
| } |
| ufstw_lu_put(tw); |
| return err; |
| } |
| |
| /* huangjianan@TECH.Storage.UFS, 2019/12/09, Add for UFS+ RUS */ |
| static inline void wbfn_enable_ctrl(struct ufstw_lu *tw, long val) |
| { |
| |
| mutex_lock(&tw->mode_lock); |
| |
| if (atomic_read(&tw->tw_mode) == TW_MODE_MANUAL) { |
| switch (val) { |
| case 0: |
| ufstw_clear_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, |
| &tw->tw_enable); |
| break; |
| case 1: |
| ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, |
| &tw->tw_enable); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| mutex_unlock(&tw->mode_lock); |
| return; |
| } |
| |
| static ssize_t wbfn_enable_write(struct file *filp, const char *ubuf, |
| size_t cnt, loff_t *data) |
| { |
| char buf[64] = {0}; |
| long val = 64; |
| int lun; |
| |
| if (!ufsf_para.ufsf) |
| return -EFAULT; |
| if (cnt >= sizeof(buf)) |
| return -EINVAL; |
| if (copy_from_user(&buf, ubuf, cnt)) |
| return -EFAULT; |
| |
| if (buf[0] == '0') |
| val = 0; |
| else if (buf[0] == '1') |
| val = 1; |
| else |
| val = 64; |
| |
| seq_scan_lu(lun) { |
| if (ufsf_para.ufsf->tw_lup[lun]) |
| wbfn_enable_ctrl(ufsf_para.ufsf->tw_lup[lun], val); |
| } |
| |
| return cnt; |
| } |
| |
| static const struct file_operations wbfn_enable_fops = { |
| .write = wbfn_enable_write, |
| }; |
| |
| static int create_wbfn_enable(void) |
| { |
| struct proc_dir_entry *d_entry; |
| |
| if (!ufsf_para.ctrl_dir) |
| return -EFAULT; |
| |
| d_entry = proc_create("wbfn_enable", S_IWUGO, ufsf_para.ctrl_dir, |
| &wbfn_enable_fops); |
| if(!d_entry) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static void remove_wbfn_enable(void) |
| { |
| if (ufsf_para.ctrl_dir) |
| remove_proc_entry("wbfn_enable", ufsf_para.ctrl_dir); |
| |
| return; |
| } |