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:
fuxm1 2021-02-24 16:27:03 +08:00 • committed by Xiaojun Ji
commit d4bbff0602
15 changed files with 10563 additions and 11 deletions

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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
View 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
View 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 */

View file

@ -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>");

View file

@ -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

File diff suppressed because it is too large Load diff

346
drivers/scsi/ufs/ufshpb.h Executable file
View 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 */

File diff suppressed because it is too large Load diff

View 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
View 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
View 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 */

View file

@ -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
};
/*