mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-06 03:45:24 -04:00
scsi/ufs: porting Samsung TW + HPB feature
Remark: proting samsung hpb + tw driver to msm-5.4 br-4350 branch Change-Id: Ie964195e3b9bc1ef7d5ba585e839239b4aa1c761 Signed-off-by: fuxm1 <fuxm1@motorola.com> Reviewed-on: https://gerrit.mot.com/1885583 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Huosheng Liao <liaohs@motorola.com> Submit-Approved: Jira Key
This commit is contained in:
parent
b2c751350a
commit
d4bbff0602
15 changed files with 10563 additions and 11 deletions
|
|
@ -196,3 +196,61 @@ config SCSI_UFS_CRYPTO_QTI
|
|||
Enable Vendor Crypto Engine Support in UFS
|
||||
Enabling this allows kernel to use UFS crypto operations defined
|
||||
and implemented by QTI.
|
||||
|
||||
config UFSFEATURE
|
||||
bool "UFS feature activate"
|
||||
depends on SCSI_UFSHCD
|
||||
---help---
|
||||
UFS feature activate such as hpb, tw and etc.
|
||||
|
||||
config UFSHPB
|
||||
bool "UFS Host Performance Booster support"
|
||||
depends on SCSI_UFSHCD && UFSFEATURE
|
||||
---help---
|
||||
UFS HPB Feature Enable
|
||||
|
||||
config HPB_DEBUG
|
||||
bool "HPB Debug Log Enable"
|
||||
depends on SCSI_UFSHCD && UFSFEATURE && UFSHPB
|
||||
default n
|
||||
---help---
|
||||
UFS HPB Debug Enable (dmesg log[=if set debug sysfs] & BLK_TRACE msg)
|
||||
|
||||
config HPB_ERR_INJECTION
|
||||
bool "HPB ERROR INJECTION"
|
||||
depends on SCSI_UFSHCD && UFSFEATURE && UFSHPB
|
||||
---help---
|
||||
error injection for checking HPB entry integrity
|
||||
|
||||
config UFSTW
|
||||
bool "UFS Turbo Write support"
|
||||
depends on SCSI_UFSHCD && UFSFEATURE
|
||||
---help---
|
||||
UFS TW Feature Enable
|
||||
|
||||
config UFSTW_IGNORE_GUARANTEE_BIT
|
||||
bool "Ignore UFS Turbo Write Life Time Guarantee bit for POC"
|
||||
default n
|
||||
depends on SCSI_UFSHCD && UFSFEATURE && UFSTW
|
||||
---help---
|
||||
ignore the guarantee bit[31] of dTurboWriteBufferLifeTimeEst for PoC
|
||||
|
||||
config UFSTW_BOOT_ENABLED
|
||||
bool "Turbo Write enabled at boot time"
|
||||
default y
|
||||
depends on SCSI_UFSHCD && UFSFEATURE && UFSTW
|
||||
---help---
|
||||
fTurboWriteEn and fTurboWriteBufferFlushDuringHibnerEnter flags
|
||||
are enabled at boot time.
|
||||
|
||||
config SCSI_SKHPB
|
||||
bool "Activate HPB Host-aware Performance Booster"
|
||||
depends on (SCSI_UFSHCD || SCSI_UFSHCD_QTI)
|
||||
help
|
||||
Activate or deactive SKHPB driver
|
||||
|
||||
config MICRON_HPB
|
||||
bool "Activate micron HPB Host-aware Performance Booster"
|
||||
depends on SCSI_UFSHCD && UFSFEATURE && UFSHPB
|
||||
help
|
||||
Activate or deactive micron HPB driver
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@ obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-d
|
|||
obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o
|
||||
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
|
||||
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o
|
||||
obj-$(CONFIG_UFSFEATURE) += ufsfeature.o
|
||||
obj-$(CONFIG_UFSHPB) += ufshpb.o
|
||||
obj-$(CONFIG_UFSTW) += ufstw.o
|
||||
ufshcd-core-y += ufshcd.o ufs-sysfs.o
|
||||
ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
|
||||
obj-$(CONFIG_SCSI_SKHPB) += ufshpb_skh.o
|
||||
obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
|
||||
obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
|
||||
obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o
|
||||
|
|
|
|||
|
|
@ -2257,6 +2257,14 @@ ufs_qcom_query_ioctl(struct ufs_hba *hba, u8 lun, void __user *buffer)
|
|||
goto out_release_mem;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (ufsf_check_query(ioctl_data->opcode)) {
|
||||
err = ufsf_query_ioctl(hba, lun, buffer, ioctl_data,
|
||||
UFSFEATURE_SELECTOR);
|
||||
goto out_release_mem;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* verify legal parameters & send query */
|
||||
switch (ioctl_data->opcode) {
|
||||
case UPIU_QUERY_OPCODE_READ_DESC:
|
||||
|
|
|
|||
|
|
@ -152,6 +152,14 @@ enum flag_idn {
|
|||
QUERY_FLAG_IDN_WB_EN = 0x0E,
|
||||
QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN = 0x0F,
|
||||
QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8 = 0x10,
|
||||
#if defined(CONFIG_UFSHPB) || defined(CONFIG_SCSI_SKHPB)
|
||||
QUERY_FLAG_IDN_HPB_RESET = 0x11,
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
QUERY_FLAG_IDN_TW_EN = 0x0E,
|
||||
QUERY_FLAG_IDN_TW_BUF_FLUSH_EN = 0x0F,
|
||||
QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN = 0x10,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Attribute idn for Query requests */
|
||||
|
|
@ -184,8 +192,19 @@ enum attr_idn {
|
|||
QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D,
|
||||
QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E,
|
||||
QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE = 0x1F,
|
||||
#if defined(CONFIG_UFSTW)
|
||||
QUERY_ATTR_IDN_TW_FLUSH_STATUS = 0x1C,
|
||||
QUERY_ATTR_IDN_TW_AVAIL_BUF_SIZE = 0x1D,
|
||||
QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST = 0x1E,
|
||||
QUERY_ATTR_IDN_TW_CURR_BUF_SIZE = 0x1F,
|
||||
#endif
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
QUERY_ATTR_IDN_SUP_VENDOR_OPTIONS = 0xFF,
|
||||
#endif
|
||||
};
|
||||
|
||||
#define QUERY_ATTR_IDN_BOOT_LU_EN_MAX 0x02
|
||||
|
||||
/* Descriptor idn for Query requests */
|
||||
enum desc_idn {
|
||||
QUERY_DESC_IDN_DEVICE = 0x0,
|
||||
|
|
@ -207,11 +226,11 @@ enum desc_header_offset {
|
|||
};
|
||||
|
||||
enum ufs_desc_def_size {
|
||||
QUERY_DESC_DEVICE_DEF_SIZE = 0x59,
|
||||
QUERY_DESC_CONFIGURATION_DEF_SIZE = 0x90,
|
||||
QUERY_DESC_DEVICE_DEF_SIZE = 0x5F,
|
||||
QUERY_DESC_CONFIGURATION_DEF_SIZE = 0xE6,
|
||||
QUERY_DESC_UNIT_DEF_SIZE = 0x2D,
|
||||
QUERY_DESC_INTERCONNECT_DEF_SIZE = 0x06,
|
||||
QUERY_DESC_GEOMETRY_DEF_SIZE = 0x48,
|
||||
QUERY_DESC_GEOMETRY_DEF_SIZE = 0x59,
|
||||
QUERY_DESC_POWER_DEF_SIZE = 0x62,
|
||||
QUERY_DESC_HEALTH_DEF_SIZE = 0x25,
|
||||
};
|
||||
|
|
@ -236,6 +255,15 @@ enum unit_desc_param {
|
|||
UNIT_DESC_PARAM_CTX_CAPABILITIES = 0x20,
|
||||
UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22,
|
||||
UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS = 0x29,
|
||||
#if defined(CONFIG_UFSHPB) || defined(CONFIG_SCSI_SKHPB)
|
||||
UNIT_DESC_HPB_LU_MAX_ACTIVE_REGIONS = 0x23,
|
||||
UNIT_DESC_HPB_LU_PIN_REGION_START_OFFSET = 0x25,
|
||||
UNIT_DESC_HPB_LU_NUM_PIN_REGIONS = 0x27,
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
UNIT_DESC_TW_LU_WRITE_BUFFER_ALLOC_UNIT = 0x29,
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Device descriptor parameters offsets in bytes*/
|
||||
|
|
@ -279,6 +307,19 @@ enum device_desc_param {
|
|||
DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN = 0x53,
|
||||
DEVICE_DESC_PARAM_WB_TYPE = 0x54,
|
||||
DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS = 0x55,
|
||||
#if defined(CONFIG_UFSHPB) || defined(CONFIG_SCSI_SKHPB)
|
||||
DEVICE_DESC_PARAM_HPB_VER = 0x40,
|
||||
DEVICE_DESC_PARAM_HPB_CONTROL = 0x42,
|
||||
#endif
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
DEVICE_DESC_PARAM_EX_FEAT_SUP = 0x4F,
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
DEVICE_DESC_PARAM_TW_VER = 0x4D,
|
||||
DEVICE_DESC_PARAM_TW_RETURN_TO_USER = 0x53,
|
||||
DEVICE_DESC_PARAM_TW_BUF_TYPE = 0x54,
|
||||
DEVICE_DESC_PARAM_TW_SHARED_BUF_ALLOC_UNITS = 0x55,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Interconnect descriptor parameters offsets in bytes*/
|
||||
|
|
@ -328,6 +369,20 @@ enum geometry_desc_param {
|
|||
GEOMETRY_DESC_PARAM_WB_BUFF_CAP_ADJ = 0x54,
|
||||
GEOMETRY_DESC_PARAM_WB_SUP_RED_TYPE = 0x55,
|
||||
GEOMETRY_DESC_PARAM_WB_SUP_WB_TYPE = 0x56,
|
||||
#if defined(CONFIG_UFSHPB) || defined(CONFIG_SCSI_SKHPB)
|
||||
GEOMETRY_DESC_HPB_REGION_SIZE = 0x48,
|
||||
GEOMETRY_DESC_HPB_NUMBER_LU = 0x49,
|
||||
GEOMETRY_DESC_HPB_SUBREGION_SIZE = 0x4A,
|
||||
GEOMETRY_DESC_HPB_DEVICE_MAX_ACTIVE_REGIONS = 0x4B,
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
GEOMETRY_DESC_TW_GROUP_NUM_CAP = 0x4E,
|
||||
GEOMETRY_DESC_TW_MAX_SIZE = 0x4F,
|
||||
GEOMETRY_DESC_TW_NUMBER_LU = 0x53,
|
||||
GEOMETRY_DESC_TW_CAP_ADJ_FAC = 0x54,
|
||||
GEOMETRY_DESC_TW_SUPPORT_USER_REDUCTION_TYPES = 0x55,
|
||||
GEOMETRY_DESC_TW_SUPPORT_BUF_TYPE = 0x56,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Health descriptor parameters offsets in bytes*/
|
||||
|
|
@ -391,6 +446,9 @@ enum power_desc_param_offset {
|
|||
enum {
|
||||
MASK_EE_STATUS = 0xFFFF,
|
||||
MASK_EE_URGENT_BKOPS = (1 << 2),
|
||||
#if defined(CONFIG_UFSTW)
|
||||
MASK_EE_TW = (1 << 5),
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Background operation status */
|
||||
|
|
@ -469,6 +527,9 @@ enum {
|
|||
MASK_RSP_EXCEPTION_EVENT = 0x10000,
|
||||
MASK_TM_SERVICE_RESP = 0xFF,
|
||||
MASK_TM_FUNC = 0xFF,
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
MASK_RSP_UPIU_HPB_UPDATE_ALERT = 0x20000,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Task management service response */
|
||||
|
|
|
|||
703
drivers/scsi/ufs/ufsfeature.c
Executable file
703
drivers/scsi/ufs/ufsfeature.c
Executable file
|
|
@ -0,0 +1,703 @@
|
|||
/*
|
||||
* 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-qcom.h"
|
||||
|
||||
#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.
|
||||
*/
|
||||
static 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;
|
||||
int err;
|
||||
|
||||
BUG_ON(!hba);
|
||||
|
||||
ufshcd_hold(hba, false);
|
||||
mutex_lock(&hba->dev_cmd.lock);
|
||||
|
||||
/*
|
||||
* Init the query response and request parameters
|
||||
*/
|
||||
ufsf_init_query(hba, &request, &response, opcode, idn, index,
|
||||
UFSFEATURE_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;
|
||||
|
||||
BUG_ON(idx > 7);
|
||||
|
||||
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;
|
||||
|
||||
for (retries = 0; retries < UFSF_QUERY_REQ_RETRIES; retries++) {
|
||||
ret = ufshcd_query_attr(hba, opcode, idn, idx,
|
||||
UFSFEATURE_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;
|
||||
|
||||
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_UFSHPB)
|
||||
ufshpb_get_dev_info(ufsf, desc_buf);
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
ufstw_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_UFSHPB)
|
||||
if (ufshpb_get_state(ufsf) == HPB_NEED_INIT)
|
||||
ufshpb_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
|
||||
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_UFSHPB)
|
||||
if (ufshpb_get_state(ufsf) == HPB_NEED_INIT)
|
||||
ufshpb_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 = &hba->ufsf;
|
||||
int ret, lun;
|
||||
u32 status;
|
||||
|
||||
ufsf->hba = hba;
|
||||
|
||||
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 ufs_hba *hba, 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(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;
|
||||
#if 0
|
||||
case QUERY_DESC_IDN_STRING:
|
||||
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 = ufsf_issue_req_dev_ctx(ufsf, 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(hba, opcode, ioctl_data->idn,
|
||||
index, selector, kernel_buf,
|
||||
&length);
|
||||
if (err)
|
||||
goto out_release_mem;
|
||||
#if 0
|
||||
copy_buffer:
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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/%d)",
|
||||
(int)sdev->lun, sdev, sdev->request_queue,
|
||||
ufsf->slave_conf_cnt, ufsf->num_lu);
|
||||
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
if (ufsf->num_lu == ufsf->slave_conf_cnt) {
|
||||
if (ufshpb_get_state(ufsf) == HPB_NEED_INIT) {
|
||||
INFO_MSG("wakeup ufshpb_init_handler");
|
||||
wake_up(&ufsf->hpb_wait);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void ufsf_prep_fn(struct ufsf_feature *ufsf,
|
||||
struct ufshcd_lrb *lrbp)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
if (ufshpb_get_state(ufsf) == HPB_PRESENT &&
|
||||
ufsf->issue_ioctl == false)
|
||||
ufshpb_prep_fn(ufsf, lrbp);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_UFSTW)
|
||||
if (ufstw_get_state(ufsf) == TW_PRESENT)
|
||||
ufstw_prep_fn(ufsf, lrbp);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ufsf_reset_lu(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSTW)
|
||||
INFO_MSG("run reset_lu.. tw_state(%d) -> TW_RESET",
|
||||
ufstw_get_state(ufsf));
|
||||
ufstw_set_state(ufsf, TW_RESET);
|
||||
ufstw_reset(ufsf, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ufsf_reset_host(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
INFO_MSG("run reset_host.. hpb_state(%d) -> HPB_RESET",
|
||||
ufshpb_get_state(ufsf));
|
||||
if (ufshpb_get_state(ufsf) == HPB_PRESENT)
|
||||
ufshpb_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
|
||||
}
|
||||
|
||||
inline void ufsf_init(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
if (ufshpb_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
|
||||
}
|
||||
|
||||
inline void ufsf_reset(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
if (ufshpb_get_state(ufsf) == HPB_RESET) {
|
||||
INFO_MSG("reset start.. hpb_state %d", HPB_RESET);
|
||||
ufshpb_reset(ufsf);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_UFSTW)
|
||||
if (ufstw_get_state(ufsf) == TW_RESET &&
|
||||
!ufsf->hba->pm_op_in_progress) {
|
||||
INFO_MSG("reset start.. tw_state %d",
|
||||
ufstw_get_state(ufsf));
|
||||
ufstw_reset(ufsf, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ufsf_remove(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
if (ufshpb_get_state(ufsf) == HPB_PRESENT)
|
||||
ufshpb_remove(ufsf, HPB_NEED_INIT);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_UFSTW)
|
||||
if (ufstw_get_state(ufsf) == TW_PRESENT)
|
||||
ufstw_remove(ufsf);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ufsf_set_init_state(struct ufsf_feature *ufsf)
|
||||
{
|
||||
ufsf->slave_conf_cnt = 0;
|
||||
ufsf->issue_ioctl = false;
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
ufshpb_set_state(ufsf, HPB_NEED_INIT);
|
||||
INIT_WORK(&ufsf->hpb_init_work, ufshpb_init_handler);
|
||||
init_waitqueue_head(&ufsf->hpb_wait);
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
ufstw_set_state(ufsf, TW_NEED_INIT);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ufsf_suspend(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
/*
|
||||
* if suspend failed, pm could call the suspend function again,
|
||||
* in this case, ufshpb state already had been changed to SUSPEND state.
|
||||
* so, we will not call ufshpb_suspend.
|
||||
*/
|
||||
if (ufshpb_get_state(ufsf) == HPB_PRESENT)
|
||||
ufshpb_suspend(ufsf);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ufsf_resume(struct ufsf_feature *ufsf)
|
||||
{
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
if (ufshpb_get_state(ufsf) == HPB_SUSPEND ||
|
||||
ufshpb_get_state(ufsf) == HPB_PRESENT) {
|
||||
if (ufshpb_get_state(ufsf) == HPB_PRESENT)
|
||||
WARN_MSG("warning.. hpb state PRESENT in resuming");
|
||||
ufshpb_resume(ufsf);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_UFSTW)
|
||||
if (ufstw_get_state(ufsf) == TW_RESET)
|
||||
ufstw_reset(ufsf, true);
|
||||
#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 ufshpb.
|
||||
*/
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp)
|
||||
{
|
||||
if (ufshpb_get_state(ufsf) == HPB_PRESENT)
|
||||
ufshpb_rsp_upiu(ufsf, lrbp);
|
||||
}
|
||||
|
||||
#else
|
||||
inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf,
|
||||
struct ufshcd_lrb *lrbp) {}
|
||||
#endif
|
||||
192
drivers/scsi/ufs/ufsfeature.h
Executable file
192
drivers/scsi/ufs/ufsfeature.h
Executable file
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _UFSFEATURE_H_
|
||||
#define _UFSFEATURE_H_
|
||||
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
|
||||
#include "ufs.h"
|
||||
|
||||
#include "ufshpb.h"
|
||||
#include "ufstw.h"
|
||||
|
||||
/* Version info */
|
||||
#define UFSFEATURE_DD_VER 0x010100
|
||||
#define UFSFEATURE_DD_VER_POST ""
|
||||
|
||||
#define UFSFEATURE_QUERY_OPCODE 0x5500
|
||||
|
||||
/* For read10 debug */
|
||||
#define READ10_DEBUG_LUN 0x7F
|
||||
#define READ10_DEBUG_LBA 0x48504230
|
||||
|
||||
/* Constant value*/
|
||||
#define SECTOR 512
|
||||
#define BLOCK 4096
|
||||
#define SECTORS_PER_BLOCK (BLOCK / SECTOR)
|
||||
#define BITS_PER_DWORD 32
|
||||
#define sects_per_blk_shift 3
|
||||
#define bits_per_dword_shift 5
|
||||
#define bits_per_dword_mask 0x1F
|
||||
#define bits_per_byte_shift 3
|
||||
|
||||
#define IOCTL_DEV_CTX_MAX_SIZE OS_PAGE_SIZE
|
||||
#define OS_PAGE_SIZE 4096
|
||||
#define OS_PAGE_SHIFT 12
|
||||
|
||||
#define UFSF_QUERY_REQ_RETRIES 1
|
||||
|
||||
/* Description */
|
||||
#define UFSF_QUERY_DESC_DEVICE_MAX_SIZE 0x5F
|
||||
#define UFSF_QUERY_DESC_CONFIGURAION_MAX_SIZE 0xE6
|
||||
#define UFSF_QUERY_DESC_UNIT_MAX_SIZE 0x2D
|
||||
#define UFSF_QUERY_DESC_GEOMETRY_MAX_SIZE 0x59
|
||||
|
||||
#define UFSFEATURE_SELECTOR 0x01
|
||||
|
||||
/* query_flag */
|
||||
#define MASK_QUERY_UPIU_FLAG_LOC 0xFF
|
||||
|
||||
/* For read10 debug */
|
||||
#define READ10_DEBUG_LUN 0x7F
|
||||
#define READ10_DEBUG_LBA 0x48504230
|
||||
|
||||
/* BIG -> LI */
|
||||
#define LI_EN_16(x) be16_to_cpu(*(__be16 *)(x))
|
||||
#define LI_EN_32(x) be32_to_cpu(*(__be32 *)(x))
|
||||
#define LI_EN_64(x) be64_to_cpu(*(__be64 *)(x))
|
||||
|
||||
/* LI -> BIG */
|
||||
#define GET_BYTE_0(num) (((num) >> 0) & 0xff)
|
||||
#define GET_BYTE_1(num) (((num) >> 8) & 0xff)
|
||||
#define GET_BYTE_2(num) (((num) >> 16) & 0xff)
|
||||
#define GET_BYTE_3(num) (((num) >> 24) & 0xff)
|
||||
#define GET_BYTE_4(num) (((num) >> 32) & 0xff)
|
||||
#define GET_BYTE_5(num) (((num) >> 40) & 0xff)
|
||||
#define GET_BYTE_6(num) (((num) >> 48) & 0xff)
|
||||
#define GET_BYTE_7(num) (((num) >> 56) & 0xff)
|
||||
|
||||
#define INFO_MSG(msg, args...) pr_info("%s:%d info: " msg "\n", \
|
||||
__func__, __LINE__, ##args)
|
||||
#define ERR_MSG(msg, args...) pr_err("%s:%d err: " msg "\n", \
|
||||
__func__, __LINE__, ##args)
|
||||
#define WARN_MSG(msg, args...) pr_warn("%s:%d warn: " msg "\n", \
|
||||
__func__, __LINE__, ##args)
|
||||
|
||||
#define seq_scan_lu(lun) for (lun = 0; lun < UFS_UPIU_MAX_GENERAL_LUN; lun++)
|
||||
|
||||
#define TMSG(ufsf, lun, msg, args...) \
|
||||
do { if (ufsf->sdev_ufs_lu[lun] && \
|
||||
ufsf->sdev_ufs_lu[lun]->request_queue) \
|
||||
blk_add_trace_msg( \
|
||||
ufsf->sdev_ufs_lu[lun]->request_queue, \
|
||||
msg, ##args); \
|
||||
} while (0) \
|
||||
|
||||
struct ufsf_lu_desc {
|
||||
/* Common info */
|
||||
int lu_enable; /* 03h bLUEnable */
|
||||
int lu_queue_depth; /* 06h lu queue depth info*/
|
||||
int lu_logblk_size; /* 0Ah bLogicalBlockSize. default 0x0C = 4KB */
|
||||
u64 lu_logblk_cnt; /* 0Bh qLogicalBlockCount. */
|
||||
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
u16 lu_max_active_hpb_rgns; /* 23h:24h wLUMaxActiveHPBRegions */
|
||||
u16 lu_hpb_pinned_rgn_startidx; /* 25h:26h wHPBPinnedRegionStartIdx */
|
||||
u16 lu_num_hpb_pinned_rgns; /* 27h:28h wNumHPBPinnedRegions */
|
||||
int lu_hpb_pinned_end_offset;
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
unsigned int tw_lu_buf_size;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ufsf_feature {
|
||||
struct ufs_hba *hba;
|
||||
int num_lu;
|
||||
int slave_conf_cnt;
|
||||
struct scsi_device *sdev_ufs_lu[UFS_UPIU_MAX_GENERAL_LUN];
|
||||
bool issue_ioctl;
|
||||
|
||||
#if defined(CONFIG_UFSHPB)
|
||||
struct ufshpb_dev_info hpb_dev_info;
|
||||
struct ufshpb_lu *hpb_lup[UFS_UPIU_MAX_GENERAL_LUN];
|
||||
struct work_struct hpb_init_work;
|
||||
struct work_struct hpb_eh_work;
|
||||
wait_queue_head_t hpb_wait;
|
||||
atomic_t hpb_state;
|
||||
struct kref hpb_kref;
|
||||
#endif
|
||||
#if defined(CONFIG_UFSTW)
|
||||
struct ufstw_dev_info tw_dev_info;
|
||||
struct ufstw_lu *tw_lup[UFS_UPIU_MAX_GENERAL_LUN];
|
||||
atomic_t tw_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ufs_hba;
|
||||
struct ufshcd_lrb;
|
||||
struct ufs_ioctl_query_data;
|
||||
|
||||
void ufsf_device_check(struct ufs_hba *hba);
|
||||
int ufsf_check_query(__u32 opcode);
|
||||
int ufsf_query_ioctl(struct ufs_hba *hba, int lun, void __user *buffer,
|
||||
struct ufs_ioctl_query_data *ioctl_data,
|
||||
u8 selector);
|
||||
int ufsf_query_flag_retry(struct ufs_hba *hba, enum query_opcode opcode,
|
||||
enum flag_idn idn, u8 idx, bool *flag_res);
|
||||
int ufsf_query_attr_retry(struct ufs_hba *hba, enum query_opcode opcode,
|
||||
enum attr_idn idn, u8 idx, u32 *attr_val);
|
||||
int ufsf_get_scsi_device(struct ufs_hba *hba, struct scsi_device *sdev);
|
||||
bool ufsf_is_valid_lun(int lun);
|
||||
void ufsf_slave_configure(struct ufsf_feature *ufsf, struct scsi_device *sdev);
|
||||
void ufsf_change_lun(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp);
|
||||
|
||||
void ufsf_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp);
|
||||
void ufsf_reset_lu(struct ufsf_feature *ufsf);
|
||||
void ufsf_reset_host(struct ufsf_feature *ufsf);
|
||||
void ufsf_init(struct ufsf_feature *ufsf);
|
||||
void ufsf_reset(struct ufsf_feature *ufsf);
|
||||
void ufsf_remove(struct ufsf_feature *ufsf);
|
||||
void ufsf_set_init_state(struct ufsf_feature *ufsf);
|
||||
void ufsf_suspend(struct ufsf_feature *ufsf);
|
||||
void ufsf_resume(struct ufsf_feature *ufsf);
|
||||
|
||||
/* for hpb */
|
||||
void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp);
|
||||
#endif /* End of Header */
|
||||
|
|
@ -66,6 +66,7 @@
|
|||
#define UIC_CMD_TIMEOUT 500
|
||||
#endif
|
||||
|
||||
unsigned int storage_mfrid;
|
||||
/* NOP OUT retries waiting for NOP IN response */
|
||||
#define NOP_OUT_RETRIES 10
|
||||
/* Timeout after 30 msecs if NOP OUT hangs without response */
|
||||
|
|
@ -200,7 +201,21 @@ struct ufs_pm_lvl_states ufs_pm_lvl_states[] = {
|
|||
{UFS_POWERDOWN_PWR_MODE, UIC_LINK_HIBERN8_STATE},
|
||||
{UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE},
|
||||
};
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
static inline int is_support_hpb_100_device(unsigned int mfrid){
|
||||
return IS_SKHYNIX_DEVICE(mfrid);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
static inline int is_support_hpb_200_device(unsigned int mfrid){
|
||||
#if defined(CONFIG_MICRON_HPB)
|
||||
return (IS_SAMSUNG_DEVICE(mfrid) || IS_MICRON_DEVICE(mfrid));
|
||||
#else
|
||||
return IS_SAMSUNG_DEVICE(mfrid);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
static inline enum ufs_dev_pwr_mode
|
||||
ufs_get_pm_lvl_to_dev_pwr_mode(enum ufs_pm_level lvl)
|
||||
{
|
||||
|
|
@ -255,7 +270,36 @@ static struct ufs_dev_fix ufs_fixups[] = {
|
|||
UFS_FIX(UFS_VENDOR_SAMSUNG, "KLUDG4UHDB-B2D1",
|
||||
UFS_DEVICE_QUIRK_PA_HIBER8TIME),
|
||||
#endif
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H28S",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ15ACPMA",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ15AECMA",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ15AECMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ15AFAMA",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ15AFAMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ15AJAMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AECMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AECMZ",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AFAMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AFAMZ",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AJAMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AHDMM",
|
||||
SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP),
|
||||
#endif
|
||||
END_FIX
|
||||
};
|
||||
|
||||
|
|
@ -2563,9 +2607,23 @@ static int ufshcd_comp_scsi_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
|
|||
lrbp->command_type = UTP_CMD_TYPE_UFS_STORAGE;
|
||||
|
||||
if (likely(lrbp->cmd)) {
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_change_lun(&hba->ufsf, lrbp);
|
||||
ufsf_prep_fn(&hba->ufsf, lrbp);
|
||||
}
|
||||
#endif
|
||||
ufshcd_prepare_req_desc_hdr(lrbp, &upiu_flags,
|
||||
lrbp->cmd->sc_data_direction);
|
||||
ufshcd_prepare_utp_scsi_cmd_upiu(lrbp, upiu_flags);
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid)){
|
||||
if (hba->skhpb_state == SKHPB_PRESENT &&
|
||||
hba->issue_ioctl == false) {
|
||||
skhpb_prep_fn(hba, lrbp);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
|
@ -2655,6 +2713,22 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
|
|||
lrbp->req_abort_skip = false;
|
||||
|
||||
ufshcd_comp_scsi_upiu(hba, lrbp);
|
||||
#if defined(CONFIG_UFSFEATURE) && defined(CONFIG_UFSHPB)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
if (cmd->cmnd[0] != 0x28)
|
||||
BUG_ON(cmd->requeue_cnt);
|
||||
|
||||
if (cmd->requeue_cnt)
|
||||
err = -EAGAIN;
|
||||
|
||||
if (err) {
|
||||
ufshcd_release(hba);
|
||||
lrbp->cmd = NULL;
|
||||
clear_bit_unlock(tag, &hba->lrb_in_use);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
err = ufshcd_map_sg(hba, lrbp);
|
||||
if (err) {
|
||||
|
|
@ -2897,8 +2971,13 @@ static inline void ufshcd_put_dev_cmd_tag(struct ufs_hba *hba, int tag)
|
|||
* NOTE: Since there is only one available tag for device management commands,
|
||||
* it is expected you hold the hba->dev_cmd.lock mutex.
|
||||
*/
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
int ufshcd_exec_dev_cmd(struct ufs_hba *hba,
|
||||
enum dev_cmd_type cmd_type, int timeout)
|
||||
#else
|
||||
static int ufshcd_exec_dev_cmd(struct ufs_hba *hba,
|
||||
enum dev_cmd_type cmd_type, int timeout)
|
||||
#endif
|
||||
{
|
||||
struct ufshcd_lrb *lrbp;
|
||||
int err;
|
||||
|
|
@ -2967,8 +3046,13 @@ static inline void ufshcd_init_query(struct ufs_hba *hba,
|
|||
(*request)->upiu_req.selector = selector;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
int ufshcd_query_flag_retry(struct ufs_hba *hba,
|
||||
enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res)
|
||||
#else
|
||||
static int ufshcd_query_flag_retry(struct ufs_hba *hba,
|
||||
enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res)
|
||||
#endif
|
||||
{
|
||||
int ret;
|
||||
int retries;
|
||||
|
|
@ -4952,13 +5036,23 @@ static int ufshcd_slave_configure(struct scsi_device *sdev)
|
|||
struct request_queue *q = sdev->request_queue;
|
||||
struct ufs_hba *hba = shost_priv(sdev->host);
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_slave_configure(&hba->ufsf, sdev);
|
||||
}
|
||||
#endif
|
||||
blk_queue_update_dma_pad(q, PRDT_DATA_BYTE_COUNT_PAD - 1);
|
||||
|
||||
ufshcd_crypto_setup_rq_keyslot_manager(hba, q);
|
||||
|
||||
if (ufshcd_is_rpm_autosuspend_allowed(hba))
|
||||
sdev->rpm_autosuspend = 1;
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid)){
|
||||
if (sdev->lun < UFS_UPIU_MAX_GENERAL_LUN)
|
||||
hba->sdev_ufs_lu[sdev->lun] = sdev;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -5087,6 +5181,20 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
|
|||
if (schedule_work(&hba->eeh_work))
|
||||
pm_runtime_get_noresume(hba->dev);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
if (scsi_status == SAM_STAT_GOOD)
|
||||
ufsf_hpb_noti_rb(&hba->ufsf, lrbp);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid)){
|
||||
if (hba->skhpb_state == SKHPB_PRESENT &&
|
||||
scsi_status == SAM_STAT_GOOD)
|
||||
skhpb_rsp_upiu(hba, lrbp);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case UPIU_TRANSACTION_REJECT_UPIU:
|
||||
/* TODO: handle Reject UPIU Response */
|
||||
|
|
@ -5188,6 +5296,13 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
|
|||
cmd = lrbp->cmd;
|
||||
ufshcd_vops_compl_xfer_req(hba, index, (cmd) ? true : false);
|
||||
if (cmd) {
|
||||
#if defined(CONFIG_UFSFEATURE) && defined(CONFIG_UFSHPB) && defined(CONFIG_HPB_DEBUG)
|
||||
if (is_support_hpb_200_device(storage_mfrid))
|
||||
trace_printk("%llu + %u cmd 0x%X comp tag[%d] out %lX\n",
|
||||
(unsigned long long) blk_rq_pos(cmd->request),
|
||||
(unsigned int) blk_rq_sectors(cmd->request),
|
||||
cmd->cmnd[0], index, hba->outstanding_reqs);
|
||||
#endif
|
||||
ufshcd_add_command_trace(hba, index, "complete");
|
||||
result = ufshcd_transfer_rsp_status(hba, lrbp);
|
||||
scsi_dma_unmap(cmd);
|
||||
|
|
@ -6732,6 +6847,18 @@ out:
|
|||
hba->req_abort_count = 0;
|
||||
ufshcd_update_reg_hist(&hba->ufs_stats.dev_reset, (u32)err);
|
||||
if (!err) {
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid))
|
||||
ufsf_reset_lu(&hba->ufsf);
|
||||
#endif
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid)){
|
||||
if (hba->skhpb_state == SKHPB_PRESENT)
|
||||
hba->skhpb_state = SKHPB_RESET;
|
||||
schedule_delayed_work(&hba->skhpb_init_work,
|
||||
msecs_to_jiffies(10));
|
||||
}
|
||||
#endif
|
||||
err = SUCCESS;
|
||||
} else {
|
||||
dev_err(hba->dev, "%s: failed with err %d\n", __func__, err);
|
||||
|
|
@ -6942,6 +7069,12 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba *hba)
|
|||
int err;
|
||||
unsigned long flags;
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_reset_host(&hba->ufsf);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Stop the host controller and complete the requests
|
||||
* cleared by h/w
|
||||
|
|
@ -7300,18 +7433,25 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
|
|||
*/
|
||||
if (!(dev_info->wspecversion >= 0x310 ||
|
||||
dev_info->wspecversion == 0x220 ||
|
||||
(hba->dev_quirks & UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES)))
|
||||
(hba->dev_quirks & UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES))) {
|
||||
dev_err(hba->dev, "%s: vendor quirks don't support wb, wb_disable\n",__func__);
|
||||
goto wb_disabled;
|
||||
}
|
||||
|
||||
if (hba->desc_size.dev_desc < DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 4)
|
||||
if (hba->desc_size.dev_desc < DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 4) {
|
||||
dev_err(hba->dev, "%s: ufs device don't support ext ufs feature, wb_disable\n",
|
||||
__func__);
|
||||
goto wb_disabled;
|
||||
}
|
||||
|
||||
dev_info->d_ext_ufs_feature_sup =
|
||||
get_unaligned_be32(desc_buf +
|
||||
DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
|
||||
|
||||
if (!(dev_info->d_ext_ufs_feature_sup & UFS_DEV_WRITE_BOOSTER_SUP))
|
||||
if (!(dev_info->d_ext_ufs_feature_sup & UFS_DEV_WRITE_BOOSTER_SUP)) {
|
||||
dev_err(hba->dev, "%s: ufs device don't support write booster feature, wb_disable\n",__func__);
|
||||
goto wb_disabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* WB may be supported but not configured while provisioning.
|
||||
|
|
@ -7329,8 +7469,10 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
|
|||
dev_info->d_wb_alloc_units =
|
||||
get_unaligned_be32(desc_buf +
|
||||
DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS);
|
||||
if (!dev_info->d_wb_alloc_units)
|
||||
if (!dev_info->d_wb_alloc_units) {
|
||||
dev_err(hba->dev, "%s: read wb_share_alloc_units is 0 , wb_disable\n",__func__);
|
||||
goto wb_disabled;
|
||||
}
|
||||
} else {
|
||||
for (lun = 0; lun < UFS_UPIU_MAX_WB_LUN_ID; lun++) {
|
||||
d_lu_wb_buf_alloc = 0;
|
||||
|
|
@ -7345,8 +7487,10 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
|
|||
}
|
||||
}
|
||||
|
||||
if (!d_lu_wb_buf_alloc)
|
||||
if (!d_lu_wb_buf_alloc) {
|
||||
dev_err(hba->dev, "%s: read wb_share_alloc_units is 0 , wb_disable\n",__func__);
|
||||
goto wb_disabled;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
|
|
@ -7799,6 +7943,13 @@ static int ufshcd_add_lus(struct ufs_hba *hba)
|
|||
ufs_bsg_probe(hba);
|
||||
scsi_scan_host(hba->host);
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_device_check(hba);
|
||||
ufsf_init(&hba->ufsf);
|
||||
}
|
||||
#endif
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -7918,10 +8069,20 @@ reinit:
|
|||
ufshcd_set_active_icc_lvl(hba);
|
||||
|
||||
ufshcd_wb_config(hba);
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid))
|
||||
schedule_delayed_work(&hba->skhpb_init_work, 0);
|
||||
#endif
|
||||
/* Enable Auto-Hibernate if configured */
|
||||
ufshcd_auto_hibern8_enable(hba);
|
||||
|
||||
out:
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid))
|
||||
ufsf_reset(&hba->ufsf);
|
||||
#endif
|
||||
|
||||
spin_lock_irqsave(hba->host->host_lock, flags);
|
||||
if (ret)
|
||||
hba->ufshcd_state = UFSHCD_STATE_ERROR;
|
||||
|
|
@ -8783,6 +8944,11 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
|
|||
req_link_state = UIC_LINK_OFF_STATE;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid))
|
||||
ufsf_suspend(&hba->ufsf);
|
||||
#endif
|
||||
|
||||
ret = ufshcd_crypto_suspend(hba, pm_op);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
|
@ -8793,6 +8959,10 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
|
|||
*/
|
||||
ufshcd_hold(hba, false);
|
||||
hba->clk_gating.is_suspended = true;
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if(is_support_hpb_100_device(storage_mfrid))
|
||||
skhpb_suspend(hba);
|
||||
#endif
|
||||
|
||||
if (hba->clk_scaling.is_allowed) {
|
||||
cancel_work_sync(&hba->clk_scaling.suspend_work);
|
||||
|
|
@ -8917,6 +9087,11 @@ enable_gating:
|
|||
hba->dev_info.b_rpm_dev_flush_capable = false;
|
||||
ufshcd_release(hba);
|
||||
ufshcd_crypto_resume(hba, pm_op);
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_resume(&hba->ufsf);
|
||||
}
|
||||
#endif
|
||||
out:
|
||||
if (hba->dev_info.b_rpm_dev_flush_capable) {
|
||||
schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
|
||||
|
|
@ -9059,6 +9234,15 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
|
|||
cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid))
|
||||
ufsf_resume(&hba->ufsf);
|
||||
#endif
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid))
|
||||
skhpb_resume(hba);
|
||||
#endif
|
||||
|
||||
/* Schedule clock gating in case of no access to UFS device yet */
|
||||
ufshcd_release(hba);
|
||||
|
||||
|
|
@ -9313,8 +9497,20 @@ EXPORT_SYMBOL(ufshcd_shutdown);
|
|||
*/
|
||||
void ufshcd_remove(struct ufs_hba *hba)
|
||||
{
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_remove(&hba->ufsf);
|
||||
}
|
||||
#endif
|
||||
ufs_bsg_remove(hba);
|
||||
ufs_sysfs_remove_nodes(hba->dev);
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid)){
|
||||
skhpb_release(hba, SKHPB_NEED_INIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
scsi_remove_host(hba->host);
|
||||
destroy_workqueue(hba->eh_wq);
|
||||
/* disable interrupts */
|
||||
|
|
@ -9586,6 +9782,17 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
|
|||
*/
|
||||
ufshcd_set_ufs_dev_active(hba);
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
if (is_support_hpb_200_device(storage_mfrid)){
|
||||
ufsf_set_init_state(&hba->ufsf);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
if (is_support_hpb_100_device(storage_mfrid)){
|
||||
/* initialize hpb structures */
|
||||
ufshcd_init_hpb(hba);
|
||||
}
|
||||
#endif
|
||||
async_schedule(ufshcd_async_scan, hba);
|
||||
ufs_sysfs_add_nodes(hba->dev);
|
||||
|
||||
|
|
@ -9603,6 +9810,12 @@ out_disable:
|
|||
out_error:
|
||||
return err;
|
||||
}
|
||||
static int __init storage_mfrid_setup(char *str)
|
||||
{
|
||||
storage_mfrid = simple_strtol(str, NULL, 16);
|
||||
return 1;
|
||||
}
|
||||
__setup("storage_mfrid=",storage_mfrid_setup);
|
||||
EXPORT_SYMBOL_GPL(ufshcd_init);
|
||||
|
||||
MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
|
||||
|
|
|
|||
|
|
@ -73,10 +73,22 @@
|
|||
#include "ufs_quirks.h"
|
||||
#include "ufshci.h"
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
#include "ufsfeature.h"
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
#include "ufshpb_skh.h"
|
||||
#endif
|
||||
|
||||
#define UFSHCD "ufshcd"
|
||||
#define UFSHCD_DRIVER_VERSION "0.2"
|
||||
|
||||
#define UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8 0x40000
|
||||
extern unsigned int storage_mfrid;
|
||||
#define IS_SAMSUNG_DEVICE(mfrid) (UFS_VENDOR_SAMSUNG == (mfrid))
|
||||
#define IS_SKHYNIX_DEVICE(mfrid) (UFS_VENDOR_SKHYNIX == (mfrid))
|
||||
#define IS_MICRON_DEVICE(mfrid) (UFS_VENDOR_MICRON == (mfrid))
|
||||
|
||||
struct ufs_hba;
|
||||
|
||||
|
|
@ -1016,6 +1028,24 @@ struct ufs_hba {
|
|||
struct device bsg_dev;
|
||||
struct request_queue *bsg_queue;
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
/* HPB support */
|
||||
u32 skhpb_feat;
|
||||
int skhpb_state;
|
||||
int skhpb_max_regions;
|
||||
struct delayed_work skhpb_init_work;
|
||||
bool issue_ioctl;
|
||||
struct skhpb_lu *skhpb_lup[UFS_UPIU_MAX_GENERAL_LUN];
|
||||
struct work_struct skhpb_eh_work;
|
||||
u32 skhpb_quirk;
|
||||
u8 hpb_control_mode;
|
||||
#define SKHPB_U8_MAX 0xFF
|
||||
u8 skhpb_quicklist_lu_enable[UFS_UPIU_MAX_GENERAL_LUN];
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
struct scsi_device *sdev_ufs_lu[UFS_UPIU_MAX_GENERAL_LUN];
|
||||
#endif
|
||||
#ifdef CONFIG_SCSI_UFS_CRYPTO
|
||||
/* crypto */
|
||||
union ufs_crypto_capabilities crypto_capabilities;
|
||||
|
|
@ -1028,6 +1058,10 @@ struct ufs_hba {
|
|||
bool wb_buf_flush_enabled;
|
||||
bool wb_enabled;
|
||||
struct delayed_work rpm_dev_flush_recheck_work;
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
struct ufsf_feature ufsf;
|
||||
#endif
|
||||
ANDROID_KABI_RESERVE(1);
|
||||
ANDROID_KABI_RESERVE(2);
|
||||
ANDROID_KABI_RESERVE(3);
|
||||
|
|
@ -1123,7 +1157,11 @@ static inline bool ufshcd_is_auto_hibern8_enabled(struct ufs_hba *hba)
|
|||
|
||||
static inline bool ufshcd_is_wb_allowed(struct ufs_hba *hba)
|
||||
{
|
||||
#if defined(CONFIG_UFSTW)
|
||||
return false;
|
||||
#else
|
||||
return hba->caps & UFSHCD_CAP_WB_EN;
|
||||
#endif
|
||||
}
|
||||
|
||||
#define ufshcd_writel(hba, val, reg) \
|
||||
|
|
@ -1297,6 +1335,10 @@ void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, struct ufs_dev_fix *fixups);
|
|||
#define SD_ASCII_STD true
|
||||
#define SD_RAW false
|
||||
|
||||
#if defined(CONFIG_UFSFEATURE)
|
||||
int ufshcd_exec_dev_cmd(struct ufs_hba *hba,
|
||||
enum dev_cmd_type cmd_type, int timeout);
|
||||
#endif
|
||||
int ufshcd_hold(struct ufs_hba *hba, bool async);
|
||||
void ufshcd_release(struct ufs_hba *hba);
|
||||
|
||||
|
|
@ -1500,6 +1542,10 @@ static inline u8 ufshcd_scsi_to_upiu_lun(unsigned int scsi_lun)
|
|||
return scsi_lun & UFS_UPIU_MAX_UNIT_NUM_ID;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SCSI_SKHPB)
|
||||
int ufshcd_query_flag_retry(struct ufs_hba *hba,
|
||||
enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res);
|
||||
#endif
|
||||
|
||||
int ufshcd_dump_regs(struct ufs_hba *hba, size_t offset, size_t len,
|
||||
const char *prefix);
|
||||
|
|
|
|||
4099
drivers/scsi/ufs/ufshpb.c
Executable file
4099
drivers/scsi/ufs/ufshpb.c
Executable file
File diff suppressed because it is too large
Load diff
346
drivers/scsi/ufs/ufshpb.h
Executable file
346
drivers/scsi/ufs/ufshpb.h
Executable file
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Universal Flash Storage Host Performance Booster
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _UFSHPB_H_
|
||||
#define _UFSHPB_H_
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/blktrace_api.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
#include <scsi/scsi_driver.h>
|
||||
|
||||
#if defined(CONFIG_HPB_ERR_INJECTION)
|
||||
#include <linux/random.h>
|
||||
#endif
|
||||
|
||||
#include "../../../block/blk.h"
|
||||
#include "../../../block/blk-mq.h"
|
||||
#include "../../../block/blk-mq-sched.h"
|
||||
#include "../scsi_priv.h"
|
||||
|
||||
/* Version info*/
|
||||
#define UFSHPB_VER 0x0201
|
||||
#define UFSHPB_DD_VER 0x020800
|
||||
#define UFSHPB_DD_VER_POST UFSFEATURE_DD_VER_POST
|
||||
|
||||
/* Constant value*/
|
||||
#define MAX_ACTIVE_NUM 2
|
||||
#define MAX_INACTIVE_NUM 2
|
||||
|
||||
#define HPB_ENTRY_SIZE 0x08
|
||||
#define HPB_ENTREIS_PER_OS_PAGE (OS_PAGE_SIZE / HPB_ENTRY_SIZE)
|
||||
|
||||
#define RETRY_DELAY_MS 5000
|
||||
|
||||
/* HPB Support Chunk Size */
|
||||
#define HPB_MULTI_CHUNK_LOW 9
|
||||
#define HPB_MULTI_CHUNK_HIGH 128
|
||||
#define MAX_HPB_CONTEXT_ID 0x7f
|
||||
|
||||
/* Description */
|
||||
#define UFS_FEATURE_SUPPORT_HPB_BIT 0x80
|
||||
|
||||
/* Response UPIU types */
|
||||
#define HPB_RSP_NONE 0x00
|
||||
#define HPB_RSP_REQ_REGION_UPDATE 0x01
|
||||
|
||||
/* Vender defined OPCODE */
|
||||
#define UFSHPB_READ_BUFFER 0xF9
|
||||
#define UFSHPB_WRITE_BUFFER 0xFA
|
||||
|
||||
#define UFSHPB_GROUP_NUMBER 0x11
|
||||
#define UFSHPB_RB_ID_READ 0x01
|
||||
#define UFSHPB_WB_ID_PREFETCH 0x02
|
||||
#define TRANSFER_LEN 0x01
|
||||
|
||||
#define DEV_DATA_SEG_LEN 0x14
|
||||
#define DEV_SENSE_SEG_LEN 0x12
|
||||
#define DEV_DES_TYPE 0x80
|
||||
#define DEV_ADDITIONAL_LEN 0x10
|
||||
|
||||
#define PINNED_NOT_SET (-1)
|
||||
#define DEFAULT_WB_REQUEUE_CNT 3
|
||||
|
||||
/*
|
||||
* UFSHPB DEBUG
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_HPB_DEBUG)
|
||||
#define HPB_DEBUG(hpb, msg, args...) \
|
||||
do { if (hpb && hpb->debug) \
|
||||
printk(KERN_ERR "%s:%d " msg "\n", \
|
||||
__func__, __LINE__, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define HPB_FTRACE(hpb, msg, args...) \
|
||||
do { if (hpb && hpb->debug) \
|
||||
trace_printk("%04d\t " msg "\n", \
|
||||
__LINE__, ##args); \
|
||||
} while (0)
|
||||
#else
|
||||
#define HPB_DEBUG(hpb, msg, args...) do { } while (0)
|
||||
#define HPB_FTRACE(hpb, msg, args...) do { } while (0)
|
||||
#endif
|
||||
|
||||
enum UFSHPB_STATE {
|
||||
HPB_PRESENT = 1,
|
||||
HPB_SUSPEND = 2,
|
||||
HPB_FAILED = -2,
|
||||
HPB_NEED_INIT = 0,
|
||||
HPB_RESET = -3,
|
||||
};
|
||||
|
||||
enum HPB_RGN_STATE {
|
||||
HPB_RGN_INACTIVE,
|
||||
HPB_RGN_ACTIVE,
|
||||
HPB_RGN_PINNED,
|
||||
};
|
||||
|
||||
enum HPB_SRGN_STATE {
|
||||
HPB_SRGN_UNUSED,
|
||||
HPB_SRGN_DIRTY,
|
||||
HPB_SRGN_CLEAN,
|
||||
HPB_SRGN_ISSUED,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_HPB_ERR_INJECTION)
|
||||
enum HPB_ERR_INJECTION_SELECT {
|
||||
HPB_ERR_INJECTION_DISABLE,
|
||||
HPB_ERR_INJECTION_BITFLIP,
|
||||
HPB_ERR_INJECTION_OFFSET,
|
||||
HPB_ERR_INJECTION_RANDOM,
|
||||
};
|
||||
#endif
|
||||
|
||||
struct ufshpb_dev_info {
|
||||
int num_lu;
|
||||
int version;
|
||||
int rgn_size;
|
||||
int srgn_size;
|
||||
};
|
||||
|
||||
struct ufshpb_active_field {
|
||||
__be16 active_rgn;
|
||||
__be16 active_srgn;
|
||||
} __packed;
|
||||
|
||||
struct ufshpb_rsp_field {
|
||||
__be16 sense_data_len;
|
||||
u8 desc_type;
|
||||
u8 additional_len;
|
||||
u8 hpb_type;
|
||||
u8 reserved;
|
||||
u8 active_rgn_cnt;
|
||||
u8 inactive_rgn_cnt;
|
||||
struct ufshpb_active_field hpb_active_field[2];
|
||||
__be16 hpb_inactive_field[2];
|
||||
} __packed;
|
||||
|
||||
struct ufshpb_map_ctx {
|
||||
struct page **m_page;
|
||||
unsigned int *ppn_dirty;
|
||||
|
||||
struct list_head list_table;
|
||||
};
|
||||
|
||||
struct ufshpb_subregion {
|
||||
struct ufshpb_map_ctx *mctx;
|
||||
enum HPB_SRGN_STATE srgn_state;
|
||||
int rgn_idx;
|
||||
int srgn_idx;
|
||||
|
||||
/* below information is used by rsp_list */
|
||||
struct list_head list_act_srgn;
|
||||
};
|
||||
|
||||
struct ufshpb_region {
|
||||
struct ufshpb_subregion *srgn_tbl;
|
||||
enum HPB_RGN_STATE rgn_state;
|
||||
int rgn_idx;
|
||||
int srgn_cnt;
|
||||
|
||||
/* below information is used by rsp_list */
|
||||
struct list_head list_inact_rgn;
|
||||
|
||||
/* below information is used by lru */
|
||||
struct list_head list_lru_rgn;
|
||||
};
|
||||
|
||||
struct ufshpb_req {
|
||||
struct request *req;
|
||||
struct bio *bio;
|
||||
struct ufshpb_lu *hpb;
|
||||
struct list_head list_req;
|
||||
void (*end_io)(struct request *rq, int err);
|
||||
void *end_io_data;
|
||||
char sense[SCSI_SENSE_BUFFERSIZE];
|
||||
|
||||
union {
|
||||
struct {
|
||||
struct ufshpb_map_ctx *mctx;
|
||||
unsigned int rgn_idx;
|
||||
unsigned int srgn_idx;
|
||||
unsigned int lun;
|
||||
} rb;
|
||||
struct {
|
||||
struct page *m_page;
|
||||
unsigned int len;
|
||||
unsigned long lpn;
|
||||
} wb;
|
||||
};
|
||||
};
|
||||
|
||||
enum selection_type {
|
||||
LRU = 1,
|
||||
};
|
||||
|
||||
struct victim_select_info {
|
||||
int selection_type;
|
||||
struct list_head lh_lru_rgn;
|
||||
int max_lru_active_cnt; /* supported hpb #region - pinned #region */
|
||||
atomic64_t active_cnt;
|
||||
};
|
||||
|
||||
struct ufshpb_lu {
|
||||
struct ufsf_feature *ufsf;
|
||||
int lun;
|
||||
int qd;
|
||||
struct ufshpb_region *rgn_tbl;
|
||||
|
||||
spinlock_t hpb_lock;
|
||||
|
||||
spinlock_t retry_list_lock;
|
||||
struct ufshpb_req *map_req;
|
||||
int num_inflight_map_req;
|
||||
int throttle_map_req;
|
||||
struct list_head lh_map_req_free;
|
||||
struct list_head lh_map_req_retry;
|
||||
struct list_head lh_map_ctx_free;
|
||||
|
||||
spinlock_t rsp_list_lock;
|
||||
struct list_head lh_pinned_srgn;
|
||||
struct list_head lh_act_srgn;
|
||||
struct list_head lh_inact_rgn;
|
||||
|
||||
struct kobject kobj;
|
||||
struct mutex sysfs_lock;
|
||||
struct ufshpb_sysfs_entry *sysfs_entries;
|
||||
|
||||
struct ufshpb_req *pre_req;
|
||||
int num_inflight_pre_req;
|
||||
int throttle_pre_req;
|
||||
struct list_head lh_pre_req_free;
|
||||
int ctx_id_ticket;
|
||||
int pre_req_min_tr_len;
|
||||
int pre_req_max_tr_len;
|
||||
int pre_req_requeue_cnt;
|
||||
|
||||
struct work_struct pinned_work;
|
||||
struct delayed_work retry_work;
|
||||
struct work_struct task_work;
|
||||
|
||||
/* for selecting victim */
|
||||
struct victim_select_info lru_info;
|
||||
|
||||
int lu_max_active_rgns;
|
||||
int lu_pinned_rgn_startidx;
|
||||
int lu_pinned_end_offset;
|
||||
int srgns_per_lu;
|
||||
int rgns_per_lu;
|
||||
int srgns_per_rgn;
|
||||
int srgn_mem_size;
|
||||
int entries_per_rgn_shift;
|
||||
int entries_per_rgn_mask;
|
||||
int entries_per_srgn;
|
||||
int entries_per_srgn_shift;
|
||||
int entries_per_srgn_mask;
|
||||
int dwords_per_srgn;
|
||||
unsigned long long srgn_unit_size;
|
||||
int mpage_bytes;
|
||||
int mpages_per_srgn;
|
||||
int lu_num_blocks;
|
||||
|
||||
/* for debug */
|
||||
int alloc_mctx;
|
||||
int debug_free_table;
|
||||
bool force_disable;
|
||||
bool force_map_req_disable;
|
||||
bool debug;
|
||||
atomic64_t hit;
|
||||
atomic64_t miss;
|
||||
atomic64_t rb_noti_cnt;
|
||||
atomic64_t rb_active_cnt;
|
||||
atomic64_t rb_inactive_cnt;
|
||||
atomic64_t map_req_cnt;
|
||||
atomic64_t map_compl_cnt;
|
||||
atomic64_t pre_req_cnt;
|
||||
|
||||
atomic64_t act_rsp_list_cnt;
|
||||
atomic64_t inact_rsp_list_cnt;
|
||||
#if defined(CONFIG_HPB_ERR_INJECTION)
|
||||
enum HPB_ERR_INJECTION_SELECT err_injection_select;
|
||||
u64 err_injection_bitflip;
|
||||
u64 err_injection_offset;
|
||||
int err_injection_random;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ufshpb_sysfs_entry {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct ufshpb_lu *hpb, char *buf);
|
||||
ssize_t (*store)(struct ufshpb_lu *hpb, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
struct ufs_hba;
|
||||
struct ufshcd_lrb;
|
||||
|
||||
int ufshpb_get_state(struct ufsf_feature *ufsf);
|
||||
void ufshpb_set_state(struct ufsf_feature *ufsf, int state);
|
||||
void ufshpb_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf);
|
||||
void ufshpb_get_geo_info(struct ufsf_feature *ufsf, u8 *geo_buf);
|
||||
void ufshpb_get_lu_info(struct ufsf_feature *ufsf, int lun, u8 *unit_buf);
|
||||
void ufshpb_init_handler(struct work_struct *work);
|
||||
void ufshpb_rsp_upiu(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp);
|
||||
void ufshpb_resume(struct ufsf_feature *ufsf);
|
||||
void ufshpb_suspend(struct ufsf_feature *ufsf);
|
||||
void ufshpb_reset_host(struct ufsf_feature *ufsf);
|
||||
void ufshpb_reset(struct ufsf_feature *ufsf);
|
||||
void ufshpb_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp);
|
||||
void ufshpb_remove(struct ufsf_feature *ufsf, int state);
|
||||
#endif /* End of Header */
|
||||
3275
drivers/scsi/ufs/ufshpb_skh.c
Normal file
3275
drivers/scsi/ufs/ufshpb_skh.c
Normal file
File diff suppressed because it is too large
Load diff
518
drivers/scsi/ufs/ufshpb_skh.h
Normal file
518
drivers/scsi/ufs/ufshpb_skh.h
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
/*
|
||||
* Universal Flash Storage Host Performance Booster
|
||||
*
|
||||
* Original work Copyright (C) 2017-2018 Samsung Electronics Co., Ltd.
|
||||
* Modified work Copyright (C) 2018, Google, Inc.
|
||||
* Modified work Copyright (C) 2019 SK hynix
|
||||
*
|
||||
* Origianl work Authors:
|
||||
* Yongmyung Lee <ymhungry.lee@samsung.com>
|
||||
* Jinyoung Choi <j-young.choi@samsung.com>
|
||||
* Modifier
|
||||
* Kihyun Cho <kihyun.cho@sk.com>
|
||||
* Sengman Shin <seungman.shin@sk.com>
|
||||
* Jinsu Lee <jinsu1.lee@sk.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.
|
||||
*/
|
||||
|
||||
#ifndef _SKHPB_H_
|
||||
#define _SKHPB_H_
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/* Version info*/
|
||||
#define SKHPB_DD_VER 0x010506
|
||||
|
||||
/* QUIRKs */
|
||||
/* Use READ16 instead of HPB_READ command,
|
||||
* This is workaround solution to countmeasure QCT ICE issue. */
|
||||
#define SKHPB_QUIRK_USE_READ_16_FOR_ENCRYPTION (1 << 0)
|
||||
|
||||
/* This quirk makes HPB driver always works as Devie Control Mode.
|
||||
* To cover old Configuration descriptor format which interpret
|
||||
* the bHPBControl field as RESERVED. */
|
||||
#define SKHPB_QUIRK_ALWAYS_DEVICE_CONTROL_MODE (1 << 1)
|
||||
|
||||
/* Discard SubRegion activation hint information that has been processed,
|
||||
* when the host enters RPM/SPM sleep.
|
||||
* Must not be set the bit in ufs_quirks.h.*/
|
||||
#define SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP (1 << 20)
|
||||
|
||||
/* Constant value*/
|
||||
#define SKHPB_SECTOR 512
|
||||
#define SKHPB_BLOCK 4096
|
||||
#define SKHPB_SECTORS_PER_BLOCK (SKHPB_BLOCK / SKHPB_SECTOR)
|
||||
#define SKHPB_BITS_PER_DWORD 32
|
||||
#define SKHPB_MAX_ACTIVE_NUM 2
|
||||
#define SKHPB_MAX_INACTIVE_NUM 2
|
||||
|
||||
#define SKHPB_ENTRY_SIZE 0x08
|
||||
#define SKHPB_ENTREIS_PER_OS_PAGE (PAGE_SIZE / SKHPB_ENTRY_SIZE)
|
||||
|
||||
/* Description */
|
||||
#define SKHPB_UFS_FEATURE_SUPPORT_HPB_BIT 0x80
|
||||
|
||||
#define SKHPB_QUERY_DESC_DEVICE_MAX_SIZE 0x43
|
||||
#define SKHPB_QUERY_DESC_CONFIGURAION_MAX_SIZE 0xE6
|
||||
#define SKHPB_QUERY_DESC_UNIT_MAX_SIZE 0x29
|
||||
#define SKHPB_QUERY_DESC_GEOMETRY_MAX_SIZE 0x4D
|
||||
|
||||
/* Configuration for HPB */
|
||||
#define SKHPB_CONF_LU_ENABLE 0x00
|
||||
#define SKHPB_CONF_ACTIVE_REGIONS 0x10
|
||||
#define SKHPB_CONF_PINNED_START 0x12
|
||||
#define SKHPB_CONF_PINNED_NUM 0x14
|
||||
|
||||
/* Parameter Macros */
|
||||
#define SKHPB_DEV(h) ((h)->hba->dev)
|
||||
#define SKHPB_MAX_BVEC_SIZE 128
|
||||
|
||||
/* Use for HPB activate */
|
||||
#define SKHPB_CONFIG_LEN 0xd0
|
||||
|
||||
#define SKHPB_READ_LARGE_CHUNK_SUPPORT
|
||||
#define SKHPB_READ_LARGE_CHUNK_MAX_BLOCK_COUNT (128) //TRANSFER LENGTH: 8bit
|
||||
|
||||
typedef u64 skhpb_t;
|
||||
|
||||
enum skhpb_lu_set {
|
||||
LU_DISABLE = 0x00,
|
||||
LU_ENABLE = 0x01,
|
||||
LU_HPB_ENABLE = 0x02,
|
||||
LU_SET_MAX,
|
||||
};
|
||||
|
||||
struct skhpb_config_desc {
|
||||
unsigned char conf_dev_desc[16];
|
||||
unsigned char unit[UFS_UPIU_MAX_GENERAL_LUN][24];
|
||||
};
|
||||
|
||||
/* Response UPIU types */
|
||||
#define SKHPB_RSP_NONE 0x00
|
||||
#define SKHPB_RSP_REQ_REGION_UPDATE 0x01
|
||||
#define SKHPB_RSP_HPB_RESET 0x02
|
||||
#define SKHPB_PER_ACTIVE_INFO_BYTES 4
|
||||
#define SKHPB_PER_INACTIVE_INFO_BYTES 2
|
||||
|
||||
/* Vender defined OPCODE */
|
||||
#define SKHPB_READ 0xF8
|
||||
#define SKHPB_READ_BUFFER 0xF9
|
||||
#define SKHPB_WRITE_BUFFER 0xFA
|
||||
|
||||
#define SKHPB_DEV_DATA_SEG_LEN 0x14
|
||||
#define SKHPB_DEV_SENSE_SEG_LEN 0x12
|
||||
#define SKHPB_DEV_DES_TYPE 0x80
|
||||
#define SKHPB_DEV_ADDITIONAL_LEN 0x10
|
||||
|
||||
/* BYTE SHIFT */
|
||||
#define SKHPB_ZERO_BYTE_SHIFT 0
|
||||
#define SKHPB_ONE_BYTE_SHIFT 8
|
||||
#define SKHPB_TWO_BYTE_SHIFT 16
|
||||
#define SKHPB_THREE_BYTE_SHIFT 24
|
||||
|
||||
#define SKHPB_SHIFT_BYTE_0(num) ((num) << SKHPB_ZERO_BYTE_SHIFT)
|
||||
#define SKHPB_SHIFT_BYTE_1(num) ((num) << SKHPB_ONE_BYTE_SHIFT)
|
||||
|
||||
#define SKHPB_GET_BYTE_0(num) (((num) >> SKHPB_ZERO_BYTE_SHIFT) & 0xff)
|
||||
#define SKHPB_GET_BYTE_1(num) (((num) >> SKHPB_ONE_BYTE_SHIFT) & 0xff)
|
||||
#define SKHPB_GET_BYTE_2(num) (((num) >> SKHPB_TWO_BYTE_SHIFT) & 0xff)
|
||||
#define SKHPB_GET_BYTE_3(num) (((num) >> SKHPB_THREE_BYTE_SHIFT) & 0xff)
|
||||
|
||||
#define REGION_UNIT_SIZE(bit_offset) (0x01 << (bit_offset))
|
||||
|
||||
enum SKHPB_STATE {
|
||||
SKHPB_PRESENT = 1,
|
||||
SKHPB_NOT_SUPPORTED = -1,
|
||||
SKHPB_FAILED = -2,
|
||||
SKHPB_NEED_INIT = 0,
|
||||
SKHPB_RESET = -3,
|
||||
};
|
||||
|
||||
enum SKHPB_BUFFER_MODE {
|
||||
R_BUFFER = 0,
|
||||
W_BUFFER = 1,
|
||||
};
|
||||
|
||||
enum SKHPB_CMD {
|
||||
SKHPB_CMD_READ = 0,
|
||||
SKHPB_CMD_WRITE = 1,
|
||||
SKHPB_CMD_DISCARD = 2,
|
||||
SKHPB_CMD_OTHERS = 3,
|
||||
};
|
||||
|
||||
enum SKHPB_REGION_STATE {
|
||||
SKHPB_REGION_INACTIVE,
|
||||
SKHPB_REGION_ACTIVE,
|
||||
};
|
||||
|
||||
enum SKHPB_SUBREGION_STATE {
|
||||
SKHPB_SUBREGION_UNUSED,
|
||||
SKHPB_SUBREGION_DIRTY,
|
||||
SKHPB_SUBREGION_CLEAN,
|
||||
SKHPB_SUBREGION_ISSUED,
|
||||
};
|
||||
|
||||
enum SKHPB_CONTROL_MODE {
|
||||
HOST_CTRL_MODE = 0,
|
||||
DEV_CTRL_MODE = 1,
|
||||
};
|
||||
|
||||
enum SKHPB_RST_TIME {
|
||||
SKHPB_MAP_RSP_DISABLE = 0,
|
||||
SKHPB_MAP_RSP_ENABLE = 1,
|
||||
};
|
||||
|
||||
struct skhpb_func_desc {
|
||||
/*** Device Descriptor ***/
|
||||
/* 06h bNumberLU */
|
||||
int lu_cnt;
|
||||
/* 10h wSpecVersion */
|
||||
u16 spec_ver;
|
||||
/* 40h HPB Version */
|
||||
u16 hpb_ver;
|
||||
/* 42h HPB control mode */
|
||||
u8 hpb_control_mode;
|
||||
|
||||
/*** Geometry Descriptor ***/
|
||||
/* 48h bHPBRegionSize (UNIT: 512KB) */
|
||||
u8 hpb_region_size;
|
||||
/* 49h bHPBNumberLU */
|
||||
u8 hpb_number_lu;
|
||||
/* 4Ah bHPBSubRegionSize */
|
||||
u8 hpb_subregion_size;
|
||||
/* 4B:4Ch wDeviceMaxActiveHPBRegions */
|
||||
u16 hpb_device_max_active_regions;
|
||||
};
|
||||
|
||||
struct skhpb_lu_desc {
|
||||
/*** Unit Descriptor ****/
|
||||
/* 03h bLUEnable */
|
||||
int lu_enable;
|
||||
/* 06h lu queue depth info*/
|
||||
int lu_queue_depth;
|
||||
/* 0Ah bLogicalBlockSize. default 0x0C = 4KB */
|
||||
int lu_logblk_size;
|
||||
/* 0Bh qLogicalBlockCount. same as the read_capacity ret val. */
|
||||
u64 lu_logblk_cnt;
|
||||
|
||||
/* 23h:24h wLUMaxActiveHPBRegions */
|
||||
u16 lu_max_active_hpb_regions;
|
||||
/* 25h:26h wHPBPinnedRegionStartIdx */
|
||||
u16 hpb_pinned_region_startidx;
|
||||
/* 27h:28h wNumHPBPinnedRegions */
|
||||
u16 lu_num_hpb_pinned_regions;
|
||||
|
||||
|
||||
/* if 03h value is 02h, hpb_enable is set. */
|
||||
bool lu_hpb_enable;
|
||||
|
||||
int lu_hpb_pinned_end_offset;
|
||||
};
|
||||
|
||||
struct skhpb_rsp_active_list {
|
||||
u16 region[SKHPB_MAX_ACTIVE_NUM];
|
||||
u16 subregion[SKHPB_MAX_ACTIVE_NUM];
|
||||
};
|
||||
|
||||
struct skhpb_rsp_inactive_list {
|
||||
u16 region[SKHPB_MAX_INACTIVE_NUM];
|
||||
};
|
||||
|
||||
struct skhpb_rsp_update_entry {
|
||||
unsigned int lpn;
|
||||
skhpb_t ppn;
|
||||
};
|
||||
|
||||
struct skhpb_rsp_info {
|
||||
int type;
|
||||
int active_cnt;
|
||||
int inactive_cnt;
|
||||
struct skhpb_rsp_active_list active_list;
|
||||
struct skhpb_rsp_inactive_list inactive_list;
|
||||
|
||||
__u64 RSP_start;
|
||||
__u64 RSP_tasklet_enter;
|
||||
|
||||
struct list_head list_rsp_info;
|
||||
};
|
||||
|
||||
struct skhpb_rsp_field {
|
||||
u8 sense_data_len[2];
|
||||
u8 desc_type;
|
||||
u8 additional_len;
|
||||
u8 hpb_type;
|
||||
u8 lun;
|
||||
u8 active_region_cnt;
|
||||
u8 inactive_region_cnt;
|
||||
u8 hpb_active_field[8];
|
||||
u8 hpb_inactive_field[4];
|
||||
};
|
||||
|
||||
struct skhpb_map_ctx {
|
||||
struct page **m_page;
|
||||
unsigned int *ppn_dirty;
|
||||
|
||||
struct list_head list_table;
|
||||
};
|
||||
|
||||
struct skhpb_subregion {
|
||||
struct skhpb_map_ctx *mctx;
|
||||
enum SKHPB_SUBREGION_STATE subregion_state;
|
||||
int region;
|
||||
int subregion;
|
||||
bool last;
|
||||
struct list_head list_subregion;
|
||||
};
|
||||
|
||||
struct skhpb_region {
|
||||
struct skhpb_subregion *subregion_tbl;
|
||||
enum SKHPB_REGION_STATE region_state;
|
||||
bool is_pinned;
|
||||
int region;
|
||||
int subregion_count;
|
||||
/*below information is used by lru*/
|
||||
struct list_head list_region;
|
||||
int hit_count;
|
||||
};
|
||||
|
||||
struct skhpb_map_req {
|
||||
struct skhpb_lu *hpb;
|
||||
struct skhpb_map_ctx *mctx;
|
||||
struct bio bio;
|
||||
struct bio *pbio;
|
||||
struct bio_vec bvec[SKHPB_MAX_BVEC_SIZE];
|
||||
void (*end_io)(struct request *rq, int err);
|
||||
void *end_io_data;
|
||||
int region;
|
||||
int subregion;
|
||||
int subregion_mem_size;
|
||||
int lun;
|
||||
int retry_cnt;
|
||||
|
||||
/* for debug : RSP Profiling */
|
||||
__u64 RSP_start; // get the request from device
|
||||
__u64 RSP_issue; // issue scsi cmd
|
||||
__u64 RSP_end; // complete the request
|
||||
|
||||
char sense[SCSI_SENSE_BUFFERSIZE];
|
||||
|
||||
struct list_head list_map_req;
|
||||
int rwbuffer_flag;
|
||||
};
|
||||
|
||||
enum SKHPB_SELECTION_TYPE {
|
||||
TYPE_LRU = 1,
|
||||
TYPE_LFU = 2,
|
||||
};
|
||||
|
||||
struct skhpb_victim_select_info {
|
||||
int selection_type;
|
||||
struct list_head lru;
|
||||
int max_lru_active_count; // supported hpb #region - pinned #region
|
||||
atomic64_t active_count;
|
||||
};
|
||||
|
||||
struct skhpb_lu {
|
||||
struct skhpb_region *region_tbl;
|
||||
struct skhpb_rsp_info *rsp_info;
|
||||
struct skhpb_map_req *map_req;
|
||||
|
||||
struct list_head lh_map_ctx;
|
||||
struct list_head lh_subregion_req;
|
||||
struct list_head lh_rsp_info;
|
||||
|
||||
struct list_head lh_rsp_info_free;
|
||||
struct list_head lh_map_req_free;
|
||||
struct list_head lh_map_req_retry;
|
||||
int debug_free_table;
|
||||
|
||||
bool lu_hpb_enable;
|
||||
|
||||
struct delayed_work skhpb_pinned_work;
|
||||
struct delayed_work skhpb_map_req_retry_work;
|
||||
struct work_struct skhpb_rsp_work;
|
||||
struct bio_vec bvec[SKHPB_MAX_BVEC_SIZE];
|
||||
|
||||
int subregions_per_lu;
|
||||
int regions_per_lu;
|
||||
int subregion_mem_size;
|
||||
int last_subregion_mem_size;
|
||||
|
||||
/* for selecting victim */
|
||||
struct skhpb_victim_select_info lru_info;
|
||||
|
||||
int hpb_ver;
|
||||
int lu_max_active_regions;
|
||||
|
||||
int entries_per_subregion;
|
||||
int entries_per_subregion_shift;
|
||||
int entries_per_subregion_mask;
|
||||
|
||||
int entries_per_region_shift;
|
||||
int entries_per_region_mask;
|
||||
int subregions_per_region;
|
||||
|
||||
int dwords_per_subregion;
|
||||
unsigned long long subregion_unit_size;
|
||||
bool identical_size;
|
||||
|
||||
#define BITS_PER_PPN_DIRTY (BITS_PER_BYTE * sizeof(unsigned int))
|
||||
int ppn_dirties_per_subregion;
|
||||
|
||||
int mpage_bytes;
|
||||
int mpages_per_subregion;
|
||||
|
||||
/* for debug constant variables */
|
||||
unsigned long long lu_num_blocks;
|
||||
|
||||
int lun;
|
||||
|
||||
struct ufs_hba *hba;
|
||||
|
||||
spinlock_t hpb_lock;
|
||||
spinlock_t rsp_list_lock;
|
||||
spinlock_t map_list_lock;
|
||||
|
||||
struct kobject kobj;
|
||||
struct mutex sysfs_lock;
|
||||
struct skhpb_sysfs_entry *sysfs_entries;
|
||||
|
||||
bool hpb_control_mode;
|
||||
|
||||
/* for debug */
|
||||
bool force_hpb_read_disable;
|
||||
bool force_map_req_disable;
|
||||
bool read_buf_debug;
|
||||
atomic64_t hit;
|
||||
atomic64_t size_miss;
|
||||
atomic64_t region_miss;
|
||||
atomic64_t subregion_miss;
|
||||
atomic64_t entry_dirty_miss;
|
||||
atomic64_t rb_noti_cnt;
|
||||
atomic64_t rb_fail;
|
||||
atomic64_t reset_noti_cnt;
|
||||
atomic64_t w_map_req_cnt;
|
||||
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
||||
atomic64_t lc_entry_dirty_miss;
|
||||
atomic64_t lc_reg_subreg_miss;
|
||||
atomic64_t lc_hit;
|
||||
#endif
|
||||
atomic64_t map_req_cnt;
|
||||
atomic64_t region_add;
|
||||
atomic64_t region_evict;
|
||||
atomic64_t canceled_resp;
|
||||
atomic64_t canceled_map_req;
|
||||
atomic64_t alloc_map_req_cnt;
|
||||
};
|
||||
|
||||
struct skhpb_sysfs_entry {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct skhpb_lu *hpb, char *buf);
|
||||
ssize_t (*store)(struct skhpb_lu *hpb, const char *, size_t);
|
||||
};
|
||||
|
||||
struct ufshcd_lrb;
|
||||
|
||||
void ufshcd_init_hpb(struct ufs_hba *hba);
|
||||
void skhpb_init_handler(struct work_struct *work);
|
||||
void skhpb_prep_fn(struct ufs_hba *hba, struct ufshcd_lrb *lrbp);
|
||||
void skhpb_rsp_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp);
|
||||
void skhpb_suspend(struct ufs_hba *hba);
|
||||
void skhpb_resume(struct ufs_hba *hba);
|
||||
void skhpb_release(struct ufs_hba *hba, int state);
|
||||
int skhpb_issue_req_dev_ctx(struct skhpb_lu *hpb, unsigned char *buf,
|
||||
int buf_length);
|
||||
int skhpb_control_validation(struct ufs_hba *hba,
|
||||
struct skhpb_config_desc *config);
|
||||
|
||||
extern u32 skhpb_debug_mask;
|
||||
extern int debug_map_req;
|
||||
enum SKHPB_LOG_LEVEL {
|
||||
SKHPB_LOG_LEVEL_OFF = 0,
|
||||
SKHPB_LOG_LEVEL_ERR = 1,
|
||||
SKHPB_LOG_LEVEL_INFO = 2,
|
||||
SKHPB_LOG_LEVEL_DEBUG = 3,
|
||||
SKHPB_LOG_LEVEL_HEX = 4,
|
||||
};
|
||||
enum SKHPB_LOG_MASK {
|
||||
SKHPB_LOG_OFF = SKHPB_LOG_LEVEL_OFF, /* 0 */
|
||||
SKHPB_LOG_ERR = (1U << SKHPB_LOG_LEVEL_ERR), /* 2 */
|
||||
SKHPB_LOG_INFO = (1U << SKHPB_LOG_LEVEL_INFO), /* 4 */
|
||||
SKHPB_LOG_DEBUG = (1U << SKHPB_LOG_LEVEL_DEBUG), /* 8 */
|
||||
SKHPB_LOG_HEX = (1U << SKHPB_LOG_LEVEL_HEX), /* 16 */
|
||||
};
|
||||
#define SKHPB_DRIVER_E(fmt, args...) \
|
||||
do { \
|
||||
if(likely(skhpb_debug_mask & SKHPB_LOG_ERR)) \
|
||||
pr_err("[HPB E][%s:%d] " fmt, __func__, __LINE__, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define SKHPB_DRIVER_I(fmt, args...) \
|
||||
do { \
|
||||
if(unlikely(skhpb_debug_mask & SKHPB_LOG_INFO)) \
|
||||
pr_err("[HPB][%s:%d] " fmt, __func__, __LINE__, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define SKHPB_DRIVER_D(fmt, args...) \
|
||||
do { \
|
||||
if(unlikely(skhpb_debug_mask & SKHPB_LOG_DEBUG)) \
|
||||
printk(KERN_DEBUG "[HPB][%s:%d] " fmt, __func__, __LINE__, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define SKHPB_DRIVER_HEXDUMP(fmt, args...) \
|
||||
do { \
|
||||
if(unlikely(skhpb_debug_mask & SKHPB_LOG_HEX)) { \
|
||||
print_hex_dump(KERN_DEBUG, fmt, DUMP_PREFIX_ADDRESS, ##args); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SKHPB_MAP_REQ_TIME(map_req, val, print) \
|
||||
do { \
|
||||
if(unlikely(debug_map_req)) { \
|
||||
val = ktime_to_us(ktime_get()); \
|
||||
if(print) { \
|
||||
SKHPB_DRIVER_I("SKHPB COMPL BUFFER %d - %d\n", \
|
||||
map_req->region, map_req->subregion); \
|
||||
SKHPB_DRIVER_I("start~issue = %lluus, issue~end = %lluus\n", \
|
||||
map_req->RSP_issue - map_req->RSP_start, \
|
||||
map_req->RSP_end - map_req->RSP_issue); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SKHPB_RSP_TIME(val) \
|
||||
do { \
|
||||
if(unlikely(debug_map_req)) { \
|
||||
val = ktime_to_us(ktime_get()); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#endif /* End of Header */
|
||||
900
drivers/scsi/ufs/ufstw.c
Executable file
900
drivers/scsi/ufs/ufstw.c
Executable file
|
|
@ -0,0 +1,900 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
static int ufstw_create_sysfs(struct ufsf_feature *ufsf, struct ufstw_lu *tw);
|
||||
|
||||
inline int ufstw_get_state(struct ufsf_feature *ufsf)
|
||||
{
|
||||
return atomic_read(&ufsf->tw_state);
|
||||
}
|
||||
|
||||
inline void ufstw_set_state(struct ufsf_feature *ufsf, int state)
|
||||
{
|
||||
atomic_set(&ufsf->tw_state, state);
|
||||
}
|
||||
|
||||
static int ufstw_is_not_present(struct ufsf_feature *ufsf)
|
||||
{
|
||||
enum UFSTW_STATE cur_state = ufstw_get_state(ufsf);
|
||||
|
||||
if (cur_state != TW_PRESENT) {
|
||||
INFO_MSG("tw_state != TW_PRESENT (%d)", cur_state);
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define FLAG_IDN_NAME(idn) \
|
||||
(idn == QUERY_FLAG_IDN_TW_EN ? "tw_enable" : \
|
||||
idn == QUERY_FLAG_IDN_TW_BUF_FLUSH_EN ? "flush_enable" : \
|
||||
idn == QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN ? \
|
||||
"flush_hibern" : "unknown")
|
||||
|
||||
#define ATTR_IDN_NAME(idn) \
|
||||
(idn == QUERY_ATTR_IDN_TW_FLUSH_STATUS ? "flush_status" : \
|
||||
idn == QUERY_ATTR_IDN_TW_AVAIL_BUF_SIZE ? "avail_buffer_size" :\
|
||||
idn == QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST ? "lifetime_est" : \
|
||||
idn == QUERY_ATTR_IDN_TW_CURR_BUF_SIZE ? "current_buf_size" : \
|
||||
"unknown")
|
||||
|
||||
static int ufstw_read_lu_attr(struct ufstw_lu *tw, u8 idn, u32 *attr_val)
|
||||
{
|
||||
struct ufs_hba *hba = tw->ufsf->hba;
|
||||
int err = 0, lun;
|
||||
u32 val;
|
||||
|
||||
lun = (tw->lun == TW_LU_SHARED) ? 0 : tw->lun;
|
||||
err = ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn,
|
||||
(u8)lun, &val);
|
||||
if (err) {
|
||||
ERR_MSG("read attr [0x%.2X](%s) failed. (%d)", idn,
|
||||
ATTR_IDN_NAME(idn), err);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*attr_val = val;
|
||||
|
||||
INFO_MSG("read attr LUN(%d) [0x%.2X](%s) success (%u)",
|
||||
lun, idn, ATTR_IDN_NAME(idn), *attr_val);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ufstw_set_lu_flag(struct ufstw_lu *tw, u8 idn, bool *flag_res)
|
||||
{
|
||||
struct ufs_hba *hba = tw->ufsf->hba;
|
||||
int err = 0, lun;
|
||||
|
||||
lun = (tw->lun == TW_LU_SHARED) ? 0 : tw->lun;
|
||||
err = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_SET_FLAG, idn,
|
||||
(u8)lun, NULL);
|
||||
if (err) {
|
||||
ERR_MSG("set flag [0x%.2X](%s) failed. (%d)", idn,
|
||||
FLAG_IDN_NAME(idn), err);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*flag_res = true;
|
||||
|
||||
INFO_MSG("set flag LUN(%d) [0x%.2X](%s) success. (%u)",
|
||||
lun, idn, FLAG_IDN_NAME(idn), *flag_res);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ufstw_clear_lu_flag(struct ufstw_lu *tw, u8 idn, bool *flag_res)
|
||||
{
|
||||
struct ufs_hba *hba = tw->ufsf->hba;
|
||||
int err = 0, lun;
|
||||
|
||||
lun = (tw->lun == TW_LU_SHARED) ? 0 : tw->lun;
|
||||
err = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_CLEAR_FLAG, idn,
|
||||
(u8)lun, NULL);
|
||||
if (err) {
|
||||
ERR_MSG("clear flag [0x%.2X](%s) failed. (%d)", idn,
|
||||
FLAG_IDN_NAME(idn), err);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*flag_res = false;
|
||||
|
||||
INFO_MSG("clear flag LUN(%d) [0x%.2X](%s) success. (%u)",
|
||||
lun, idn, FLAG_IDN_NAME(idn), *flag_res);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ufstw_read_lu_flag(struct ufstw_lu *tw, u8 idn, bool *flag_res)
|
||||
{
|
||||
struct ufs_hba *hba = tw->ufsf->hba;
|
||||
int err = 0, lun;
|
||||
bool val;
|
||||
|
||||
lun = (tw->lun == TW_LU_SHARED) ? 0 : tw->lun;
|
||||
err = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG, idn,
|
||||
(u8)lun, &val);
|
||||
if (err) {
|
||||
ERR_MSG("read flag [0x%.2X](%s) failed. (%d)", idn,
|
||||
FLAG_IDN_NAME(idn), err);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*flag_res = val;
|
||||
|
||||
INFO_MSG("read flag LUN(%d) [0x%.2X](%s) success. (%u)",
|
||||
lun, idn, FLAG_IDN_NAME(idn), *flag_res);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
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 void ufstw_switch_disable_state(struct ufstw_lu *tw)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
WARN_MSG("dTurboWriteBUfferLifeTImeEst (0x%.2X)", tw->lifetime_est);
|
||||
WARN_MSG("tw-mode will change to disable-mode");
|
||||
|
||||
mutex_lock(&tw->sysfs_lock);
|
||||
ufstw_set_state(tw->ufsf, TW_FAILED);
|
||||
mutex_unlock(&tw->sysfs_lock);
|
||||
|
||||
if (tw->tw_enable) {
|
||||
pm_runtime_get_sync(tw->ufsf->hba->dev);
|
||||
err = ufstw_clear_lu_flag(tw, QUERY_FLAG_IDN_TW_EN,
|
||||
&tw->tw_enable);
|
||||
pm_runtime_put_sync(tw->ufsf->hba->dev);
|
||||
if (err)
|
||||
WARN_MSG("tw_enable flag clear failed");
|
||||
}
|
||||
}
|
||||
|
||||
static int ufstw_check_lifetime_not_guarantee(struct ufstw_lu *tw)
|
||||
{
|
||||
bool disable_flag = false;
|
||||
|
||||
if (tw->lifetime_est & MASK_UFSTW_LIFETIME_NOT_GUARANTEE) {
|
||||
if (tw->lun == TW_LU_SHARED)
|
||||
WARN_MSG("lun-shared lifetime_est[31] (1)");
|
||||
else
|
||||
WARN_MSG("lun %d lifetime_est[31] (1)",
|
||||
tw->lun);
|
||||
|
||||
WARN_MSG("Device not guarantee the lifetime of TW Buffer");
|
||||
#if defined(CONFIG_UFSTW_IGNORE_GUARANTEE_BIT)
|
||||
WARN_MSG("but we will ignore them for PoC");
|
||||
#else
|
||||
disable_flag = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (disable_flag ||
|
||||
(tw->lifetime_est & ~MASK_UFSTW_LIFETIME_NOT_GUARANTEE) >=
|
||||
UFSTW_MAX_LIFETIME_VALUE) {
|
||||
ufstw_switch_disable_state(tw);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ufstw_lifetime_work_fn(struct work_struct *work)
|
||||
{
|
||||
struct ufstw_lu *tw;
|
||||
int ret;
|
||||
|
||||
tw = container_of(work, struct ufstw_lu, tw_lifetime_work);
|
||||
|
||||
if (ufstw_is_not_present(tw->ufsf))
|
||||
return;
|
||||
|
||||
pm_runtime_get_sync(tw->ufsf->hba->dev);
|
||||
ret = ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST,
|
||||
&tw->lifetime_est);
|
||||
pm_runtime_put_sync(tw->ufsf->hba->dev);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
ufstw_check_lifetime_not_guarantee(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 (!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;
|
||||
}
|
||||
spin_unlock_bh(&tw->lifetime_lock);
|
||||
|
||||
TMSG(tw->ufsf, lrbp->lun, "%s:%d tw_lifetime_work %u",
|
||||
__func__, __LINE__, tw->stat_write_sec);
|
||||
}
|
||||
|
||||
static inline void ufstw_init_lu_jobs(struct ufstw_lu *tw)
|
||||
{
|
||||
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_work_sync(&tw->tw_lifetime_work);
|
||||
INFO_MSG("cancel_work_sync(tw_lifetime_work) ufstw_lu[%d] (%d)",
|
||||
tw->lun, ret);
|
||||
}
|
||||
|
||||
static inline int ufstw_version_check(struct ufstw_dev_info *tw_dev_info)
|
||||
{
|
||||
INFO_MSG("Support TW Spec : Driver = %.4X, Device = %.4X",
|
||||
UFSTW_VER, tw_dev_info->tw_ver);
|
||||
|
||||
INFO_MSG("TW Driver Version : %.6X%s", UFSTW_DD_VER,
|
||||
UFSTW_DD_VER_POST);
|
||||
|
||||
if (tw_dev_info->tw_ver != UFSTW_VER)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ufstw_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf)
|
||||
{
|
||||
struct ufstw_dev_info *tw_dev_info = &ufsf->tw_dev_info;
|
||||
|
||||
if (LI_EN_32(&desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP]) &
|
||||
UFS_FEATURE_SUPPORT_TW_BIT) {
|
||||
INFO_MSG("bUFSExFeaturesSupport: TW is set");
|
||||
} else {
|
||||
ERR_MSG("bUFSExFeaturesSupport: TW not support");
|
||||
ufstw_set_state(ufsf, TW_FAILED);
|
||||
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];
|
||||
tw_dev_info->tw_shared_buf_alloc_units =
|
||||
LI_EN_32(&desc_buf[DEVICE_DESC_PARAM_TW_SHARED_BUF_ALLOC_UNITS]);
|
||||
tw_dev_info->tw_ver = LI_EN_16(&desc_buf[DEVICE_DESC_PARAM_TW_VER]);
|
||||
|
||||
if (ufstw_version_check(tw_dev_info)) {
|
||||
ERR_MSG("TW Spec Version mismatch. TW disabled");
|
||||
ufstw_set_state(ufsf, TW_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
INFO_MSG("tw_dev [55] dNumSharedTUrboWriteBufferAllocUnits (%u)",
|
||||
tw_dev_info->tw_shared_buf_alloc_units);
|
||||
|
||||
if (tw_dev_info->tw_buf_type == TW_BUF_TYPE_SHARED &&
|
||||
tw_dev_info->tw_shared_buf_alloc_units == 0) {
|
||||
ERR_MSG("TW use shared buffer. But alloc unit is (0)");
|
||||
ufstw_set_state(ufsf, TW_FAILED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ufstw_get_geo_info(struct ufsf_feature *ufsf, u8 *geo_buf)
|
||||
{
|
||||
struct ufstw_dev_info *tw_dev_info = &ufsf->tw_dev_info;
|
||||
|
||||
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");
|
||||
ufstw_set_state(ufsf, TW_FAILED);
|
||||
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] bSupportedTWBufferUserSpaceReductionTypes (%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]);
|
||||
}
|
||||
|
||||
static void ufstw_alloc_shared_lu(struct ufsf_feature *ufsf)
|
||||
{
|
||||
struct ufstw_lu *tw;
|
||||
|
||||
tw = kzalloc(sizeof(struct ufstw_lu), GFP_KERNEL);
|
||||
if (!tw) {
|
||||
ERR_MSG("ufstw_lu[shared] memory alloc failed");
|
||||
return;
|
||||
}
|
||||
|
||||
tw->lun = TW_LU_SHARED;
|
||||
tw->ufsf = ufsf;
|
||||
ufsf->tw_lup[0] = tw;
|
||||
|
||||
INFO_MSG("ufstw_lu[shared] is TurboWrite-Enabled");
|
||||
}
|
||||
|
||||
static void ufstw_get_lu_info(struct ufsf_feature *ufsf, 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_WRITE_BUFFER_ALLOC_UNIT]);
|
||||
|
||||
ufsf->tw_lup[lun] = NULL;
|
||||
|
||||
if (lu_desc.tw_lu_buf_size) {
|
||||
ufsf->tw_lup[lun] =
|
||||
kzalloc(sizeof(struct ufstw_lu), GFP_KERNEL);
|
||||
if (!ufsf->tw_lup[lun]) {
|
||||
ERR_MSG("ufstw_lu[%d] memory alloc faild", lun);
|
||||
return;
|
||||
}
|
||||
|
||||
tw = ufsf->tw_lup[lun];
|
||||
tw->ufsf = ufsf;
|
||||
tw->lun = lun;
|
||||
INFO_MSG("ufstw_lu[%d] [29:2C] dLUNumTWBufferAllocUnits (%u)",
|
||||
lun, lu_desc.tw_lu_buf_size);
|
||||
INFO_MSG("ufstw_lu[%d] is TurboWrite-Enabled.", lun);
|
||||
} else {
|
||||
INFO_MSG("ufstw_lu[%d] [29:2C] dLUNumTWBufferAllocUnits (%u)",
|
||||
lun, lu_desc.tw_lu_buf_size);
|
||||
INFO_MSG("ufstw_lu[%d] is TurboWrite-disabled", lun);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ufstw_alloc_lu(struct ufsf_feature *ufsf,
|
||||
int lun, u8 *lu_buf)
|
||||
{
|
||||
if (ufsf->tw_dev_info.tw_buf_type == TW_BUF_TYPE_SHARED &&
|
||||
!ufsf->tw_lup[0])
|
||||
ufstw_alloc_shared_lu(ufsf);
|
||||
else if (ufsf->tw_dev_info.tw_buf_type == TW_BUF_TYPE_LU)
|
||||
ufstw_get_lu_info(ufsf, lun, lu_buf);
|
||||
}
|
||||
|
||||
static inline void ufstw_print_lu_flag_attr(struct ufstw_lu *tw)
|
||||
{
|
||||
char lun_str[20] = { 0 };
|
||||
|
||||
if (tw->lun == TW_LU_SHARED)
|
||||
snprintf(lun_str, 7, "shared");
|
||||
else
|
||||
snprintf(lun_str, 2, "%d", tw->lun);
|
||||
|
||||
INFO_MSG("tw_flag ufstw_lu[%s] IDN (0x%.2X) tw_enable (%d)",
|
||||
lun_str, QUERY_FLAG_IDN_TW_EN, tw->tw_enable);
|
||||
INFO_MSG("tw_flag ufstw_lu[%s] IDN (0x%.2X) flush_enable (%d)",
|
||||
lun_str, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN,
|
||||
tw->flush_enable);
|
||||
INFO_MSG("tw_flag ufstw_lu[%s] IDN (0x%.2X) flush_hibern (%d)",
|
||||
lun_str, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN,
|
||||
tw->flush_during_hibern_enter);
|
||||
|
||||
INFO_MSG("tw_attr ufstw_lu[%s] IDN (0x%.2X) flush_status (%u)",
|
||||
lun_str, QUERY_ATTR_IDN_TW_FLUSH_STATUS, tw->flush_status);
|
||||
INFO_MSG("tw_attr ufstw_lu[%s] IDN (0x%.2X) buffer_size (%u)",
|
||||
lun_str, QUERY_ATTR_IDN_TW_AVAIL_BUF_SIZE,
|
||||
tw->available_buffer_size);
|
||||
INFO_MSG("tw_attr ufstw_lu[%s] IDN (0x%.2X) buffer_lifetime (0x%.2X)",
|
||||
lun_str, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST,
|
||||
tw->lifetime_est);
|
||||
}
|
||||
|
||||
static inline void ufstw_lu_update(struct ufstw_lu *tw)
|
||||
{
|
||||
/* Flag */
|
||||
pm_runtime_get_sync(tw->ufsf->hba->dev);
|
||||
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->flush_enable))
|
||||
goto error_put;
|
||||
|
||||
if (ufstw_read_lu_flag(tw, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN,
|
||||
&tw->flush_during_hibern_enter))
|
||||
goto error_put;
|
||||
|
||||
/* Attribute */
|
||||
if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_FLUSH_STATUS,
|
||||
&tw->flush_status))
|
||||
goto error_put;
|
||||
|
||||
if (ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_AVAIL_BUF_SIZE,
|
||||
&tw->available_buffer_size))
|
||||
goto error_put;
|
||||
|
||||
ufstw_read_lu_attr(tw, QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST,
|
||||
&tw->lifetime_est);
|
||||
error_put:
|
||||
pm_runtime_put_sync(tw->ufsf->hba->dev);
|
||||
}
|
||||
|
||||
static int ufstw_lu_init(struct ufsf_feature *ufsf, int lun)
|
||||
{
|
||||
struct ufstw_lu *tw;
|
||||
int ret = 0;
|
||||
|
||||
if (lun == TW_LU_SHARED)
|
||||
tw = ufsf->tw_lup[0];
|
||||
else
|
||||
tw = ufsf->tw_lup[lun];
|
||||
|
||||
tw->ufsf = ufsf;
|
||||
spin_lock_init(&tw->lifetime_lock);
|
||||
|
||||
ufstw_lu_update(tw);
|
||||
|
||||
ret = ufstw_check_lifetime_not_guarantee(tw);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
ufstw_print_lu_flag_attr(tw);
|
||||
|
||||
tw->stat_write_sec = 0;
|
||||
|
||||
ufstw_init_lu_jobs(tw);
|
||||
|
||||
#if defined(CONFIG_UFSTW_BOOT_ENABLED)
|
||||
pm_runtime_get_sync(ufsf->hba->dev);
|
||||
ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_EN, &tw->tw_enable);
|
||||
ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN,
|
||||
&tw->flush_during_hibern_enter);
|
||||
pm_runtime_put_sync(ufsf->hba->dev);
|
||||
#endif
|
||||
ret = ufstw_create_sysfs(ufsf, tw);
|
||||
if (ret)
|
||||
ERR_MSG("create sysfs failed");
|
||||
err_out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ufstw_init(struct ufsf_feature *ufsf)
|
||||
{
|
||||
int lun, ret = 0;
|
||||
unsigned int tw_enabled_lun = 0;
|
||||
|
||||
INFO_MSG("init start.. tw_state (%d)", ufstw_get_state(ufsf));
|
||||
|
||||
if (ufsf->tw_dev_info.tw_buf_type == TW_BUF_TYPE_SHARED) {
|
||||
if (!ufsf->tw_lup[0]) {
|
||||
ERR_MSG("tw_lup memory allocation failed");
|
||||
goto out;
|
||||
}
|
||||
BUG_ON(ufsf->tw_lup[0]->lun != TW_LU_SHARED);
|
||||
|
||||
ret = ufstw_lu_init(ufsf, TW_LU_SHARED);
|
||||
if (ret)
|
||||
goto out_free_mem;
|
||||
|
||||
INFO_MSG("ufstw_lu[shared] working");
|
||||
tw_enabled_lun++;
|
||||
} else {
|
||||
seq_scan_lu(lun) {
|
||||
if (!ufsf->tw_lup[lun])
|
||||
continue;
|
||||
|
||||
ret = ufstw_lu_init(ufsf, lun);
|
||||
if (ret)
|
||||
goto out_free_mem;
|
||||
|
||||
INFO_MSG("ufstw_lu[%d] working", lun);
|
||||
tw_enabled_lun++;
|
||||
}
|
||||
|
||||
if (tw_enabled_lun > ufsf->tw_dev_info.tw_number_lu) {
|
||||
ERR_MSG("lu count mismatched");
|
||||
goto out_free_mem;
|
||||
}
|
||||
}
|
||||
|
||||
if (tw_enabled_lun == 0) {
|
||||
ERR_MSG("tw_enabled_lun count zero");
|
||||
goto out_free_mem;
|
||||
}
|
||||
|
||||
ufstw_set_state(ufsf, TW_PRESENT);
|
||||
return;
|
||||
out_free_mem:
|
||||
seq_scan_lu(lun) {
|
||||
kfree(ufsf->tw_lup[lun]);
|
||||
ufsf->tw_lup[lun] = NULL;
|
||||
}
|
||||
out:
|
||||
ERR_MSG("Turbo write initialization failed");
|
||||
|
||||
ufstw_set_state(ufsf, TW_FAILED);
|
||||
}
|
||||
|
||||
static inline void ufstw_remove_sysfs(struct ufstw_lu *tw)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = kobject_uevent(&tw->kobj, KOBJ_REMOVE);
|
||||
INFO_MSG("kobject removed (%d)", ret);
|
||||
kobject_del(&tw->kobj);
|
||||
}
|
||||
|
||||
void ufstw_remove(struct ufsf_feature *ufsf)
|
||||
{
|
||||
struct ufstw_lu *tw;
|
||||
int lun;
|
||||
|
||||
dump_stack();
|
||||
INFO_MSG("start release");
|
||||
|
||||
ufstw_set_state(ufsf, TW_FAILED);
|
||||
|
||||
if (ufsf->tw_dev_info.tw_buf_type == TW_BUF_TYPE_SHARED) {
|
||||
tw = ufsf->tw_lup[0];
|
||||
INFO_MSG("ufstw_lu[shared] %p", tw);
|
||||
ufsf->tw_lup[0] = NULL;
|
||||
ufstw_cancel_lu_jobs(tw);
|
||||
ufstw_remove_sysfs(tw);
|
||||
kfree(tw);
|
||||
} else {
|
||||
seq_scan_lu(lun) {
|
||||
tw = ufsf->tw_lup[lun];
|
||||
INFO_MSG("ufstw_lu[%d] %p", lun, tw);
|
||||
|
||||
if (!tw)
|
||||
continue;
|
||||
ufsf->tw_lup[lun] = NULL;
|
||||
ufstw_cancel_lu_jobs(tw);
|
||||
ufstw_remove_sysfs(tw);
|
||||
kfree(tw);
|
||||
}
|
||||
}
|
||||
|
||||
INFO_MSG("end release");
|
||||
}
|
||||
|
||||
static void ufstw_reset_query_handling(struct ufstw_lu *tw)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(tw->ufsf->hba->dev);
|
||||
if (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->flush_enable) {
|
||||
ret = ufstw_set_lu_flag(tw, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN,
|
||||
&tw->flush_enable);
|
||||
if (ret)
|
||||
tw->flush_enable = false;
|
||||
}
|
||||
|
||||
if (tw->flush_during_hibern_enter) {
|
||||
ret = ufstw_set_lu_flag(tw,
|
||||
QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN,
|
||||
&tw->flush_during_hibern_enter);
|
||||
if (ret)
|
||||
tw->flush_during_hibern_enter = false;
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(tw->ufsf->hba->dev);
|
||||
}
|
||||
|
||||
void ufstw_reset_host(struct ufsf_feature *ufsf)
|
||||
{
|
||||
struct ufstw_lu *tw;
|
||||
int lun;
|
||||
|
||||
if (ufstw_is_not_present(ufsf))
|
||||
return;
|
||||
|
||||
ufstw_set_state(ufsf, TW_RESET);
|
||||
if (ufsf->tw_dev_info.tw_buf_type == TW_BUF_TYPE_SHARED) {
|
||||
tw = ufsf->tw_lup[0];
|
||||
|
||||
INFO_MSG("ufstw_lu[shared] cancel jobs");
|
||||
ufstw_cancel_lu_jobs(tw);
|
||||
} else {
|
||||
seq_scan_lu(lun) {
|
||||
tw = ufsf->tw_lup[lun];
|
||||
if (!tw)
|
||||
continue;
|
||||
|
||||
INFO_MSG("ufstw_lu[%d] cancel jobs", lun);
|
||||
ufstw_cancel_lu_jobs(tw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ufstw_reset(struct ufsf_feature *ufsf, bool resume)
|
||||
{
|
||||
struct ufstw_lu *tw;
|
||||
int lun;
|
||||
|
||||
INFO_MSG("ufstw reset start. reason: %s",
|
||||
resume ? "resume" : "reset");
|
||||
if (ufstw_get_state(ufsf) != TW_RESET) {
|
||||
ERR_MSG("tw_state error (%d)", ufstw_get_state(ufsf));
|
||||
return;
|
||||
}
|
||||
|
||||
ufstw_set_state(ufsf, TW_PRESENT);
|
||||
|
||||
if (ufsf->tw_dev_info.tw_buf_type == TW_BUF_TYPE_SHARED) {
|
||||
tw = ufsf->tw_lup[0];
|
||||
|
||||
INFO_MSG("ufstw_lu[shared] reset");
|
||||
ufstw_reset_query_handling(tw);
|
||||
} else {
|
||||
seq_scan_lu(lun) {
|
||||
tw = ufsf->tw_lup[lun];
|
||||
if (!tw)
|
||||
continue;
|
||||
|
||||
INFO_MSG("ufstw_lu[%d] reset", lun);
|
||||
ufstw_reset_query_handling(tw);
|
||||
}
|
||||
}
|
||||
|
||||
INFO_MSG("ufstw reset finish");
|
||||
}
|
||||
|
||||
#define ufstw_sysfs_attr_show_func(_query, _name, _IDN, hex) \
|
||||
static ssize_t ufstw_sysfs_show_##_name(struct ufstw_lu *tw, char *buf) \
|
||||
{ \
|
||||
int ret; \
|
||||
\
|
||||
if (ufstw_is_not_present(tw->ufsf)) \
|
||||
return -ENODEV; \
|
||||
\
|
||||
pm_runtime_get_sync(tw->ufsf->hba->dev); \
|
||||
ret = ufstw_read_lu_##_query(tw, _IDN, &tw->_name); \
|
||||
pm_runtime_put_sync(tw->ufsf->hba->dev); \
|
||||
if (ret) \
|
||||
return -ENODEV; \
|
||||
\
|
||||
INFO_MSG("read "#_query" "#_name" %u (0x%X)", \
|
||||
tw->_name, tw->_name); \
|
||||
if (hex) \
|
||||
return snprintf(buf, PAGE_SIZE, "0x%.2X\n", tw->_name); \
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", tw->_name); \
|
||||
}
|
||||
|
||||
#define ufstw_sysfs_attr_store_func(_name, _IDN) \
|
||||
static ssize_t ufstw_sysfs_store_##_name(struct ufstw_lu *tw, \
|
||||
const char *buf, \
|
||||
size_t count) \
|
||||
{ \
|
||||
unsigned long val; \
|
||||
ssize_t ret = count; \
|
||||
\
|
||||
if (kstrtoul(buf, 0, &val)) \
|
||||
return -EINVAL; \
|
||||
\
|
||||
if (!(val == 0 || val == 1)) \
|
||||
return -EINVAL; \
|
||||
\
|
||||
INFO_MSG("val %lu", val); \
|
||||
if (ufstw_is_not_present(tw->ufsf)) \
|
||||
return -ENODEV; \
|
||||
\
|
||||
pm_runtime_get_sync(tw->ufsf->hba->dev); \
|
||||
if (val) { \
|
||||
if (ufstw_set_lu_flag(tw, _IDN, &tw->_name)) \
|
||||
ret = -ENODEV; \
|
||||
} else { \
|
||||
if (ufstw_clear_lu_flag(tw, _IDN, &tw->_name)) \
|
||||
ret = -ENODEV; \
|
||||
} \
|
||||
pm_runtime_put_sync(tw->ufsf->hba->dev); \
|
||||
\
|
||||
INFO_MSG(#_name " query success"); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
ufstw_sysfs_attr_show_func(flag, tw_enable, QUERY_FLAG_IDN_TW_EN, 0);
|
||||
ufstw_sysfs_attr_store_func(tw_enable, QUERY_FLAG_IDN_TW_EN);
|
||||
ufstw_sysfs_attr_show_func(flag, flush_enable,
|
||||
QUERY_FLAG_IDN_TW_BUF_FLUSH_EN, 0);
|
||||
ufstw_sysfs_attr_store_func(flush_enable, QUERY_FLAG_IDN_TW_BUF_FLUSH_EN);
|
||||
ufstw_sysfs_attr_show_func(flag, flush_during_hibern_enter,
|
||||
QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN, 0);
|
||||
ufstw_sysfs_attr_store_func(flush_during_hibern_enter,
|
||||
QUERY_FLAG_IDN_TW_FLUSH_DURING_HIBERN);
|
||||
|
||||
ufstw_sysfs_attr_show_func(attr, flush_status,
|
||||
QUERY_ATTR_IDN_TW_FLUSH_STATUS, 0);
|
||||
ufstw_sysfs_attr_show_func(attr, available_buffer_size,
|
||||
QUERY_ATTR_IDN_TW_AVAIL_BUF_SIZE, 0);
|
||||
ufstw_sysfs_attr_show_func(attr, lifetime_est,
|
||||
QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST, 1);
|
||||
ufstw_sysfs_attr_show_func(attr, curr_buffer_size,
|
||||
QUERY_ATTR_IDN_TW_CURR_BUF_SIZE, 0);
|
||||
|
||||
#define ufstw_sysfs_attr_ro(_name) __ATTR(_name, 0444,\
|
||||
ufstw_sysfs_show_##_name, NULL)
|
||||
#define ufstw_sysfs_attr_rw(_name) __ATTR(_name, 0644,\
|
||||
ufstw_sysfs_show_##_name, \
|
||||
ufstw_sysfs_store_##_name)
|
||||
|
||||
static struct ufstw_sysfs_entry ufstw_sysfs_entries[] = {
|
||||
/* Flag */
|
||||
ufstw_sysfs_attr_rw(tw_enable),
|
||||
ufstw_sysfs_attr_rw(flush_enable),
|
||||
ufstw_sysfs_attr_rw(flush_during_hibern_enter),
|
||||
/* Attribute */
|
||||
ufstw_sysfs_attr_ro(flush_status),
|
||||
ufstw_sysfs_attr_ro(available_buffer_size),
|
||||
ufstw_sysfs_attr_ro(lifetime_est),
|
||||
ufstw_sysfs_attr_ro(curr_buffer_size),
|
||||
__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);
|
||||
if (!entry->show)
|
||||
return -EIO;
|
||||
|
||||
tw = container_of(kobj, struct ufstw_lu, kobj);
|
||||
|
||||
mutex_lock(&tw->sysfs_lock);
|
||||
error = entry->show(tw, page);
|
||||
mutex_unlock(&tw->sysfs_lock);
|
||||
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);
|
||||
if (!entry->store)
|
||||
return -EIO;
|
||||
|
||||
tw = container_of(kobj, struct ufstw_lu, kobj);
|
||||
|
||||
mutex_lock(&tw->sysfs_lock);
|
||||
error = entry->store(tw, page, length);
|
||||
mutex_unlock(&tw->sysfs_lock);
|
||||
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;
|
||||
char lun_str[20] = { 0 };
|
||||
|
||||
tw->sysfs_entries = ufstw_sysfs_entries;
|
||||
|
||||
kobject_init(&tw->kobj, &ufstw_ktype);
|
||||
mutex_init(&tw->sysfs_lock);
|
||||
|
||||
if (tw->lun == TW_LU_SHARED) {
|
||||
snprintf(lun_str, 6, "ufstw");
|
||||
INFO_MSG("ufstw creates sysfs ufstw-shared");
|
||||
} else {
|
||||
snprintf(lun_str, 10, "ufstw_lu%d", tw->lun);
|
||||
INFO_MSG("ufstw creates sysfs ufstw_lu%d", tw->lun);
|
||||
}
|
||||
|
||||
err = kobject_add(&tw->kobj, kobject_get(&dev->kobj), lun_str);
|
||||
if (!err) {
|
||||
for (entry = tw->sysfs_entries; entry->attr.name != NULL;
|
||||
entry++) {
|
||||
if (tw->lun == TW_LU_SHARED)
|
||||
INFO_MSG("ufstw-shared sysfs attr creates: %s",
|
||||
entry->attr.name);
|
||||
else
|
||||
INFO_MSG("ufstw_lu(%d) sysfs attr creates: %s",
|
||||
tw->lun, entry->attr.name);
|
||||
|
||||
err = sysfs_create_file(&tw->kobj, &entry->attr);
|
||||
if (err) {
|
||||
ERR_MSG("create entry(%s) failed",
|
||||
entry->attr.name);
|
||||
goto kobj_del;
|
||||
}
|
||||
}
|
||||
kobject_uevent(&tw->kobj, KOBJ_ADD);
|
||||
} else {
|
||||
ERR_MSG("kobject_add failed");
|
||||
}
|
||||
|
||||
return err;
|
||||
kobj_del:
|
||||
err = kobject_uevent(&tw->kobj, KOBJ_REMOVE);
|
||||
INFO_MSG("kobject removed (%d)", err);
|
||||
kobject_del(&tw->kobj);
|
||||
return -EINVAL;
|
||||
}
|
||||
126
drivers/scsi/ufs/ufstw.h
Executable file
126
drivers/scsi/ufs/ufstw.h
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Universal Flash Storage tw 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.
|
||||
*/
|
||||
|
||||
#ifndef _UFSTW_H_
|
||||
#define _UFSTW_H_
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/blktrace_api.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
|
||||
#include "../../../block/blk.h"
|
||||
|
||||
#define UFSTW_VER 0x0110
|
||||
#define UFSTW_DD_VER 0x010300
|
||||
#define UFSTW_DD_VER_POST UFSFEATURE_DD_VER_POST
|
||||
|
||||
#define UFSTW_LIFETIME_SECT 2097152 /* 1GB */
|
||||
#define UFSTW_MAX_LIFETIME_VALUE 0x0B
|
||||
#define MASK_UFSTW_LIFETIME_NOT_GUARANTEE 0x80
|
||||
#define UFS_FEATURE_SUPPORT_TW_BIT 0x100
|
||||
|
||||
#define TW_LU_SHARED -1
|
||||
|
||||
enum UFSTW_STATE {
|
||||
TW_NEED_INIT = 0,
|
||||
TW_PRESENT = 1,
|
||||
TW_RESET = -3,
|
||||
TW_FAILED = -4
|
||||
};
|
||||
|
||||
enum {
|
||||
TW_BUF_TYPE_LU = 0,
|
||||
TW_BUF_TYPE_SHARED,
|
||||
};
|
||||
|
||||
struct ufstw_dev_info {
|
||||
/* from Device Descriptor */
|
||||
u16 tw_ver;
|
||||
u32 tw_shared_buf_alloc_units;
|
||||
u8 tw_buf_no_reduct;
|
||||
u8 tw_buf_type;
|
||||
|
||||
/* from Geometry Descriptor */
|
||||
u8 tw_number_lu;
|
||||
};
|
||||
|
||||
struct ufstw_lu {
|
||||
struct ufsf_feature *ufsf;
|
||||
|
||||
int lun;
|
||||
|
||||
bool tw_enable;
|
||||
bool flush_enable;
|
||||
bool flush_during_hibern_enter;
|
||||
|
||||
unsigned int flush_status;
|
||||
unsigned int available_buffer_size;
|
||||
unsigned int curr_buffer_size;
|
||||
|
||||
unsigned int lifetime_est;
|
||||
spinlock_t lifetime_lock;
|
||||
u32 stat_write_sec;
|
||||
struct work_struct tw_lifetime_work;
|
||||
|
||||
/* for sysfs */
|
||||
struct kobject kobj;
|
||||
struct mutex sysfs_lock;
|
||||
struct ufstw_sysfs_entry *sysfs_entries;
|
||||
};
|
||||
|
||||
struct ufstw_sysfs_entry {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct ufstw_lu *tw, char *buf);
|
||||
ssize_t (*store)(struct ufstw_lu *tw, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
struct ufshcd_lrb;
|
||||
|
||||
int ufstw_get_state(struct ufsf_feature *ufsf);
|
||||
void ufstw_set_state(struct ufsf_feature *ufsf, int state);
|
||||
void ufstw_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf);
|
||||
void ufstw_get_geo_info(struct ufsf_feature *ufsf, u8 *geo_buf);
|
||||
void ufstw_alloc_lu(struct ufsf_feature *ufsf, int lun, u8 *lu_buf);
|
||||
|
||||
void ufstw_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp);
|
||||
void ufstw_init(struct ufsf_feature *ufsf);
|
||||
void ufstw_remove(struct ufsf_feature *ufsf);
|
||||
void ufstw_reset_host(struct ufsf_feature *ufsf);
|
||||
void ufstw_reset(struct ufsf_feature *ufsf, bool resume);
|
||||
#endif /* End of Header */
|
||||
|
|
@ -147,6 +147,9 @@ struct scsi_cmnd {
|
|||
ANDROID_KABI_RESERVE(2);
|
||||
ANDROID_KABI_RESERVE(3);
|
||||
ANDROID_KABI_RESERVE(4);
|
||||
#if defined(CONFIG_UFSFEATURE) && defined(CONFIG_UFSHPB)
|
||||
u8 requeue_cnt;
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in a new issue