6db4831e98
Android 14
3241 lines
88 KiB
C
3241 lines
88 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2017-2018 Samsung Electronics Co., Ltd.
|
|
* Modified work Copyright (C) 2018, Google, Inc.
|
|
* Modified work Copyright (C) 2019 SK hynix
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/blkdev.h>
|
|
#include <scsi/scsi.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/blktrace_api.h>
|
|
|
|
#include "../../../block/blk.h"
|
|
|
|
#include "ufs.h"
|
|
#include "ufshcd.h"
|
|
#include "ufshpb_skh.h"
|
|
#include <asm/unaligned.h>
|
|
|
|
u32 skhpb_debug_mask = SKHPB_LOG_ERR | SKHPB_LOG_INFO;
|
|
//u32 skhpb_debug_mask = SKHPB_LOG_ERR | SKHPB_LOG_INFO | SKHPB_LOG_DEBUG | SKHPB_LOG_HEX;
|
|
int debug_map_req = SKHPB_MAP_RSP_DISABLE;
|
|
|
|
/*
|
|
* debug variables
|
|
*/
|
|
static int skhpb_alloc_mctx;
|
|
|
|
/*
|
|
* define global constants
|
|
*/
|
|
static int skhpb_sects_per_blk_shift;
|
|
static int skhpb_bits_per_dword_shift;
|
|
static int skhpb_bits_per_dword_mask;
|
|
|
|
static int skhpb_create_sysfs(struct ufs_hba *hba, struct skhpb_lu *hpb);
|
|
static int skhpb_check_lru_evict(struct skhpb_lu *hpb, struct skhpb_region *cb);
|
|
static void skhpb_error_handler(struct work_struct *work);
|
|
static void skhpb_evict_region(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb);
|
|
static void skhpb_purge_active_block(struct skhpb_lu *hpb);
|
|
static int skhpb_set_map_req(struct skhpb_lu *hpb,
|
|
int region, int subregion, struct skhpb_map_ctx *mctx,
|
|
struct skhpb_rsp_info *rsp_info,
|
|
enum SKHPB_BUFFER_MODE flag);
|
|
static int skhpb_rsp_map_cmd_req(struct skhpb_lu *hpb,
|
|
struct skhpb_rsp_info *rsp_info);
|
|
static void skhpb_map_loading_trigger(struct skhpb_lu *hpb,
|
|
bool only_pinned, bool do_work_handler);
|
|
|
|
static inline void skhpb_purge_active_page(struct skhpb_lu *hpb,
|
|
struct skhpb_subregion *cp, int state);
|
|
|
|
static void skhpb_hit_lru_info(struct skhpb_victim_select_info *lru_info,
|
|
struct skhpb_region *cb);
|
|
|
|
static inline void skhpb_get_bit_offset(
|
|
struct skhpb_lu *hpb, int subregion_offset,
|
|
int *dword, int *offset)
|
|
{
|
|
*dword = subregion_offset >> skhpb_bits_per_dword_shift;
|
|
*offset = subregion_offset & skhpb_bits_per_dword_mask;
|
|
}
|
|
|
|
/* called with hpb_lock (irq) */
|
|
static bool skhpb_ppn_dirty_check(struct skhpb_lu *hpb,
|
|
struct skhpb_subregion *cp, int subregion_offset)
|
|
{
|
|
bool is_dirty = false;
|
|
unsigned int bit_dword, bit_offset;
|
|
|
|
if (!cp->mctx->ppn_dirty)
|
|
return true;
|
|
|
|
skhpb_get_bit_offset(hpb, subregion_offset,
|
|
&bit_dword, &bit_offset);
|
|
is_dirty = cp->mctx->ppn_dirty[bit_dword] & (1 << bit_offset) ? true : false;
|
|
|
|
return is_dirty;
|
|
}
|
|
|
|
static void skhpb_ppn_prep(struct skhpb_lu *hpb,
|
|
struct ufshcd_lrb *lrbp, skhpb_t ppn,
|
|
unsigned int sector_len)
|
|
{
|
|
unsigned char *cmd = lrbp->cmd->cmnd;
|
|
|
|
if (hpb->hpb_ver < 0x0200) {
|
|
cmd[0] = READ_16;
|
|
cmd[1] = 0x0;
|
|
cmd[15] = 0x01; //NAND V5,V6 : Use Control field for HPB_READ
|
|
} else {
|
|
cmd[0] = READ_16;
|
|
cmd[1] = 0x5; // NAND V7 : Use reseved fields for HPB_READ
|
|
cmd[15] = 0x0;
|
|
}
|
|
put_unaligned(ppn, (u64 *)&cmd[6]);
|
|
cmd[14] = (u8)(sector_len >> skhpb_sects_per_blk_shift); //Transfer length
|
|
lrbp->cmd->cmd_len = MAX_CDB_SIZE;
|
|
//To verify the values within READ command
|
|
/* SKHPB_DRIVER_HEXDUMP("[HPB] HPB READ ", 16, 1, cmd, sizeof(cmd), 1); */
|
|
}
|
|
|
|
static inline void skhpb_set_dirty_bits(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb, struct skhpb_subregion *cp,
|
|
int dword, int offset, unsigned int count)
|
|
{
|
|
const unsigned long mask = ((1UL << count) - 1) & 0xffffffff;
|
|
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE)
|
|
return;
|
|
|
|
BUG_ON(!cp->mctx);
|
|
cp->mctx->ppn_dirty[dword] |= (mask << offset);
|
|
}
|
|
|
|
/* called with hpb_lock (irq) */
|
|
static void skhpb_set_dirty(struct skhpb_lu *hpb,
|
|
struct ufshcd_lrb *lrbp, int region,
|
|
int subregion, int subregion_offset)
|
|
{
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
int count;
|
|
int bit_count, bit_dword, bit_offset;
|
|
|
|
count = blk_rq_sectors(lrbp->cmd->request) >> skhpb_sects_per_blk_shift;
|
|
skhpb_get_bit_offset(hpb, subregion_offset,
|
|
&bit_dword, &bit_offset);
|
|
|
|
do {
|
|
bit_count = min(count, SKHPB_BITS_PER_DWORD - bit_offset);
|
|
|
|
cb = hpb->region_tbl + region;
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
skhpb_set_dirty_bits(hpb, cb, cp,
|
|
bit_dword, bit_offset, bit_count);
|
|
|
|
bit_offset = 0;
|
|
bit_dword++;
|
|
|
|
if (bit_dword == hpb->dwords_per_subregion) {
|
|
bit_dword = 0;
|
|
subregion++;
|
|
|
|
if (subregion == hpb->subregions_per_region) {
|
|
subregion = 0;
|
|
region++;
|
|
}
|
|
}
|
|
|
|
count -= bit_count;
|
|
} while (count);
|
|
}
|
|
|
|
#if 0
|
|
static inline bool skhpb_is_encrypted_lrbp(struct ufshcd_lrb *lrbp)
|
|
{
|
|
return (lrbp->utr_descriptor_ptr->header.dword_0 & UTRD_CRYPTO_ENABLE);
|
|
}
|
|
#endif
|
|
|
|
static inline enum SKHPB_CMD skhpb_get_cmd(struct ufshcd_lrb *lrbp)
|
|
{
|
|
unsigned char cmd = lrbp->cmd->cmnd[0];
|
|
|
|
if (cmd == READ_10 || cmd == READ_16)
|
|
return SKHPB_CMD_READ;
|
|
if (cmd == WRITE_10 || cmd == WRITE_16)
|
|
return SKHPB_CMD_WRITE;
|
|
if (cmd == UNMAP)
|
|
return SKHPB_CMD_DISCARD;
|
|
return SKHPB_CMD_OTHERS;
|
|
}
|
|
|
|
static inline void skhpb_get_pos_from_lpn(struct skhpb_lu *hpb,
|
|
unsigned int lpn, int *region, int *subregion, int *offset)
|
|
{
|
|
int region_offset;
|
|
|
|
*region = lpn >> hpb->entries_per_region_shift;
|
|
region_offset = lpn & hpb->entries_per_region_mask;
|
|
*subregion = region_offset >> hpb->entries_per_subregion_shift;
|
|
*offset = region_offset & hpb->entries_per_subregion_mask;
|
|
}
|
|
|
|
static inline bool skhpb_check_region_subregion_validity(struct skhpb_lu *hpb,
|
|
int region, int subregion)
|
|
{
|
|
struct skhpb_region *cb;
|
|
|
|
if (region >= hpb->regions_per_lu) {
|
|
SKHPB_DRIVER_E("[HCM] Out of REGION range - region[%d]:MAX[%d]\n",
|
|
region, hpb->regions_per_lu);
|
|
return false;
|
|
}
|
|
cb = hpb->region_tbl + region;
|
|
if (subregion >= cb->subregion_count) {
|
|
SKHPB_DRIVER_E("[HCM] Out of SUBREGION range - subregion[i]:MAX[%d]\n",
|
|
subregion, cb->subregion_count);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static inline skhpb_t skhpb_get_ppn(struct skhpb_map_ctx *mctx, int pos)
|
|
{
|
|
skhpb_t *ppn_table;
|
|
int index, offset;
|
|
|
|
index = pos / SKHPB_ENTREIS_PER_OS_PAGE;
|
|
offset = pos % SKHPB_ENTREIS_PER_OS_PAGE;
|
|
|
|
ppn_table = page_address(mctx->m_page[index]);
|
|
return ppn_table[offset];
|
|
}
|
|
|
|
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
static bool skhpb_subregion_dirty_check(struct skhpb_lu *hpb, struct skhpb_subregion *cp,
|
|
int subregion_offset, int reqBlkCnt)
|
|
{
|
|
unsigned int bit_dword, bit_offset;
|
|
unsigned int tmp;
|
|
int checkCnt;
|
|
|
|
if (!cp->mctx)
|
|
return true;
|
|
|
|
if (!cp->mctx->ppn_dirty)
|
|
return true;
|
|
|
|
skhpb_get_bit_offset(hpb, subregion_offset,
|
|
&bit_dword, &bit_offset);
|
|
|
|
while (true) {
|
|
checkCnt = SKHPB_BITS_PER_DWORD - bit_offset;
|
|
if (cp->mctx->ppn_dirty[bit_dword]) {
|
|
tmp = cp->mctx->ppn_dirty[bit_dword] << bit_offset;
|
|
if (SKHPB_BITS_PER_DWORD - reqBlkCnt > 0)
|
|
tmp = tmp >> (SKHPB_BITS_PER_DWORD - reqBlkCnt);
|
|
if (tmp)
|
|
return true;
|
|
}
|
|
reqBlkCnt -= checkCnt;
|
|
if (reqBlkCnt <= 0)
|
|
break;
|
|
bit_dword++;
|
|
if (bit_dword >= hpb->ppn_dirties_per_subregion)
|
|
break;
|
|
bit_offset = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool skhpb_lc_dirty_check(struct skhpb_lu *hpb, unsigned int lpn, unsigned int rq_sectors)
|
|
{
|
|
int reg;
|
|
int subReg;
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
unsigned long cur_lpn = lpn;
|
|
int subRegOffset;
|
|
int reqBlkCnt = rq_sectors >> skhpb_sects_per_blk_shift;
|
|
|
|
do {
|
|
skhpb_get_pos_from_lpn(hpb, cur_lpn, ®, &subReg, &subRegOffset);
|
|
if (!skhpb_check_region_subregion_validity(hpb, reg, subReg))
|
|
return true;
|
|
cb = hpb->region_tbl + reg;
|
|
cp = cb->subregion_tbl + subReg;
|
|
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE ||
|
|
cp->subregion_state != SKHPB_SUBREGION_CLEAN) {
|
|
atomic64_inc(&hpb->lc_reg_subreg_miss);
|
|
return true;
|
|
}
|
|
|
|
if (skhpb_subregion_dirty_check(hpb, cp, subRegOffset, reqBlkCnt)) {
|
|
//SKHPB_DRIVER_D("[NORMAL READ] DIRTY: Region(%d), SubRegion(%d) \n", reg, subReg);
|
|
atomic64_inc(&hpb->lc_entry_dirty_miss);
|
|
return true;
|
|
}
|
|
|
|
if (hpb->entries_per_subregion < subRegOffset + reqBlkCnt) {
|
|
reqBlkCnt -= (hpb->entries_per_subregion - subRegOffset);
|
|
} else {
|
|
reqBlkCnt = 0;
|
|
}
|
|
|
|
cur_lpn += reqBlkCnt;
|
|
} while (reqBlkCnt);
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void skhpb_prep_fn(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
|
|
{
|
|
struct skhpb_lu *hpb;
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
unsigned int lpn;
|
|
skhpb_t ppn = 0;
|
|
int region, subregion, subregion_offset;
|
|
const struct request *rq;
|
|
unsigned long long rq_pos;
|
|
unsigned int rq_sectors;
|
|
unsigned char cmd;
|
|
unsigned long flags;
|
|
|
|
/* WKLU could not be HPB-LU */
|
|
if (lrbp->lun >= UFS_UPIU_MAX_GENERAL_LUN)
|
|
return;
|
|
|
|
hpb = hba->skhpb_lup[lrbp->lun];
|
|
if (!hpb || !hpb->lu_hpb_enable)
|
|
return;
|
|
|
|
if (hpb->force_hpb_read_disable)
|
|
return;
|
|
|
|
cmd = skhpb_get_cmd(lrbp);
|
|
if (cmd == SKHPB_CMD_OTHERS)
|
|
return;
|
|
if (blk_rq_is_scsi(lrbp->cmd->request))
|
|
return;
|
|
/*
|
|
* TODO: check if ICE is not supported or not.
|
|
*
|
|
* if (cmd==SKHPB_CMD_READ && skhpb_is_encrypted_lrbp(lrbp))
|
|
* return;
|
|
*/
|
|
rq = lrbp->cmd->request;
|
|
rq_pos = blk_rq_pos(rq);
|
|
rq_sectors = blk_rq_sectors(rq);
|
|
|
|
lpn = rq_pos / SKHPB_SECTORS_PER_BLOCK;
|
|
skhpb_get_pos_from_lpn(
|
|
hpb, lpn, ®ion, &subregion, &subregion_offset);
|
|
if (!skhpb_check_region_subregion_validity(hpb, region, subregion))
|
|
return;
|
|
cb = hpb->region_tbl + region;
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
if (cmd == SKHPB_CMD_WRITE ||
|
|
cmd == SKHPB_CMD_DISCARD) {
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE) {
|
|
return;
|
|
}
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_set_dirty(hpb, lrbp, region, subregion,
|
|
subregion_offset);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return;
|
|
}
|
|
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
if (((rq_sectors & (SKHPB_SECTORS_PER_BLOCK - 1)) != 0) ||
|
|
rq_sectors >
|
|
(SKHPB_READ_LARGE_CHUNK_MAX_BLOCK_COUNT << skhpb_sects_per_blk_shift)) {
|
|
#else
|
|
if (rq_sectors != SKHPB_SECTORS_PER_BLOCK) {
|
|
#endif
|
|
atomic64_inc(&hpb->size_miss);
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE) {
|
|
atomic64_inc(&hpb->region_miss);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return;
|
|
} else if (cp->subregion_state != SKHPB_SUBREGION_CLEAN) {
|
|
atomic64_inc(&hpb->subregion_miss);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return;
|
|
}
|
|
if (rq_sectors <= SKHPB_SECTORS_PER_BLOCK) {
|
|
if (skhpb_ppn_dirty_check(hpb, cp, subregion_offset)) {
|
|
atomic64_inc(&hpb->entry_dirty_miss);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return;
|
|
}
|
|
}
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
else {
|
|
if (skhpb_lc_dirty_check(hpb, lpn, rq_sectors)) {
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return;
|
|
}
|
|
atomic64_inc(&hpb->lc_hit);
|
|
}
|
|
#endif
|
|
ppn = skhpb_get_ppn(cp->mctx, subregion_offset);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
|
|
//SKHPB_DRIVER_D("XXX ufs ppn %016llx, lba %u\n", (unsigned long long)ppn, lpn);
|
|
skhpb_ppn_prep(hpb, lrbp, ppn, rq_sectors);
|
|
atomic64_inc(&hpb->hit);
|
|
return;
|
|
}
|
|
|
|
static int skhpb_clean_dirty_bitmap(
|
|
struct skhpb_lu *hpb, struct skhpb_subregion *cp)
|
|
{
|
|
struct skhpb_region *cb;
|
|
|
|
cb = hpb->region_tbl + cp->region;
|
|
|
|
/* if mctx is null, active block had been evicted out */
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE || !cp->mctx) {
|
|
SKHPB_DRIVER_D("%d - %d error already evicted\n",
|
|
cp->region, cp->subregion);
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(cp->mctx->ppn_dirty, 0x00,
|
|
hpb->entries_per_subregion / BITS_PER_BYTE);
|
|
return 0;
|
|
}
|
|
|
|
static void skhpb_clean_active_subregion(
|
|
struct skhpb_lu *hpb, struct skhpb_subregion *cp)
|
|
{
|
|
struct skhpb_region *cb;
|
|
|
|
cb = hpb->region_tbl + cp->region;
|
|
|
|
/* if mctx is null, active block had been evicted out */
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE || !cp->mctx) {
|
|
SKHPB_DRIVER_D("%d - %d clean already evicted\n",
|
|
cp->region, cp->subregion);
|
|
return;
|
|
}
|
|
cp->subregion_state = SKHPB_SUBREGION_CLEAN;
|
|
}
|
|
|
|
static void skhpb_error_active_subregion(
|
|
struct skhpb_lu *hpb, struct skhpb_subregion *cp)
|
|
{
|
|
struct skhpb_region *cb;
|
|
|
|
cb = hpb->region_tbl + cp->region;
|
|
|
|
/* if mctx is null, active block had been evicted out */
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE || !cp->mctx) {
|
|
SKHPB_DRIVER_E("%d - %d evicted\n", cp->region, cp->subregion);
|
|
return;
|
|
}
|
|
cp->subregion_state = SKHPB_SUBREGION_DIRTY;
|
|
}
|
|
|
|
static void skhpb_map_compl_process(struct skhpb_lu *hpb,
|
|
struct skhpb_map_req *map_req)
|
|
{
|
|
unsigned long flags;
|
|
|
|
SKHPB_MAP_REQ_TIME(map_req, map_req->RSP_end, 1);
|
|
SKHPB_DRIVER_D("SKHPB RB COMPL BUFFER %d - %d\n", map_req->region, map_req->subregion);
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_clean_active_subregion(hpb,
|
|
hpb->region_tbl[map_req->region].subregion_tbl +
|
|
map_req->subregion);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
}
|
|
|
|
static void skhpb_map_inactive_compl_process(struct skhpb_lu *hpb,
|
|
struct skhpb_map_req *map_req)
|
|
{
|
|
SKHPB_DRIVER_D("SKHPB WB COMPL BUFFER %d\n", map_req->region);
|
|
SKHPB_MAP_REQ_TIME(map_req, map_req->RSP_end, 1);
|
|
}
|
|
|
|
/*
|
|
* Must held rsp_list_lock before enter this function
|
|
*/
|
|
static struct skhpb_rsp_info *skhpb_get_req_info(struct skhpb_lu *hpb)
|
|
{
|
|
struct skhpb_rsp_info *rsp_info =
|
|
list_first_entry_or_null(&hpb->lh_rsp_info_free,
|
|
struct skhpb_rsp_info,
|
|
list_rsp_info);
|
|
if (!rsp_info) {
|
|
SKHPB_DRIVER_E("there is no rsp_info");
|
|
return NULL;
|
|
}
|
|
list_del(&rsp_info->list_rsp_info);
|
|
memset(rsp_info, 0x00, sizeof(struct skhpb_rsp_info));
|
|
|
|
INIT_LIST_HEAD(&rsp_info->list_rsp_info);
|
|
|
|
return rsp_info;
|
|
}
|
|
|
|
static void skhpb_map_req_compl_fn(struct request *req, blk_status_t error)
|
|
{
|
|
struct skhpb_map_req *map_req = req->end_io_data;
|
|
struct ufs_hba *hba;
|
|
struct skhpb_lu *hpb;
|
|
struct scsi_sense_hdr sshdr;
|
|
struct skhpb_region *cb;
|
|
struct scsi_request *scsireq = scsi_req(req);
|
|
unsigned long flags;
|
|
|
|
/* shut up "bio leak" warning */
|
|
memcpy(map_req->sense, scsireq->sense, SCSI_SENSE_BUFFERSIZE);
|
|
req->bio = NULL;
|
|
__blk_put_request(req->q, req);
|
|
|
|
hpb = map_req->hpb;
|
|
hba = hpb->hba;
|
|
cb = hpb->region_tbl + map_req->region;
|
|
|
|
if (hba->skhpb_state != SKHPB_PRESENT)
|
|
goto free_map_req;
|
|
|
|
if (!error) {
|
|
skhpb_map_compl_process(hpb, map_req);
|
|
goto free_map_req;
|
|
}
|
|
|
|
SKHPB_DRIVER_E("error number %d ( %d - %d )\n",
|
|
error, map_req->region, map_req->subregion);
|
|
scsi_normalize_sense(map_req->sense,
|
|
SCSI_SENSE_BUFFERSIZE, &sshdr);
|
|
SKHPB_DRIVER_E("code %x sense_key %x asc %x ascq %x\n",
|
|
sshdr.response_code,
|
|
sshdr.sense_key, sshdr.asc, sshdr.ascq);
|
|
SKHPB_DRIVER_E("byte4 %x byte5 %x byte6 %x additional_len %x\n",
|
|
sshdr.byte4, sshdr.byte5,
|
|
sshdr.byte6, sshdr.additional_length);
|
|
atomic64_inc(&hpb->rb_fail);
|
|
|
|
if (sshdr.sense_key == ILLEGAL_REQUEST) {
|
|
if (sshdr.asc == 0x00 && sshdr.ascq == 0x16) {
|
|
/* OPERATION IN PROGRESS */
|
|
SKHPB_DRIVER_E("retry rb %d - %d",
|
|
map_req->region, map_req->subregion);
|
|
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
INIT_LIST_HEAD(&map_req->list_map_req);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_retry);
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
|
|
schedule_delayed_work(&hpb->skhpb_map_req_retry_work,
|
|
msecs_to_jiffies(5000));
|
|
return;
|
|
}
|
|
}
|
|
// Only change subregion status at here.
|
|
// Do not put map_ctx, it will re-use when it is activated again.
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_error_active_subregion(hpb, cb->subregion_tbl + map_req->subregion);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
free_map_req:
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
INIT_LIST_HEAD(&map_req->list_map_req);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_free);
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
atomic64_dec(&hpb->alloc_map_req_cnt);
|
|
}
|
|
|
|
static void skhpb_map_inactive_req_compl_fn(
|
|
struct request *req, blk_status_t error)
|
|
{
|
|
struct skhpb_map_req *map_req = req->end_io_data;
|
|
struct ufs_hba *hba;
|
|
struct skhpb_lu *hpb;
|
|
struct scsi_sense_hdr sshdr;
|
|
struct skhpb_region *cb;
|
|
struct scsi_request *scsireq = scsi_req(req);
|
|
unsigned long flags;
|
|
|
|
/* shut up "bio leak" warning */
|
|
memcpy(map_req->sense, scsireq->sense, SCSI_SENSE_BUFFERSIZE);
|
|
req->bio = NULL;
|
|
__blk_put_request(req->q, req);
|
|
|
|
hpb = map_req->hpb;
|
|
hba = hpb->hba;
|
|
cb = hpb->region_tbl + map_req->region;
|
|
|
|
if (hba->skhpb_state != SKHPB_PRESENT)
|
|
goto free_map_req;
|
|
|
|
if (!error) {
|
|
skhpb_map_inactive_compl_process(hpb, map_req);
|
|
goto free_map_req;
|
|
}
|
|
|
|
SKHPB_DRIVER_E("error number %d ( %d - %d )",
|
|
error, map_req->region, map_req->subregion);
|
|
scsi_normalize_sense(map_req->sense, SCSI_SENSE_BUFFERSIZE,
|
|
&sshdr);
|
|
SKHPB_DRIVER_E("code %x sense_key %x asc %x ascq %x",
|
|
sshdr.response_code,
|
|
sshdr.sense_key, sshdr.asc, sshdr.ascq);
|
|
SKHPB_DRIVER_E("byte4 %x byte5 %x byte6 %x additional_len %x",
|
|
sshdr.byte4, sshdr.byte5,
|
|
sshdr.byte6, sshdr.additional_length);
|
|
atomic64_inc(&hpb->rb_fail);
|
|
|
|
if (sshdr.sense_key == ILLEGAL_REQUEST) {
|
|
if (cb->is_pinned) {
|
|
SKHPB_DRIVER_E("WRITE_BUFFER is not allowed on pinned area: region#%d",
|
|
cb->region);
|
|
} else {
|
|
if (sshdr.asc == 0x00 && sshdr.ascq == 0x16) {
|
|
/* OPERATION IN PROGRESS */
|
|
SKHPB_DRIVER_E("retry wb %d", map_req->region);
|
|
|
|
spin_lock(&hpb->map_list_lock);
|
|
INIT_LIST_HEAD(&map_req->list_map_req);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_retry);
|
|
spin_unlock(&hpb->map_list_lock);
|
|
|
|
schedule_delayed_work(&hpb->skhpb_map_req_retry_work,
|
|
msecs_to_jiffies(5000));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// Only change subregion status at here.
|
|
// Do not put map_ctx, it will re-use when it is activated again.
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_error_active_subregion(hpb, cb->subregion_tbl + map_req->subregion);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
free_map_req:
|
|
spin_lock(&hpb->map_list_lock);
|
|
INIT_LIST_HEAD(&map_req->list_map_req);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_free);
|
|
spin_unlock(&hpb->map_list_lock);
|
|
atomic64_dec(&hpb->alloc_map_req_cnt);
|
|
}
|
|
|
|
static int skhpb_execute_req_dev_ctx(struct skhpb_lu *hpb,
|
|
unsigned char *cmd, void *buf, int length)
|
|
{
|
|
unsigned long flags;
|
|
struct scsi_sense_hdr sshdr;
|
|
struct scsi_device *sdp;
|
|
struct ufs_hba *hba = hpb->hba;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(hba->host->host_lock, flags);
|
|
sdp = hba->sdev_ufs_lu[hpb->lun];
|
|
if (sdp) {
|
|
ret = scsi_device_get(sdp);
|
|
if (!ret && !scsi_device_online(sdp)) {
|
|
ret = -ENODEV;
|
|
scsi_device_put(sdp);
|
|
} else if (!ret) {
|
|
hba->issue_ioctl = true;
|
|
}
|
|
} else {
|
|
ret = -ENODEV;
|
|
}
|
|
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = scsi_execute_req(sdp, cmd, DMA_FROM_DEVICE,
|
|
buf, length, &sshdr,
|
|
msecs_to_jiffies(30000), 3, NULL);
|
|
spin_lock_irqsave(hba->host->host_lock, flags);
|
|
hba->issue_ioctl = false;
|
|
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
|
scsi_device_put(sdp);
|
|
return ret;
|
|
}
|
|
|
|
static inline void skhpb_set_read_dev_ctx_cmd(unsigned char *cmd, int lba,
|
|
int length)
|
|
{
|
|
cmd[0] = READ_10;
|
|
cmd[1] = 0x02;
|
|
cmd[2] = SKHPB_GET_BYTE_3(lba);
|
|
cmd[3] = SKHPB_GET_BYTE_2(lba);
|
|
cmd[4] = SKHPB_GET_BYTE_1(lba);
|
|
cmd[5] = SKHPB_GET_BYTE_0(lba);
|
|
cmd[6] = SKHPB_GET_BYTE_2(length);
|
|
cmd[7] = SKHPB_GET_BYTE_1(length);
|
|
cmd[8] = SKHPB_GET_BYTE_0(length);
|
|
}
|
|
|
|
int skhpb_issue_req_dev_ctx(struct skhpb_lu *hpb, unsigned char *buf,
|
|
int buf_length)
|
|
{
|
|
unsigned char cmd[10] = { 0 };
|
|
int cmd_len = buf_length >> PAGE_SHIFT;
|
|
int ret = 0;
|
|
|
|
skhpb_set_read_dev_ctx_cmd(cmd, 0x48504230, cmd_len);
|
|
|
|
ret = skhpb_execute_req_dev_ctx(hpb, cmd, buf, buf_length);
|
|
if (ret < 0)
|
|
SKHPB_DRIVER_E("failed with err %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static inline void skhpb_set_write_buf_cmd(unsigned char *cmd, int region)
|
|
{
|
|
cmd[0] = SKHPB_WRITE_BUFFER;
|
|
cmd[1] = 0x01;
|
|
cmd[2] = SKHPB_GET_BYTE_1(region);
|
|
cmd[3] = SKHPB_GET_BYTE_0(region);
|
|
cmd[4] = 0x00;
|
|
cmd[5] = 0x00;
|
|
cmd[6] = 0x00;
|
|
cmd[7] = 0x00;
|
|
cmd[8] = 0x00;
|
|
cmd[9] = 0x00;
|
|
|
|
//To verify the values within WRITE_BUFFER command
|
|
SKHPB_DRIVER_HEXDUMP("[HPB] WRITE BUFFER ", 16, 1, cmd, 10, 1);
|
|
}
|
|
|
|
static inline void skhpb_set_read_buf_cmd(unsigned char *cmd,
|
|
int region, int subregion, int subregion_mem_size)
|
|
{
|
|
cmd[0] = SKHPB_READ_BUFFER;
|
|
cmd[1] = 0x01;
|
|
cmd[2] = SKHPB_GET_BYTE_1(region);
|
|
cmd[3] = SKHPB_GET_BYTE_0(region);
|
|
cmd[4] = SKHPB_GET_BYTE_1(subregion);
|
|
cmd[5] = SKHPB_GET_BYTE_0(subregion);
|
|
cmd[6] = SKHPB_GET_BYTE_2(subregion_mem_size);
|
|
cmd[7] = SKHPB_GET_BYTE_1(subregion_mem_size);
|
|
cmd[8] = SKHPB_GET_BYTE_0(subregion_mem_size);
|
|
cmd[9] = 0x00;
|
|
|
|
//To verify the values within READ_BUFFER command
|
|
SKHPB_DRIVER_HEXDUMP("[HPB] READ BUFFER ", 16, 1, cmd, 10, 1);
|
|
}
|
|
|
|
static int skhpb_add_bio_page(struct skhpb_lu *hpb,
|
|
struct request_queue *q, struct bio *bio, struct bio_vec *bvec,
|
|
struct skhpb_map_ctx *mctx)
|
|
{
|
|
struct page *page = NULL;
|
|
int i, ret;
|
|
|
|
bio_init(bio, bvec, hpb->mpages_per_subregion);
|
|
|
|
for (i = 0; i < hpb->mpages_per_subregion; i++) {
|
|
page = mctx->m_page[i];
|
|
if (!page)
|
|
return -ENOMEM;
|
|
|
|
ret = bio_add_pc_page(q, bio, page, hpb->mpage_bytes, 0);
|
|
if (ret != hpb->mpage_bytes) {
|
|
SKHPB_DRIVER_E("error ret %d\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int skhpb_map_req_issue(
|
|
struct skhpb_lu *hpb, struct skhpb_map_req *map_req)
|
|
{
|
|
struct request_queue *q = hpb->hba->sdev_ufs_lu[hpb->lun]->request_queue;
|
|
struct request *req;
|
|
struct scsi_request *scsireq;
|
|
unsigned char cmd[10] = { 0 };
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
if (map_req->rwbuffer_flag == W_BUFFER)
|
|
skhpb_set_write_buf_cmd(cmd, map_req->region);
|
|
else
|
|
skhpb_set_read_buf_cmd(cmd, map_req->region, map_req->subregion,
|
|
map_req->subregion_mem_size);
|
|
if (map_req->rwbuffer_flag == W_BUFFER)
|
|
req = blk_get_request(q, REQ_OP_SCSI_OUT, BLK_MQ_REQ_PREEMPT);
|
|
else
|
|
req = blk_get_request(q, REQ_OP_SCSI_IN, BLK_MQ_REQ_PREEMPT);
|
|
if (IS_ERR(req)) {
|
|
int rv = PTR_ERR(req);
|
|
|
|
if (map_req->rwbuffer_flag == W_BUFFER)
|
|
SKHPB_DRIVER_E("blk_get_request errno %d, \
|
|
retry #%d, WRITE BUFFER %d\n",
|
|
rv, map_req->retry_cnt, map_req->region);
|
|
else
|
|
SKHPB_DRIVER_E("blk_get_request errno %d, \
|
|
retry #%d, READ BUFFER %d:%d\n",
|
|
rv, map_req->retry_cnt,
|
|
map_req->region, map_req->subregion);
|
|
|
|
if (map_req->retry_cnt == 10) {
|
|
/* give up */
|
|
return rv;
|
|
}
|
|
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_retry);
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
|
|
schedule_delayed_work(&hpb->skhpb_map_req_retry_work, msecs_to_jiffies(10));
|
|
|
|
return 0;
|
|
}
|
|
scsireq = scsi_req(req);
|
|
|
|
scsireq->cmd_len = COMMAND_SIZE(cmd[0]);
|
|
BUG_ON(scsireq->cmd_len > sizeof(scsireq->__cmd));
|
|
scsireq->cmd = scsireq->__cmd;
|
|
memcpy(scsireq->cmd, cmd, scsireq->cmd_len);
|
|
|
|
req->rq_flags |= RQF_QUIET | RQF_PREEMPT;
|
|
req->timeout = msecs_to_jiffies(30000);
|
|
req->end_io_data = map_req;
|
|
|
|
if (map_req->rwbuffer_flag == R_BUFFER) {
|
|
ret = skhpb_add_bio_page(
|
|
hpb, q, &map_req->bio, map_req->bvec, map_req->mctx);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("skhpb_add_bio_page_error %d\n", ret);
|
|
goto out_put_request;
|
|
}
|
|
map_req->pbio = &map_req->bio;
|
|
blk_rq_append_bio(req, &map_req->pbio);
|
|
}
|
|
SKHPB_DRIVER_D("issue map_request: %d - %d\n",
|
|
map_req->region, map_req->subregion);
|
|
|
|
SKHPB_MAP_REQ_TIME(map_req, map_req->RSP_issue, 0);
|
|
if (hpb->hpb_control_mode == HOST_CTRL_MODE) {
|
|
if (map_req->rwbuffer_flag == W_BUFFER)
|
|
blk_execute_rq_nowait(
|
|
q, NULL, req, 0, skhpb_map_inactive_req_compl_fn);
|
|
else
|
|
blk_execute_rq_nowait(q, NULL, req, 0, skhpb_map_req_compl_fn);
|
|
} else
|
|
blk_execute_rq_nowait(q, NULL, req, 1, skhpb_map_req_compl_fn);
|
|
|
|
if (map_req->rwbuffer_flag == W_BUFFER)
|
|
atomic64_inc(&hpb->w_map_req_cnt);
|
|
else
|
|
atomic64_inc(&hpb->map_req_cnt);
|
|
|
|
return 0;
|
|
|
|
out_put_request:
|
|
blk_put_request(req);
|
|
return ret;
|
|
}
|
|
|
|
static int skhpb_set_map_req(struct skhpb_lu *hpb,
|
|
int region, int subregion, struct skhpb_map_ctx *mctx,
|
|
struct skhpb_rsp_info *rsp_info,
|
|
enum SKHPB_BUFFER_MODE flag)
|
|
{
|
|
bool last = hpb->region_tbl[region].subregion_tbl[subregion].last;
|
|
struct skhpb_map_req *map_req;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
map_req = list_first_entry_or_null(&hpb->lh_map_req_free,
|
|
struct skhpb_map_req,
|
|
list_map_req);
|
|
if (!map_req) {
|
|
SKHPB_DRIVER_D("There is no map_req\n");
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
list_del(&map_req->list_map_req);
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
atomic64_inc(&hpb->alloc_map_req_cnt);
|
|
|
|
memset(map_req, 0x00, sizeof(struct skhpb_map_req));
|
|
|
|
map_req->hpb = hpb;
|
|
map_req->region = region;
|
|
map_req->subregion = subregion;
|
|
map_req->subregion_mem_size =
|
|
last ? hpb->last_subregion_mem_size : hpb->subregion_mem_size;
|
|
map_req->mctx = mctx;
|
|
map_req->lun = hpb->lun;
|
|
map_req->RSP_start = rsp_info->RSP_start;
|
|
if (flag == W_BUFFER) {
|
|
map_req->rwbuffer_flag = W_BUFFER;
|
|
} else
|
|
map_req->rwbuffer_flag = R_BUFFER;
|
|
|
|
if (skhpb_map_req_issue(hpb, map_req)) {
|
|
SKHPB_DRIVER_E("issue Failed!!!\n");
|
|
return -ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct skhpb_map_ctx *skhpb_get_map_ctx(struct skhpb_lu *hpb)
|
|
{
|
|
struct skhpb_map_ctx *mctx;
|
|
|
|
mctx = list_first_entry_or_null(&hpb->lh_map_ctx,
|
|
struct skhpb_map_ctx, list_table);
|
|
if (mctx) {
|
|
list_del_init(&mctx->list_table);
|
|
hpb->debug_free_table--;
|
|
return mctx;
|
|
}
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
static inline void skhpb_add_lru_info(struct skhpb_victim_select_info *lru_info,
|
|
struct skhpb_region *cb)
|
|
{
|
|
cb->region_state = SKHPB_REGION_ACTIVE;
|
|
list_add_tail(&cb->list_region, &lru_info->lru);
|
|
atomic64_inc(&lru_info->active_count);
|
|
}
|
|
|
|
static inline int skhpb_add_region(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb)
|
|
{
|
|
struct skhpb_victim_select_info *lru_info;
|
|
int subregion;
|
|
int err = 0;
|
|
|
|
lru_info = &hpb->lru_info;
|
|
|
|
//SKHPB_DRIVER_D("E->active region: %d", cb->region);
|
|
|
|
for (subregion = 0; subregion < cb->subregion_count; subregion++) {
|
|
struct skhpb_subregion *cp;
|
|
|
|
cp = cb->subregion_tbl + subregion;
|
|
cp->mctx = skhpb_get_map_ctx(hpb);
|
|
if (IS_ERR(cp->mctx)) {
|
|
err = PTR_ERR(cp->mctx);
|
|
goto out;
|
|
}
|
|
cp->subregion_state = SKHPB_SUBREGION_DIRTY;
|
|
}
|
|
if (!cb->is_pinned)
|
|
skhpb_add_lru_info(lru_info, cb);
|
|
|
|
atomic64_inc(&hpb->region_add);
|
|
out:
|
|
if (err)
|
|
SKHPB_DRIVER_E("get mctx failed. err %d subregion %d free_table %d\n",
|
|
err, subregion, hpb->debug_free_table);
|
|
return err;
|
|
}
|
|
|
|
static inline void skhpb_put_map_ctx(
|
|
struct skhpb_lu *hpb, struct skhpb_map_ctx *mctx)
|
|
{
|
|
list_add(&mctx->list_table, &hpb->lh_map_ctx);
|
|
hpb->debug_free_table++;
|
|
}
|
|
|
|
static inline void skhpb_purge_active_page(struct skhpb_lu *hpb,
|
|
struct skhpb_subregion *cp, int state)
|
|
{
|
|
if (state == SKHPB_SUBREGION_UNUSED) {
|
|
skhpb_put_map_ctx(hpb, cp->mctx);
|
|
cp->mctx = NULL;
|
|
}
|
|
cp->subregion_state = state;
|
|
}
|
|
|
|
static inline void skhpb_cleanup_lru_info(
|
|
struct skhpb_victim_select_info *lru_info,
|
|
struct skhpb_region *cb)
|
|
{
|
|
list_del_init(&cb->list_region);
|
|
cb->region_state = SKHPB_REGION_INACTIVE;
|
|
cb->hit_count = 0;
|
|
atomic64_dec(&lru_info->active_count);
|
|
}
|
|
|
|
static inline void skhpb_evict_region(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb)
|
|
{
|
|
struct skhpb_victim_select_info *lru_info;
|
|
struct skhpb_subregion *cp;
|
|
int subregion;
|
|
|
|
// If the maximum value is exceeded at the time of region addition,
|
|
// it may have already been processed.
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE) {
|
|
SKHPB_DRIVER_D("Region:%d was already inactivated.\n",
|
|
cb->region);
|
|
return;
|
|
}
|
|
lru_info = &hpb->lru_info;
|
|
|
|
//SKHPB_DRIVER_D("C->EVICT region: %d\n", cb->region);
|
|
|
|
skhpb_cleanup_lru_info(lru_info, cb);
|
|
atomic64_inc(&hpb->region_evict);
|
|
for (subregion = 0; subregion < cb->subregion_count; subregion++) {
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
skhpb_purge_active_page(hpb, cp, SKHPB_SUBREGION_UNUSED);
|
|
}
|
|
}
|
|
|
|
static void skhpb_hit_lru_info(struct skhpb_victim_select_info *lru_info,
|
|
struct skhpb_region *cb)
|
|
{
|
|
switch (lru_info->selection_type) {
|
|
case TYPE_LRU:
|
|
list_move_tail(&cb->list_region, &lru_info->lru);
|
|
break;
|
|
case TYPE_LFU:
|
|
if (cb->hit_count != 0xffffffff)
|
|
cb->hit_count++;
|
|
|
|
list_move_tail(&cb->list_region, &lru_info->lru);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct skhpb_region *skhpb_victim_lru_info(
|
|
struct skhpb_victim_select_info *lru_info)
|
|
{
|
|
struct skhpb_region *cb;
|
|
struct skhpb_region *victim_cb = NULL;
|
|
u32 hit_count = 0xffffffff;
|
|
|
|
switch (lru_info->selection_type) {
|
|
case TYPE_LRU:
|
|
victim_cb = list_first_entry(&lru_info->lru,
|
|
struct skhpb_region, list_region);
|
|
break;
|
|
case TYPE_LFU:
|
|
list_for_each_entry(cb, &lru_info->lru, list_region) {
|
|
if (hit_count > cb->hit_count) {
|
|
hit_count = cb->hit_count;
|
|
victim_cb = cb;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return victim_cb;
|
|
}
|
|
|
|
static int skhpb_check_lru_evict(struct skhpb_lu *hpb, struct skhpb_region *cb)
|
|
{
|
|
struct skhpb_victim_select_info *lru_info = &hpb->lru_info;
|
|
struct skhpb_region *victim_cb;
|
|
unsigned long flags;
|
|
|
|
if (cb->is_pinned)
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
if (!list_empty(&cb->list_region)) {
|
|
skhpb_hit_lru_info(lru_info, cb);
|
|
goto out;
|
|
}
|
|
|
|
if (cb->region_state != SKHPB_REGION_INACTIVE)
|
|
goto out;
|
|
|
|
if (atomic64_read(&lru_info->active_count) == lru_info->max_lru_active_count) {
|
|
|
|
victim_cb = skhpb_victim_lru_info(lru_info);
|
|
|
|
if (!victim_cb) {
|
|
SKHPB_DRIVER_E("SKHPB victim_cb is NULL\n");
|
|
goto unlock_error;
|
|
}
|
|
SKHPB_DRIVER_D("max lru case. victim : %d\n", victim_cb->region);
|
|
skhpb_evict_region(hpb, victim_cb);
|
|
}
|
|
if (skhpb_add_region(hpb, cb)) {
|
|
SKHPB_DRIVER_E("SKHPB memory allocation failed\n");
|
|
goto unlock_error;
|
|
}
|
|
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return 0;
|
|
unlock_error:
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int skhpb_evict_load_region(struct skhpb_lu *hpb,
|
|
struct skhpb_rsp_info *rsp_info)
|
|
{
|
|
struct skhpb_region *cb;
|
|
int region, iter;
|
|
unsigned long flags;
|
|
|
|
for (iter = 0; iter < rsp_info->inactive_cnt; iter++) {
|
|
region = rsp_info->inactive_list.region[iter];
|
|
cb = hpb->region_tbl + region;
|
|
|
|
if (cb->is_pinned) {
|
|
/*
|
|
* Pinned active-block should not drop-out.
|
|
* But if so, it would treat error as critical,
|
|
* and it will run skhpb_eh_work
|
|
*/
|
|
SKHPB_DRIVER_E("SKHPB pinned active-block drop-out error\n");
|
|
return -ENOMEM;
|
|
}
|
|
if (list_empty(&cb->list_region))
|
|
continue;
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_evict_region(hpb, cb);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
}
|
|
|
|
for (iter = 0; iter < rsp_info->active_cnt; iter++) {
|
|
region = rsp_info->active_list.region[iter];
|
|
cb = hpb->region_tbl + region;
|
|
if (skhpb_check_lru_evict(hpb, cb))
|
|
return -ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline struct skhpb_rsp_field *skhpb_get_hpb_rsp(
|
|
struct ufshcd_lrb *lrbp)
|
|
{
|
|
return (struct skhpb_rsp_field *)&lrbp->ucd_rsp_ptr->sr.sense_data_len;
|
|
}
|
|
|
|
static int skhpb_rsp_map_cmd_req(struct skhpb_lu *hpb,
|
|
struct skhpb_rsp_info *rsp_info)
|
|
{
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
int region, subregion;
|
|
int iter;
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* Before Issue read buffer CMD for active active block,
|
|
* prepare the memory from memory pool.
|
|
*/
|
|
ret = skhpb_evict_load_region(hpb, rsp_info);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("region evict/load failed. ret %d\n", ret);
|
|
goto wakeup_ee_worker;
|
|
}
|
|
for (iter = 0; iter < rsp_info->active_cnt; iter++) {
|
|
region = rsp_info->active_list.region[iter];
|
|
subregion = rsp_info->active_list.subregion[iter];
|
|
cb = hpb->region_tbl + region;
|
|
|
|
if (region >= hpb->regions_per_lu ||
|
|
subregion >= cb->subregion_count) {
|
|
SKHPB_DRIVER_E("skhpb issue-map %d - %d range error\n",
|
|
region, subregion);
|
|
goto wakeup_ee_worker;
|
|
}
|
|
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
/*
|
|
* if subregion_state set SKHPB_SUBREGION_ISSUED,
|
|
* active_page has already been added to list,
|
|
* so it just ends function.
|
|
*/
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
if (cp->subregion_state == SKHPB_SUBREGION_ISSUED) {
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
continue;
|
|
}
|
|
|
|
cp->subregion_state = SKHPB_SUBREGION_ISSUED;
|
|
|
|
ret = skhpb_clean_dirty_bitmap(hpb, cp);
|
|
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
|
|
if (ret)
|
|
continue;
|
|
|
|
if (!hpb->hba->sdev_ufs_lu[hpb->lun] ||
|
|
!hpb->hba->sdev_ufs_lu[hpb->lun]->request_queue)
|
|
return -ENODEV;
|
|
ret = skhpb_set_map_req(hpb, region, subregion,
|
|
cp->mctx, rsp_info, R_BUFFER);
|
|
SKHPB_DRIVER_D("SEND READ_BUFFER - Region:%d, SubRegion:%d\n",
|
|
region, subregion);
|
|
if (ret) {
|
|
if (ret == -EAGAIN) {
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
cp->subregion_state = SKHPB_SUBREGION_DIRTY;
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
rsp_info->inactive_cnt = 0;
|
|
if (iter) {
|
|
rsp_info->active_list.region[0] = region;
|
|
rsp_info->active_list.subregion[0] = subregion;
|
|
rsp_info->active_cnt = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
SKHPB_DRIVER_E("skhpb_set_map_req error %d\n", ret);
|
|
goto wakeup_ee_worker;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
wakeup_ee_worker:
|
|
hpb->hba->skhpb_state = SKHPB_FAILED;
|
|
schedule_work(&hpb->hba->skhpb_eh_work);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* routine : isr (ufs) */
|
|
void skhpb_rsp_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
|
|
{
|
|
struct skhpb_lu *hpb;
|
|
struct skhpb_rsp_field *rsp_field;
|
|
struct skhpb_rsp_info *rsp_info;
|
|
int data_seg_len, num, blk_idx, update_alert;
|
|
|
|
update_alert = be32_to_cpu(lrbp->ucd_rsp_ptr->header.dword_2)
|
|
& MASK_RSP_UPIU_HPB_UPDATE_ALERT;
|
|
data_seg_len = be32_to_cpu(lrbp->ucd_rsp_ptr->header.dword_2)
|
|
& MASK_RSP_UPIU_DATA_SEG_LEN;
|
|
if (!update_alert || !data_seg_len) {
|
|
bool do_tasklet = false;
|
|
|
|
if (lrbp->lun >= UFS_UPIU_MAX_GENERAL_LUN)
|
|
return;
|
|
|
|
hpb = hba->skhpb_lup[lrbp->lun];
|
|
if (!hpb)
|
|
return;
|
|
|
|
spin_lock(&hpb->rsp_list_lock);
|
|
do_tasklet = !list_empty(&hpb->lh_rsp_info);
|
|
spin_unlock(&hpb->rsp_list_lock);
|
|
|
|
if (do_tasklet)
|
|
schedule_work(&hpb->skhpb_rsp_work);
|
|
return;
|
|
}
|
|
|
|
rsp_field = skhpb_get_hpb_rsp(lrbp);
|
|
|
|
if ((get_unaligned_be16(rsp_field->sense_data_len + 0)
|
|
!= SKHPB_DEV_SENSE_SEG_LEN) ||
|
|
rsp_field->desc_type != SKHPB_DEV_DES_TYPE ||
|
|
rsp_field->additional_len != SKHPB_DEV_ADDITIONAL_LEN ||
|
|
rsp_field->hpb_type == SKHPB_RSP_NONE ||
|
|
rsp_field->active_region_cnt > SKHPB_MAX_ACTIVE_NUM ||
|
|
rsp_field->inactive_region_cnt > SKHPB_MAX_INACTIVE_NUM)
|
|
return;
|
|
|
|
if (rsp_field->lun >= UFS_UPIU_MAX_GENERAL_LUN) {
|
|
SKHPB_DRIVER_E("lun is not general = %d", rsp_field->lun);
|
|
return;
|
|
}
|
|
hpb = hba->skhpb_lup[rsp_field->lun];
|
|
if (!hpb) {
|
|
SKHPB_DRIVER_E("UFS-LU%d is not SKHPB LU\n", rsp_field->lun);
|
|
return;
|
|
}
|
|
if (hpb->force_map_req_disable)
|
|
return;
|
|
SKHPB_DRIVER_D("HPB-Info Noti: %d LUN: %d Seg-Len %d, Req_type = %d\n",
|
|
rsp_field->hpb_type, rsp_field->lun,
|
|
data_seg_len, rsp_field->hpb_type);
|
|
if (!hpb->lu_hpb_enable) {
|
|
SKHPB_DRIVER_E("LU(%d) not HPB-LU\n", rsp_field->lun);
|
|
return;
|
|
}
|
|
//To verify the values within RESPONSE UPIU.
|
|
SKHPB_DRIVER_HEXDUMP("[HPB] RESP UPIU ", 16, 1,
|
|
lrbp->ucd_rsp_ptr, sizeof(struct utp_upiu_rsp), 1);
|
|
//If wLUMaxActiveHPBRegions == wNumHPBPinnedRegions
|
|
if (hpb->lru_info.max_lru_active_count == 0) {
|
|
SKHPB_DRIVER_D("max_lru_active_count is 0");
|
|
return;
|
|
}
|
|
|
|
switch (rsp_field->hpb_type) {
|
|
case SKHPB_RSP_REQ_REGION_UPDATE:
|
|
atomic64_inc(&hpb->rb_noti_cnt);
|
|
WARN_ON(data_seg_len != SKHPB_DEV_DATA_SEG_LEN);
|
|
|
|
spin_lock(&hpb->rsp_list_lock);
|
|
rsp_info = skhpb_get_req_info(hpb);
|
|
spin_unlock(&hpb->rsp_list_lock);
|
|
if (!rsp_info)
|
|
return;
|
|
rsp_info->type = SKHPB_RSP_REQ_REGION_UPDATE;
|
|
|
|
for (num = 0; num < rsp_field->active_region_cnt; num++) {
|
|
blk_idx = num * SKHPB_PER_ACTIVE_INFO_BYTES;
|
|
rsp_info->active_list.region[num] =
|
|
get_unaligned_be16(rsp_field->hpb_active_field + blk_idx);
|
|
rsp_info->active_list.subregion[num] =
|
|
get_unaligned_be16(rsp_field->hpb_active_field
|
|
+ blk_idx + 2);
|
|
SKHPB_DRIVER_D("active num: %d, #block: %d, page#: %d\n",
|
|
num + 1,
|
|
rsp_info->active_list.region[num],
|
|
rsp_info->active_list.subregion[num]);
|
|
}
|
|
rsp_info->active_cnt = num;
|
|
|
|
for (num = 0; num < rsp_field->inactive_region_cnt; num++) {
|
|
blk_idx = num * SKHPB_PER_INACTIVE_INFO_BYTES;
|
|
rsp_info->inactive_list.region[num] =
|
|
get_unaligned_be16(rsp_field->hpb_inactive_field + blk_idx);
|
|
SKHPB_DRIVER_D("inactive num: %d, #block: %d\n",
|
|
num + 1, rsp_info->inactive_list.region[num]);
|
|
}
|
|
rsp_info->inactive_cnt = num;
|
|
|
|
SKHPB_DRIVER_D("active cnt: %d, inactive cnt: %d\n",
|
|
rsp_info->active_cnt, rsp_info->inactive_cnt);
|
|
SKHPB_DRIVER_D("add_list %p -> %p\n",
|
|
rsp_info, &hpb->lh_rsp_info);
|
|
|
|
spin_lock(&hpb->rsp_list_lock);
|
|
list_add_tail(&rsp_info->list_rsp_info, &hpb->lh_rsp_info);
|
|
spin_unlock(&hpb->rsp_list_lock);
|
|
|
|
schedule_work(&hpb->skhpb_rsp_work);
|
|
break;
|
|
|
|
case SKHPB_RSP_HPB_RESET:
|
|
for (num = 0 ; num < UFS_UPIU_MAX_GENERAL_LUN ; num++) {
|
|
hpb = hba->skhpb_lup[num];
|
|
if (!hpb || !hpb->lu_hpb_enable)
|
|
continue;
|
|
atomic64_inc(&hpb->reset_noti_cnt);
|
|
|
|
spin_lock(&hpb->rsp_list_lock);
|
|
rsp_info = skhpb_get_req_info(hpb);
|
|
spin_unlock(&hpb->rsp_list_lock);
|
|
if (!rsp_info)
|
|
return;
|
|
|
|
rsp_info->type = SKHPB_RSP_HPB_RESET;
|
|
|
|
spin_lock(&hpb->rsp_list_lock);
|
|
list_add_tail(&rsp_info->list_rsp_info, &hpb->lh_rsp_info);
|
|
spin_unlock(&hpb->rsp_list_lock);
|
|
|
|
schedule_work(&hpb->skhpb_rsp_work);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
SKHPB_DRIVER_E("hpb_type is not available : %d\n",
|
|
rsp_field->hpb_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int skhpb_read_desc(struct ufs_hba *hba,
|
|
u8 desc_id, u8 desc_index, u8 *desc_buf, u32 size)
|
|
{
|
|
int err = 0;
|
|
|
|
err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC,
|
|
desc_id, desc_index, 0, desc_buf, &size);
|
|
if (err) {
|
|
SKHPB_DRIVER_E("reading Device Desc failed. err = %d\n", err);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int skhpb_read_device_desc(
|
|
struct ufs_hba *hba, u8 *desc_buf, u32 size)
|
|
{
|
|
return skhpb_read_desc(hba, QUERY_DESC_IDN_DEVICE, 0, desc_buf, size);
|
|
}
|
|
|
|
static int skhpb_read_geo_desc(struct ufs_hba *hba, u8 *desc_buf, u32 size)
|
|
{
|
|
return skhpb_read_desc(hba, QUERY_DESC_IDN_GEOMETRY, 0,
|
|
desc_buf, size);
|
|
}
|
|
|
|
static int skhpb_read_unit_desc(struct ufs_hba *hba, int lun,
|
|
u8 *desc_buf, u32 size)
|
|
{
|
|
return skhpb_read_desc(hba, QUERY_DESC_IDN_UNIT,
|
|
lun, desc_buf, size);
|
|
}
|
|
|
|
static inline void skhpb_add_subregion_to_req_list(struct skhpb_lu *hpb,
|
|
struct skhpb_subregion *cp)
|
|
{
|
|
list_add_tail(&cp->list_subregion, &hpb->lh_subregion_req);
|
|
cp->subregion_state = SKHPB_SUBREGION_DIRTY;
|
|
}
|
|
|
|
static int skhpb_execute_req(struct skhpb_lu *hpb, unsigned char *cmd,
|
|
struct skhpb_subregion *cp)
|
|
{
|
|
struct ufs_hba *hba = hpb->hba;
|
|
struct scsi_device *sdp;
|
|
struct request_queue *q;
|
|
struct request *req;
|
|
struct scsi_request *scsireq;
|
|
struct bio bio;
|
|
struct bio *pbio = &bio;
|
|
struct scsi_sense_hdr sshdr;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(hba->host->host_lock, flags);
|
|
sdp = hba->sdev_ufs_lu[hpb->lun];
|
|
if (sdp) {
|
|
ret = scsi_device_get(sdp);
|
|
if (!ret && !scsi_device_online(sdp)) {
|
|
ret = -ENODEV;
|
|
scsi_device_put(sdp);
|
|
}
|
|
} else {
|
|
ret = -ENODEV;
|
|
}
|
|
spin_unlock_irqrestore(hba->host->host_lock, flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
q = sdp->request_queue;
|
|
|
|
req = blk_get_request(q, REQ_OP_SCSI_IN, BLK_MQ_REQ_PREEMPT);
|
|
if (IS_ERR(req)) {
|
|
ret = PTR_ERR(req);
|
|
goto out_put;
|
|
}
|
|
scsireq = scsi_req(req);
|
|
|
|
scsireq->cmd_len = COMMAND_SIZE(cmd[0]);
|
|
if (scsireq->cmd_len > sizeof(scsireq->__cmd)) {
|
|
scsireq->cmd = kmalloc(scsireq->cmd_len, __GFP_RECLAIM);
|
|
if (!scsireq->cmd) {
|
|
ret = -ENOMEM;
|
|
goto out_put_request;
|
|
}
|
|
} else {
|
|
scsireq->cmd = scsireq->__cmd;
|
|
}
|
|
memcpy(scsireq->cmd, cmd, scsireq->cmd_len);
|
|
|
|
scsireq->retries = 3;
|
|
req->timeout = msecs_to_jiffies(10000);
|
|
req->rq_flags |= RQF_QUIET | RQF_PREEMPT;
|
|
|
|
ret = skhpb_add_bio_page(hpb, q, &bio, hpb->bvec, cp->mctx);
|
|
if (ret)
|
|
goto out_put_scsi_req;
|
|
blk_rq_append_bio(req, &pbio);
|
|
|
|
blk_execute_rq(q, NULL, req, 1);
|
|
|
|
if (scsireq->result) {
|
|
scsi_normalize_sense(scsireq->sense, SCSI_SENSE_BUFFERSIZE, &sshdr);
|
|
SKHPB_DRIVER_E("code %x sense_key %x asc %x ascq %x",
|
|
sshdr.response_code, sshdr.sense_key, sshdr.asc,
|
|
sshdr.ascq);
|
|
SKHPB_DRIVER_E("byte4 %x byte5 %x byte6 %x additional_len %x",
|
|
sshdr.byte4, sshdr.byte5, sshdr.byte6,
|
|
sshdr.additional_length);
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_error_active_subregion(hpb, cp);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
ret = -EIO;
|
|
} else {
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
ret = skhpb_clean_dirty_bitmap(hpb, cp);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("skhpb_clean_dirty_bitmap error %d", ret);
|
|
}
|
|
ret = 0;
|
|
}
|
|
|
|
out_put_scsi_req:
|
|
scsi_req_free_cmd(scsireq);
|
|
out_put_request:
|
|
blk_put_request(req);
|
|
out_put:
|
|
scsi_device_put(sdp);
|
|
return ret;
|
|
}
|
|
|
|
static int skhpb_issue_map_req_from_list(struct skhpb_lu *hpb)
|
|
{
|
|
struct skhpb_subregion *cp, *next_cp;
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
LIST_HEAD(req_list);
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
list_splice_init(&hpb->lh_subregion_req, &req_list);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
|
|
list_for_each_entry_safe(cp, next_cp, &req_list, list_subregion) {
|
|
int subregion_mem_size =
|
|
cp->last ? hpb->last_subregion_mem_size : hpb->subregion_mem_size;
|
|
unsigned char cmd[10] = { 0 };
|
|
|
|
skhpb_set_read_buf_cmd(cmd, cp->region, cp->subregion,
|
|
subregion_mem_size);
|
|
|
|
SKHPB_DRIVER_D("issue map_request: %d - %d/%d\n",
|
|
cp->region, cp->subregion, subregion_mem_size);
|
|
|
|
ret = skhpb_execute_req(hpb, cmd, cp);
|
|
if (ret < 0) {
|
|
SKHPB_DRIVER_E("region %d sub %d failed with err %d",
|
|
cp->region, cp->subregion, ret);
|
|
continue;
|
|
}
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_clean_active_subregion(hpb, cp);
|
|
list_del_init(&cp->list_subregion);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void skhpb_pinned_work_handler(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct skhpb_lu *hpb =
|
|
container_of(dwork, struct skhpb_lu, skhpb_pinned_work);
|
|
int ret;
|
|
|
|
SKHPB_DRIVER_D("worker start\n");
|
|
|
|
if (!list_empty(&hpb->lh_subregion_req)) {
|
|
pm_runtime_get_sync(SKHPB_DEV(hpb));
|
|
ret = skhpb_issue_map_req_from_list(hpb);
|
|
/*
|
|
* if its function failed at init time,
|
|
* skhpb-device will request map-req,
|
|
* so it is not critical-error, and just finish work-handler
|
|
*/
|
|
if (ret)
|
|
SKHPB_DRIVER_E("failed map-issue. ret %d\n", ret);
|
|
pm_runtime_mark_last_busy(SKHPB_DEV(hpb));
|
|
pm_runtime_put_noidle(SKHPB_DEV(hpb));
|
|
}
|
|
|
|
SKHPB_DRIVER_D("worker end\n");
|
|
}
|
|
|
|
static void skhpb_map_req_retry_work_handler(struct work_struct *work)
|
|
{
|
|
struct skhpb_lu *hpb;
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct skhpb_map_req *map_req;
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
LIST_HEAD(retry_list);
|
|
|
|
hpb = container_of(dwork, struct skhpb_lu, skhpb_map_req_retry_work);
|
|
SKHPB_DRIVER_D("retry worker start");
|
|
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
list_splice_init(&hpb->lh_map_req_retry, &retry_list);
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
|
|
while (1) {
|
|
map_req = list_first_entry_or_null(&retry_list,
|
|
struct skhpb_map_req, list_map_req);
|
|
if (!map_req) {
|
|
SKHPB_DRIVER_D("There is no map_req");
|
|
break;
|
|
}
|
|
list_del(&map_req->list_map_req);
|
|
|
|
map_req->retry_cnt++;
|
|
|
|
ret = skhpb_map_req_issue(hpb, map_req);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("skhpb_map_req_issue error %d", ret);
|
|
goto wakeup_ee_worker;
|
|
}
|
|
}
|
|
SKHPB_DRIVER_D("worker end");
|
|
return;
|
|
|
|
wakeup_ee_worker:
|
|
hpb->hba->skhpb_state = SKHPB_FAILED;
|
|
schedule_work(&hpb->hba->skhpb_eh_work);
|
|
}
|
|
|
|
static void skhpb_delayed_rsp_work_handler(struct work_struct *work)
|
|
{
|
|
struct skhpb_lu *hpb = container_of(work, struct skhpb_lu, skhpb_rsp_work);
|
|
struct skhpb_rsp_info *rsp_info;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
SKHPB_DRIVER_D("rsp_work enter");
|
|
|
|
while (!hpb->hba->clk_gating.is_suspended) {
|
|
spin_lock_irqsave(&hpb->rsp_list_lock, flags);
|
|
rsp_info = list_first_entry_or_null(&hpb->lh_rsp_info,
|
|
struct skhpb_rsp_info, list_rsp_info);
|
|
spin_unlock_irqrestore(&hpb->rsp_list_lock, flags);
|
|
if (!rsp_info) {
|
|
break;
|
|
}
|
|
SKHPB_RSP_TIME(rsp_info->RSP_start);
|
|
|
|
switch (rsp_info->type) {
|
|
case SKHPB_RSP_REQ_REGION_UPDATE:
|
|
ret = skhpb_rsp_map_cmd_req(hpb, rsp_info);
|
|
if (ret)
|
|
return;
|
|
break;
|
|
|
|
case SKHPB_RSP_HPB_RESET:
|
|
skhpb_purge_active_block(hpb);
|
|
skhpb_map_loading_trigger(hpb, true, false);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
spin_lock_irqsave(&hpb->rsp_list_lock, flags);
|
|
list_del_init(&rsp_info->list_rsp_info);
|
|
list_add_tail(&rsp_info->list_rsp_info, &hpb->lh_rsp_info_free);
|
|
spin_unlock_irqrestore(&hpb->rsp_list_lock, flags);
|
|
}
|
|
SKHPB_DRIVER_D("rsp_work end");
|
|
}
|
|
|
|
static void skhpb_init_constant(void)
|
|
{
|
|
skhpb_sects_per_blk_shift = ffs(SKHPB_BLOCK) - ffs(SKHPB_SECTOR);
|
|
SKHPB_DRIVER_D("skhpb_sects_per_blk_shift: %u %u\n",
|
|
skhpb_sects_per_blk_shift, ffs(SKHPB_SECTORS_PER_BLOCK) - 1);
|
|
|
|
skhpb_bits_per_dword_shift = ffs(SKHPB_BITS_PER_DWORD) - 1;
|
|
skhpb_bits_per_dword_mask = SKHPB_BITS_PER_DWORD - 1;
|
|
SKHPB_DRIVER_D("bits_per_dword %u shift %u mask 0x%X\n",
|
|
SKHPB_BITS_PER_DWORD, skhpb_bits_per_dword_shift, skhpb_bits_per_dword_mask);
|
|
}
|
|
|
|
static void skhpb_table_mempool_remove(struct skhpb_lu *hpb)
|
|
{
|
|
struct skhpb_map_ctx *mctx, *next;
|
|
int i;
|
|
|
|
/*
|
|
* the mctx in the lh_map_ctx has been allocated completely.
|
|
*/
|
|
list_for_each_entry_safe(mctx, next, &hpb->lh_map_ctx, list_table) {
|
|
list_del(&mctx->list_table);
|
|
|
|
for (i = 0; i < hpb->mpages_per_subregion; i++)
|
|
__free_page(mctx->m_page[i]);
|
|
|
|
kvfree(mctx->ppn_dirty);
|
|
kfree(mctx->m_page);
|
|
kfree(mctx);
|
|
skhpb_alloc_mctx--;
|
|
}
|
|
}
|
|
|
|
static int skhpb_init_pinned_active_block(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb)
|
|
{
|
|
struct skhpb_subregion *cp;
|
|
int subregion, j;
|
|
int err = 0;
|
|
unsigned long flags;
|
|
|
|
for (subregion = 0 ; subregion < cb->subregion_count ; subregion++) {
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
cp->mctx = skhpb_get_map_ctx(hpb);
|
|
if (IS_ERR(cp->mctx)) {
|
|
err = PTR_ERR(cp->mctx);
|
|
goto release;
|
|
}
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
skhpb_add_subregion_to_req_list(hpb, cp);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
}
|
|
return 0;
|
|
|
|
release:
|
|
for (j = 0 ; j < subregion ; j++) {
|
|
cp = cb->subregion_tbl + j;
|
|
skhpb_put_map_ctx(hpb, cp->mctx);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static inline bool skhpb_is_pinned(
|
|
struct skhpb_lu_desc *lu_desc, int region)
|
|
{
|
|
if (lu_desc->lu_hpb_pinned_end_offset != -1 &&
|
|
region >= lu_desc->hpb_pinned_region_startidx &&
|
|
region <= lu_desc->lu_hpb_pinned_end_offset)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline void skhpb_init_jobs(struct skhpb_lu *hpb)
|
|
{
|
|
INIT_DELAYED_WORK(&hpb->skhpb_pinned_work, skhpb_pinned_work_handler);
|
|
INIT_DELAYED_WORK(&hpb->skhpb_map_req_retry_work, skhpb_map_req_retry_work_handler);
|
|
INIT_WORK(&hpb->skhpb_rsp_work, skhpb_delayed_rsp_work_handler);
|
|
}
|
|
|
|
static inline void skhpb_cancel_jobs(struct skhpb_lu *hpb)
|
|
{
|
|
cancel_delayed_work_sync(&hpb->skhpb_pinned_work);
|
|
cancel_work_sync(&hpb->skhpb_rsp_work);
|
|
cancel_delayed_work_sync(&hpb->skhpb_map_req_retry_work);
|
|
}
|
|
|
|
static void skhpb_init_subregion_tbl(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb, bool last_region)
|
|
{
|
|
int subregion;
|
|
|
|
for (subregion = 0 ; subregion < cb->subregion_count ; subregion++) {
|
|
struct skhpb_subregion *cp = cb->subregion_tbl + subregion;
|
|
|
|
cp->region = cb->region;
|
|
cp->subregion = subregion;
|
|
cp->subregion_state = SKHPB_SUBREGION_UNUSED;
|
|
cp->last = (last_region && subregion == cb->subregion_count - 1);
|
|
}
|
|
}
|
|
|
|
static inline int skhpb_alloc_subregion_tbl(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb, int subregion_count)
|
|
{
|
|
cb->subregion_tbl = kzalloc(sizeof(struct skhpb_subregion) * subregion_count,
|
|
GFP_KERNEL);
|
|
if (!cb->subregion_tbl)
|
|
return -ENOMEM;
|
|
|
|
cb->subregion_count = subregion_count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int skhpb_table_mempool_init(struct skhpb_lu *hpb,
|
|
int num_regions, int subregions_per_region,
|
|
int entry_count, int entry_byte)
|
|
{
|
|
int i, j;
|
|
struct skhpb_map_ctx *mctx = NULL;
|
|
|
|
for (i = 0 ; i < num_regions * subregions_per_region ; i++) {
|
|
mctx = kzalloc(sizeof(struct skhpb_map_ctx), GFP_KERNEL);
|
|
if (!mctx)
|
|
goto release_mem;
|
|
|
|
mctx->m_page = kzalloc(sizeof(struct page *) *
|
|
hpb->mpages_per_subregion, GFP_KERNEL);
|
|
if (!mctx->m_page)
|
|
goto release_mem;
|
|
|
|
mctx->ppn_dirty = kvzalloc(entry_count / BITS_PER_BYTE, GFP_KERNEL);
|
|
if (!mctx->ppn_dirty)
|
|
goto release_mem;
|
|
|
|
for (j = 0; j < hpb->mpages_per_subregion; j++) {
|
|
mctx->m_page[j] = alloc_page(GFP_KERNEL | __GFP_ZERO);
|
|
if (!mctx->m_page[j])
|
|
goto release_mem;
|
|
}
|
|
/* SKSKHPB_DRIVER_D("[%d] mctx->m_page %p get_order %d\n", i,
|
|
mctx->m_page, get_order(hpb->mpages_per_subregion)); */
|
|
|
|
INIT_LIST_HEAD(&mctx->list_table);
|
|
list_add(&mctx->list_table, &hpb->lh_map_ctx);
|
|
|
|
hpb->debug_free_table++;
|
|
}
|
|
|
|
skhpb_alloc_mctx = num_regions * subregions_per_region;
|
|
/* SKSKHPB_DRIVER_D("number of mctx %d %d %d. debug_free_table %d\n",
|
|
num_regions * subregions_per_region, num_regions,
|
|
subregions_per_region, hpb->debug_free_table); */
|
|
return 0;
|
|
|
|
release_mem:
|
|
/*
|
|
* mctxs already added in lh_map_ctx will be removed
|
|
* in the caller function.
|
|
*/
|
|
if (!mctx)
|
|
goto out;
|
|
|
|
if (mctx->m_page) {
|
|
for (j = 0; j < hpb->mpages_per_subregion; j++)
|
|
if (mctx->m_page[j])
|
|
__free_page(mctx->m_page[j]);
|
|
kfree(mctx->m_page);
|
|
}
|
|
kvfree(mctx->ppn_dirty);
|
|
kfree(mctx);
|
|
out:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int skhpb_req_mempool_init(struct ufs_hba *hba,
|
|
struct skhpb_lu *hpb, int qd)
|
|
{
|
|
struct skhpb_rsp_info *rsp_info = NULL;
|
|
struct skhpb_map_req *map_req = NULL;
|
|
int i, rec_qd;
|
|
|
|
if (!qd) {
|
|
qd = hba->nutrs;
|
|
SKHPB_DRIVER_D("hba->nutrs = %d\n", hba->nutrs);
|
|
}
|
|
if (hpb->hpb_ver >= 0x0200)
|
|
rec_qd = hpb->lu_max_active_regions;
|
|
else
|
|
rec_qd = qd;
|
|
|
|
INIT_LIST_HEAD(&hpb->lh_rsp_info_free);
|
|
INIT_LIST_HEAD(&hpb->lh_map_req_free);
|
|
INIT_LIST_HEAD(&hpb->lh_map_req_retry);
|
|
|
|
hpb->rsp_info = vzalloc(rec_qd * sizeof(struct skhpb_rsp_info));
|
|
if (!hpb->rsp_info)
|
|
goto release_mem;
|
|
|
|
hpb->map_req = vzalloc(qd * sizeof(struct skhpb_map_req));
|
|
if (!hpb->map_req)
|
|
goto release_mem;
|
|
|
|
for (i = 0; i < rec_qd; i++) {
|
|
rsp_info = hpb->rsp_info + i;
|
|
INIT_LIST_HEAD(&rsp_info->list_rsp_info);
|
|
list_add_tail(&rsp_info->list_rsp_info, &hpb->lh_rsp_info_free);
|
|
}
|
|
|
|
for (i = 0; i < qd; i++) {
|
|
map_req = hpb->map_req + i;
|
|
INIT_LIST_HEAD(&map_req->list_map_req);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_free);
|
|
}
|
|
|
|
return 0;
|
|
|
|
release_mem:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void skhpb_init_lu_constant(struct skhpb_lu *hpb,
|
|
struct skhpb_lu_desc *lu_desc,
|
|
struct skhpb_func_desc *func_desc)
|
|
{
|
|
unsigned long long region_unit_size, region_mem_size;
|
|
unsigned long long last_subregion_unit_size;
|
|
int entries_per_region;
|
|
|
|
/* From descriptors */
|
|
region_unit_size = SKHPB_SECTOR * (1ULL << func_desc->hpb_region_size);
|
|
region_mem_size = region_unit_size / SKHPB_BLOCK * SKHPB_ENTRY_SIZE;
|
|
|
|
hpb->subregion_unit_size = SKHPB_SECTOR * (1ULL << func_desc->hpb_subregion_size);
|
|
hpb->subregion_mem_size = hpb->subregion_unit_size /
|
|
SKHPB_BLOCK * SKHPB_ENTRY_SIZE;
|
|
if (func_desc->hpb_region_size == func_desc->hpb_subregion_size)
|
|
hpb->identical_size = true;
|
|
else
|
|
hpb->identical_size = false;
|
|
|
|
last_subregion_unit_size =
|
|
lu_desc->lu_logblk_cnt *
|
|
(1ULL << lu_desc->lu_logblk_size) % hpb->subregion_unit_size;
|
|
hpb->last_subregion_mem_size =
|
|
last_subregion_unit_size / SKHPB_BLOCK * SKHPB_ENTRY_SIZE;
|
|
if (hpb->last_subregion_mem_size == 0)
|
|
hpb->last_subregion_mem_size = hpb->subregion_mem_size;
|
|
hpb->hpb_ver = func_desc->hpb_ver;
|
|
hpb->lu_max_active_regions = lu_desc->lu_max_active_hpb_regions;
|
|
hpb->lru_info.max_lru_active_count =
|
|
lu_desc->lu_max_active_hpb_regions
|
|
- lu_desc->lu_num_hpb_pinned_regions;
|
|
|
|
/* relation : lu <-> region <-> sub region <-> entry */
|
|
hpb->lu_num_blocks = lu_desc->lu_logblk_cnt;
|
|
entries_per_region = region_mem_size / SKHPB_ENTRY_SIZE;
|
|
hpb->entries_per_subregion = hpb->subregion_mem_size / SKHPB_ENTRY_SIZE;
|
|
hpb->subregions_per_region = region_mem_size / hpb->subregion_mem_size;
|
|
|
|
hpb->hpb_control_mode = func_desc->hpb_control_mode;
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
hpb->ppn_dirties_per_subregion =
|
|
hpb->entries_per_subregion/BITS_PER_PPN_DIRTY;
|
|
/* SKSKHPB_DRIVER_D("ppn_dirties_per_subregion:%d, %d \n",
|
|
hpb->ppn_dirties_per_subregion, BITS_PER_PPN_DIRTY); */
|
|
#endif
|
|
/*
|
|
* 1. regions_per_lu
|
|
* = (lu_num_blocks * 4096) / region_unit_size
|
|
* = (lu_num_blocks * SKHPB_ENTRY_SIZE) / region_mem_size
|
|
* = lu_num_blocks / (region_mem_size / SKHPB_ENTRY_SIZE)
|
|
*
|
|
* 2. regions_per_lu = lu_num_blocks / subregion_mem_size (is trik...)
|
|
* if SKHPB_ENTRY_SIZE != subregions_per_region, it is error.
|
|
*/
|
|
hpb->regions_per_lu = (hpb->lu_num_blocks
|
|
+ (region_mem_size / SKHPB_ENTRY_SIZE) - 1)
|
|
/ (region_mem_size / SKHPB_ENTRY_SIZE);
|
|
hpb->subregions_per_lu = (hpb->lu_num_blocks
|
|
+ (hpb->subregion_mem_size / SKHPB_ENTRY_SIZE) - 1)
|
|
/ (hpb->subregion_mem_size / SKHPB_ENTRY_SIZE);
|
|
|
|
/* mempool info */
|
|
hpb->mpage_bytes = PAGE_SIZE;
|
|
hpb->mpages_per_subregion = hpb->subregion_mem_size / hpb->mpage_bytes;
|
|
|
|
/* Bitmask Info. */
|
|
hpb->dwords_per_subregion = hpb->entries_per_subregion / SKHPB_BITS_PER_DWORD;
|
|
hpb->entries_per_region_shift = ffs(entries_per_region) - 1;
|
|
hpb->entries_per_region_mask = entries_per_region - 1;
|
|
hpb->entries_per_subregion_shift = ffs(hpb->entries_per_subregion) - 1;
|
|
hpb->entries_per_subregion_mask = hpb->entries_per_subregion - 1;
|
|
|
|
SKHPB_DRIVER_I("===== Device Descriptor =====\n");
|
|
SKHPB_DRIVER_I("hpb_region_size = %d, hpb_subregion_size = %d\n",
|
|
func_desc->hpb_region_size,
|
|
func_desc->hpb_subregion_size);
|
|
SKHPB_DRIVER_I("===== Constant Values =====\n");
|
|
SKHPB_DRIVER_I("region_unit_size = %llu, region_mem_size %llu\n",
|
|
region_unit_size, region_mem_size);
|
|
SKHPB_DRIVER_I("subregion_unit_size = %llu, subregion_mem_size %d\n",
|
|
hpb->subregion_unit_size, hpb->subregion_mem_size);
|
|
SKHPB_DRIVER_I("last_subregion_mem_size = %d\n", hpb->last_subregion_mem_size);
|
|
SKHPB_DRIVER_I("lu_num_blks = %llu, reg_per_lu = %d, subreg_per_lu = %d\n",
|
|
hpb->lu_num_blocks, hpb->regions_per_lu,
|
|
hpb->subregions_per_lu);
|
|
SKHPB_DRIVER_I("subregions_per_region = %d\n",
|
|
hpb->subregions_per_region);
|
|
SKHPB_DRIVER_I("entries_per_region %u shift %u mask 0x%X\n",
|
|
entries_per_region, hpb->entries_per_region_shift,
|
|
hpb->entries_per_region_mask);
|
|
SKHPB_DRIVER_I("entries_per_subregion %u shift %u mask 0x%X\n",
|
|
hpb->entries_per_subregion,
|
|
hpb->entries_per_subregion_shift,
|
|
hpb->entries_per_subregion_mask);
|
|
SKHPB_DRIVER_I("mpages_per_subregion : %d\n",
|
|
hpb->mpages_per_subregion);
|
|
SKHPB_DRIVER_I("===================================\n");
|
|
}
|
|
|
|
static int skhpb_lu_hpb_init(struct ufs_hba *hba, struct skhpb_lu *hpb,
|
|
struct skhpb_func_desc *func_desc,
|
|
struct skhpb_lu_desc *lu_desc, int lun,
|
|
bool *do_work_lun)
|
|
{
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
int region, subregion;
|
|
int total_subregion_count, subregion_count;
|
|
int ret, j;
|
|
|
|
*do_work_lun = false;
|
|
|
|
spin_lock_init(&hpb->hpb_lock);
|
|
spin_lock_init(&hpb->rsp_list_lock);
|
|
spin_lock_init(&hpb->map_list_lock);
|
|
|
|
/* init lru information */
|
|
INIT_LIST_HEAD(&hpb->lru_info.lru);
|
|
hpb->lru_info.selection_type = TYPE_LRU;
|
|
|
|
INIT_LIST_HEAD(&hpb->lh_subregion_req);
|
|
INIT_LIST_HEAD(&hpb->lh_rsp_info);
|
|
INIT_LIST_HEAD(&hpb->lh_map_ctx);
|
|
|
|
hpb->lu_hpb_enable = true;
|
|
|
|
skhpb_init_lu_constant(hpb, lu_desc, func_desc);
|
|
|
|
hpb->region_tbl = vzalloc(sizeof(struct skhpb_region) * hpb->regions_per_lu);
|
|
if (!hpb->region_tbl)
|
|
return -ENOMEM;
|
|
|
|
SKHPB_DRIVER_D("active_block_table bytes: %lu\n",
|
|
(sizeof(struct skhpb_region) * hpb->regions_per_lu));
|
|
|
|
ret = skhpb_table_mempool_init(hpb,
|
|
lu_desc->lu_max_active_hpb_regions,
|
|
hpb->subregions_per_region,
|
|
hpb->entries_per_subregion, SKHPB_ENTRY_SIZE);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("ppn table mempool init fail!\n");
|
|
goto release_mempool;
|
|
}
|
|
|
|
ret = skhpb_req_mempool_init(hba, hpb, lu_desc->lu_queue_depth);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("rsp_info_mempool init fail!\n");
|
|
goto release_mempool;
|
|
}
|
|
|
|
total_subregion_count = hpb->subregions_per_lu;
|
|
|
|
skhpb_init_jobs(hpb);
|
|
|
|
SKHPB_DRIVER_D("total_subregion_count: %d\n", total_subregion_count);
|
|
for (region = 0, subregion_count = 0,
|
|
total_subregion_count = hpb->subregions_per_lu;
|
|
region < hpb->regions_per_lu;
|
|
region++, total_subregion_count -= subregion_count) {
|
|
cb = hpb->region_tbl + region;
|
|
cb->region = region;
|
|
|
|
/* init lru region information*/
|
|
INIT_LIST_HEAD(&cb->list_region);
|
|
cb->hit_count = 0;
|
|
|
|
subregion_count = min(total_subregion_count,
|
|
hpb->subregions_per_region);
|
|
/* SKSKHPB_DRIVER_D("total: %d subregion_count: %d\n",
|
|
total_subregion_count, subregion_count); */
|
|
|
|
ret = skhpb_alloc_subregion_tbl(hpb, cb, subregion_count);
|
|
if (ret)
|
|
goto release_region_cp;
|
|
skhpb_init_subregion_tbl(hpb, cb, region == hpb->regions_per_lu - 1);
|
|
|
|
if (skhpb_is_pinned(lu_desc, region)) {
|
|
SKHPB_DRIVER_D("region: %d PINNED %d ~ %d\n",
|
|
region, lu_desc->hpb_pinned_region_startidx,
|
|
lu_desc->lu_hpb_pinned_end_offset);
|
|
ret = skhpb_init_pinned_active_block(hpb, cb);
|
|
if (ret)
|
|
goto release_region_cp;
|
|
*do_work_lun = true;
|
|
cb->is_pinned = true;
|
|
cb->region_state = SKHPB_REGION_ACTIVE;
|
|
} else {
|
|
/* SKSKHPB_DRIVER_D("region: %d inactive\n", cb->region); */
|
|
cb->is_pinned = false;
|
|
cb->region_state = SKHPB_REGION_INACTIVE;
|
|
}
|
|
}
|
|
if (total_subregion_count != 0) {
|
|
SKHPB_DRIVER_E("error total_subregion_count: %d\n",
|
|
total_subregion_count);
|
|
goto release_region_cp;
|
|
}
|
|
hpb->hba = hba;
|
|
hpb->lun = lun;
|
|
/*
|
|
* even if creating sysfs failed, skhpb could run normally.
|
|
* so we don't deal with error handling
|
|
*/
|
|
skhpb_create_sysfs(hba, hpb);
|
|
return 0;
|
|
|
|
release_region_cp:
|
|
for (j = 0 ; j < region ; j++) {
|
|
cb = hpb->region_tbl + j;
|
|
if (cb->subregion_tbl) {
|
|
for (subregion = 0; subregion < cb->subregion_count;
|
|
subregion++) {
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
if (cp->mctx)
|
|
skhpb_put_map_ctx(hpb, cp->mctx);
|
|
}
|
|
kfree(cb->subregion_tbl);
|
|
}
|
|
}
|
|
|
|
release_mempool:
|
|
skhpb_table_mempool_remove(hpb);
|
|
*do_work_lun = false;
|
|
return ret;
|
|
}
|
|
|
|
static int skhpb_get_hpb_lu_desc(struct ufs_hba *hba,
|
|
struct skhpb_lu_desc *lu_desc, int lun)
|
|
{
|
|
int ret;
|
|
u8 logical_buf[SKHPB_QUERY_DESC_UNIT_MAX_SIZE] = { 0 };
|
|
|
|
ret = skhpb_read_unit_desc(hba, lun, logical_buf,
|
|
SKHPB_QUERY_DESC_UNIT_MAX_SIZE);
|
|
if (ret) {
|
|
SKHPB_DRIVER_E("read unit desc failed. ret %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
lu_desc->lu_queue_depth = logical_buf[UNIT_DESC_PARAM_LU_Q_DEPTH];
|
|
|
|
// 2^log, ex) 0x0C = 4KB
|
|
lu_desc->lu_logblk_size = logical_buf[UNIT_DESC_PARAM_LOGICAL_BLK_SIZE];
|
|
lu_desc->lu_logblk_cnt =
|
|
get_unaligned_be64(&logical_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT]);
|
|
|
|
if (logical_buf[UNIT_DESC_PARAM_LU_ENABLE] == LU_HPB_ENABLE)
|
|
lu_desc->lu_hpb_enable = true;
|
|
else
|
|
lu_desc->lu_hpb_enable = false;
|
|
|
|
lu_desc->lu_max_active_hpb_regions =
|
|
get_unaligned_be16(logical_buf +
|
|
UNIT_DESC_HPB_LU_MAX_ACTIVE_REGIONS);
|
|
lu_desc->hpb_pinned_region_startidx =
|
|
get_unaligned_be16(logical_buf +
|
|
UNIT_DESC_HPB_LU_PIN_REGION_START_OFFSET);
|
|
lu_desc->lu_num_hpb_pinned_regions =
|
|
get_unaligned_be16(logical_buf +
|
|
UNIT_DESC_HPB_LU_NUM_PIN_REGIONS);
|
|
|
|
if (lu_desc->lu_hpb_enable) {
|
|
SKHPB_DRIVER_D("LUN(%d) [0A] bLogicalBlockSize %d\n",
|
|
lun, lu_desc->lu_logblk_size);
|
|
SKHPB_DRIVER_D("LUN(%d) [0B] qLogicalBlockCount %llu\n",
|
|
lun, lu_desc->lu_logblk_cnt);
|
|
SKHPB_DRIVER_D("LUN(%d) [03] bLuEnable %d\n",
|
|
lun, logical_buf[UNIT_DESC_PARAM_LU_ENABLE]);
|
|
SKHPB_DRIVER_D("LUN(%d) [06] bLuQueueDepth %d\n",
|
|
lun, lu_desc->lu_queue_depth);
|
|
SKHPB_DRIVER_D("LUN(%d) [23:24] wLUMaxActiveHPBRegions %d\n",
|
|
lun, lu_desc->lu_max_active_hpb_regions);
|
|
SKHPB_DRIVER_D("LUN(%d) [25:26] wHPBPinnedRegionStartIdx %d\n",
|
|
lun, lu_desc->hpb_pinned_region_startidx);
|
|
SKHPB_DRIVER_D("LUN(%d) [27:28] wNumHPBPinnedRegions %d\n",
|
|
lun, lu_desc->lu_num_hpb_pinned_regions);
|
|
}
|
|
|
|
if (lu_desc->lu_num_hpb_pinned_regions > 0) {
|
|
lu_desc->lu_hpb_pinned_end_offset =
|
|
lu_desc->hpb_pinned_region_startidx +
|
|
lu_desc->lu_num_hpb_pinned_regions - 1;
|
|
} else
|
|
lu_desc->lu_hpb_pinned_end_offset = -1;
|
|
|
|
if (lu_desc->lu_hpb_enable)
|
|
SKHPB_DRIVER_I("Enable, LU: %d, MAX_REGION: %d, PIN: %d - %d\n",
|
|
lun,
|
|
lu_desc->lu_max_active_hpb_regions,
|
|
lu_desc->hpb_pinned_region_startidx,
|
|
lu_desc->lu_num_hpb_pinned_regions);
|
|
return 0;
|
|
}
|
|
|
|
static void skhpb_quirk_setup(struct ufs_hba *hba,
|
|
struct skhpb_func_desc *desc)
|
|
{
|
|
if (hba->dev_quirks & SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP) {
|
|
hba->skhpb_quirk |= SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP;
|
|
SKHPB_DRIVER_I("QUIRK set PURGE_HINT_INFO_WHEN_SLEEP\n");
|
|
}
|
|
}
|
|
|
|
static int skhpb_read_dev_desc_support(struct ufs_hba *hba,
|
|
struct skhpb_func_desc *desc)
|
|
{
|
|
u8 desc_buf[SKHPB_QUERY_DESC_DEVICE_MAX_SIZE];
|
|
int err;
|
|
|
|
err = skhpb_read_device_desc(hba, desc_buf,
|
|
SKHPB_QUERY_DESC_DEVICE_MAX_SIZE);
|
|
if (err)
|
|
return err;
|
|
|
|
if (desc_buf[DEVICE_DESC_PARAM_FEAT_SUP] &
|
|
SKHPB_UFS_FEATURE_SUPPORT_HPB_BIT) {
|
|
hba->skhpb_feat |= SKHPB_UFS_FEATURE_SUPPORT_HPB_BIT;
|
|
SKHPB_DRIVER_I("FeaturesSupport= support\n");
|
|
} else {
|
|
SKHPB_DRIVER_I("FeaturesSupport= not support\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
desc->lu_cnt = desc_buf[DEVICE_DESC_PARAM_NUM_LU];
|
|
SKHPB_DRIVER_D("Dev LU count= %d\n", desc->lu_cnt);
|
|
|
|
desc->spec_ver =
|
|
(u16)SKHPB_SHIFT_BYTE_1(desc_buf[DEVICE_DESC_PARAM_SPEC_VER]) |
|
|
(u16)SKHPB_SHIFT_BYTE_0(desc_buf[DEVICE_DESC_PARAM_SPEC_VER + 1]);
|
|
SKHPB_DRIVER_I("Dev Spec Ver= %x.%x\n",
|
|
SKHPB_GET_BYTE_1(desc->spec_ver),
|
|
SKHPB_GET_BYTE_0(desc->spec_ver));
|
|
|
|
desc->hpb_ver =
|
|
(u16)SKHPB_SHIFT_BYTE_1(desc_buf[DEVICE_DESC_PARAM_HPB_VER]) |
|
|
(u16)SKHPB_SHIFT_BYTE_0(desc_buf[DEVICE_DESC_PARAM_HPB_VER + 1]);
|
|
SKHPB_DRIVER_I("Dev Ver= %x.%x.%x, DD Ver= %x.%x.%x\n",
|
|
(desc->hpb_ver >> 8) & 0xf,
|
|
(desc->hpb_ver >> 4) & 0xf,
|
|
(desc->hpb_ver >> 0) & 0xf,
|
|
SKHPB_GET_BYTE_2(SKHPB_DD_VER),
|
|
SKHPB_GET_BYTE_1(SKHPB_DD_VER),
|
|
SKHPB_GET_BYTE_0(SKHPB_DD_VER));
|
|
|
|
skhpb_quirk_setup(hba, desc);
|
|
|
|
if (hba->skhpb_quirk & SKHPB_QUIRK_ALWAYS_DEVICE_CONTROL_MODE)
|
|
desc->hpb_control_mode = DEV_CTRL_MODE;
|
|
else
|
|
desc->hpb_control_mode = (u8)desc_buf[DEVICE_DESC_PARAM_HPB_CONTROL];
|
|
|
|
|
|
SKHPB_DRIVER_I("HPB Control Mode = %s",
|
|
(desc->hpb_control_mode)?"DEV MODE":"HOST MODE");
|
|
if (desc->hpb_control_mode == HOST_CTRL_MODE) {
|
|
SKHPB_DRIVER_E("Driver does not support Host Control Mode");
|
|
return -ENODEV;
|
|
}
|
|
hba->hpb_control_mode = desc->hpb_control_mode;
|
|
return 0;
|
|
}
|
|
|
|
static int skhpb_read_geo_desc_support(struct ufs_hba *hba,
|
|
struct skhpb_func_desc *desc)
|
|
{
|
|
int err;
|
|
u8 geometry_buf[SKHPB_QUERY_DESC_GEOMETRY_MAX_SIZE];
|
|
|
|
err = skhpb_read_geo_desc(hba, geometry_buf,
|
|
SKHPB_QUERY_DESC_GEOMETRY_MAX_SIZE);
|
|
if (err)
|
|
return err;
|
|
|
|
desc->hpb_region_size = geometry_buf[GEOMETRY_DESC_HPB_REGION_SIZE];
|
|
desc->hpb_number_lu = geometry_buf[GEOMETRY_DESC_HPB_NUMBER_LU];
|
|
desc->hpb_subregion_size =
|
|
geometry_buf[GEOMETRY_DESC_HPB_SUBREGION_SIZE];
|
|
desc->hpb_device_max_active_regions =
|
|
get_unaligned_be16(geometry_buf
|
|
+ GEOMETRY_DESC_HPB_DEVICE_MAX_ACTIVE_REGIONS);
|
|
|
|
SKHPB_DRIVER_D("[48] bHPBRegionSize %u\n", desc->hpb_region_size);
|
|
SKHPB_DRIVER_D("[49] bHPBNumberLU %u\n", desc->hpb_number_lu);
|
|
SKHPB_DRIVER_D("[4A] bHPBSubRegionSize %u\n", desc->hpb_subregion_size);
|
|
SKHPB_DRIVER_D("[4B:4C] wDeviceMaxActiveHPBRegions %u\n",
|
|
desc->hpb_device_max_active_regions);
|
|
|
|
if (desc->hpb_number_lu == 0) {
|
|
SKHPB_DRIVER_E("HPB is not supported\n");
|
|
return -ENODEV;
|
|
}
|
|
/* for activation */
|
|
hba->skhpb_max_regions = desc->hpb_device_max_active_regions;
|
|
return 0;
|
|
}
|
|
|
|
int skhpb_control_validation(struct ufs_hba *hba,
|
|
struct skhpb_config_desc *config)
|
|
{
|
|
unsigned int num_regions = 0;
|
|
int lun;
|
|
|
|
if (!(hba->skhpb_feat & SKHPB_UFS_FEATURE_SUPPORT_HPB_BIT))
|
|
return -ENOTSUPP;
|
|
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
unsigned char *unit = config->unit[lun];
|
|
|
|
if (unit[SKHPB_CONF_LU_ENABLE] >= LU_SET_MAX)
|
|
return -EINVAL;
|
|
|
|
/* total should not exceed max_active_regions */
|
|
num_regions += unit[SKHPB_CONF_ACTIVE_REGIONS] << 8;
|
|
num_regions += unit[SKHPB_CONF_ACTIVE_REGIONS + 1];
|
|
if (num_regions > hba->skhpb_max_regions)
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int skhpb_init(struct ufs_hba *hba)
|
|
{
|
|
struct skhpb_func_desc func_desc;
|
|
int lun, ret, retries;
|
|
int hpb_dev = 0;
|
|
bool do_work;
|
|
|
|
pm_runtime_get_sync(hba->dev);
|
|
ret = skhpb_read_dev_desc_support(hba, &func_desc);
|
|
if (ret)
|
|
goto out_state;
|
|
|
|
ret = skhpb_read_geo_desc_support(hba, &func_desc);
|
|
if (ret)
|
|
goto out_state;
|
|
|
|
for (retries = 0; retries < 20; retries++) {
|
|
if (!hba->lrb_in_use) {
|
|
ret = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_SET_FLAG,
|
|
QUERY_FLAG_IDN_HPB_RESET, NULL);
|
|
if (!ret) {
|
|
SKHPB_DRIVER_I("Query fHPBReset is successfully sent retries = %d\n", retries);
|
|
break;
|
|
}
|
|
} else
|
|
msleep(200);
|
|
}
|
|
if (ret == 0) {
|
|
bool fHPBReset = true;
|
|
|
|
ufshcd_query_flag_retry(hba,
|
|
UPIU_QUERY_OPCODE_READ_FLAG,
|
|
QUERY_FLAG_IDN_HPB_RESET,
|
|
&fHPBReset);
|
|
if (fHPBReset)
|
|
SKHPB_DRIVER_I("fHPBReset still set\n");
|
|
}
|
|
|
|
skhpb_init_constant();
|
|
|
|
do_work = false;
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
struct skhpb_lu_desc lu_desc;
|
|
bool do_work_lun;
|
|
|
|
ret = skhpb_get_hpb_lu_desc(hba, &lu_desc, lun);
|
|
if (ret)
|
|
goto out_state;
|
|
|
|
if (lu_desc.lu_hpb_enable == false)
|
|
continue;
|
|
|
|
hba->skhpb_lup[lun] = kzalloc(sizeof(struct skhpb_lu),
|
|
GFP_KERNEL);
|
|
if (!hba->skhpb_lup[lun]) {
|
|
ret = -ENOMEM;
|
|
goto out_free_mem;
|
|
}
|
|
|
|
ret = skhpb_lu_hpb_init(hba, hba->skhpb_lup[lun],
|
|
&func_desc, &lu_desc, lun, &do_work_lun);
|
|
if (ret) {
|
|
if (ret == -ENODEV)
|
|
continue;
|
|
else
|
|
goto out_free_mem;
|
|
}
|
|
do_work |= do_work_lun;
|
|
hba->skhpb_quicklist_lu_enable[hpb_dev] = lun;
|
|
SKHPB_DRIVER_D("skhpb_quicklist_lu_enable[%d] = %d\n", hpb_dev, lun);
|
|
hpb_dev++;
|
|
}
|
|
|
|
if (hpb_dev)
|
|
goto done;
|
|
|
|
goto out_free_mem;
|
|
|
|
done:
|
|
INIT_WORK(&hba->skhpb_eh_work, skhpb_error_handler);
|
|
hba->skhpb_state = SKHPB_PRESENT;
|
|
hba->issue_ioctl = false;
|
|
pm_runtime_mark_last_busy(hba->dev);
|
|
pm_runtime_put_noidle(hba->dev);
|
|
|
|
if (do_work) {
|
|
for (lun = 0; lun < UFS_UPIU_MAX_GENERAL_LUN; lun++) {
|
|
struct skhpb_lu *hpb = hba->skhpb_lup[lun];
|
|
|
|
if (hpb)
|
|
schedule_delayed_work(&hpb->skhpb_pinned_work, 0);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_free_mem:
|
|
skhpb_release(hba, SKHPB_NOT_SUPPORTED);
|
|
out_state:
|
|
hba->skhpb_state = SKHPB_NOT_SUPPORTED;
|
|
pm_runtime_mark_last_busy(hba->dev);
|
|
pm_runtime_put_noidle(hba->dev);
|
|
return ret;
|
|
}
|
|
|
|
static void skhpb_map_loading_trigger(struct skhpb_lu *hpb,
|
|
bool only_pinned, bool do_work_handler)
|
|
{
|
|
int region, subregion;
|
|
unsigned long flags;
|
|
|
|
if (do_work_handler)
|
|
goto work_out;
|
|
flush_delayed_work(&hpb->skhpb_pinned_work);
|
|
for (region = 0 ; region < hpb->regions_per_lu ; region++) {
|
|
struct skhpb_region *cb;
|
|
|
|
cb = hpb->region_tbl + region;
|
|
|
|
if (cb->region_state != SKHPB_REGION_ACTIVE &&
|
|
!cb->is_pinned)
|
|
continue;
|
|
|
|
if ((only_pinned && cb->is_pinned) ||
|
|
!only_pinned) {
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
for (subregion = 0; subregion < cb->subregion_count;
|
|
subregion++)
|
|
skhpb_add_subregion_to_req_list(hpb,
|
|
cb->subregion_tbl + subregion);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
do_work_handler = true;
|
|
}
|
|
}
|
|
work_out:
|
|
if (do_work_handler)
|
|
schedule_delayed_work(&hpb->skhpb_pinned_work, 0);
|
|
}
|
|
|
|
static void skhpb_purge_active_block(struct skhpb_lu *hpb)
|
|
{
|
|
int region, subregion;
|
|
int state;
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
for (region = 0 ; region < hpb->regions_per_lu ; region++) {
|
|
cb = hpb->region_tbl + region;
|
|
|
|
if (cb->region_state == SKHPB_REGION_INACTIVE) {
|
|
continue;
|
|
}
|
|
|
|
if (cb->is_pinned) {
|
|
state = SKHPB_SUBREGION_DIRTY;
|
|
} else if (cb->region_state == SKHPB_REGION_ACTIVE) {
|
|
state = SKHPB_SUBREGION_UNUSED;
|
|
skhpb_cleanup_lru_info(&hpb->lru_info, cb);
|
|
} else {
|
|
SKHPB_DRIVER_E("Unsupported state of region\n");
|
|
continue;
|
|
}
|
|
|
|
SKHPB_DRIVER_D("region %d state %d dft %d\n",
|
|
region, state,
|
|
hpb->debug_free_table);
|
|
for (subregion = 0 ; subregion < cb->subregion_count;
|
|
subregion++) {
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
skhpb_purge_active_page(hpb, cp, state);
|
|
}
|
|
SKHPB_DRIVER_D("region %d state %d dft %d\n",
|
|
region, state, hpb->debug_free_table);
|
|
}
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
}
|
|
|
|
static void skhpb_retrieve_rsp_info(struct skhpb_lu *hpb)
|
|
{
|
|
struct skhpb_rsp_info *rsp_info;
|
|
unsigned long flags;
|
|
|
|
while (1) {
|
|
spin_lock_irqsave(&hpb->rsp_list_lock, flags);
|
|
rsp_info = list_first_entry_or_null(&hpb->lh_rsp_info,
|
|
struct skhpb_rsp_info, list_rsp_info);
|
|
if (!rsp_info) {
|
|
spin_unlock_irqrestore(&hpb->rsp_list_lock, flags);
|
|
break;
|
|
}
|
|
list_move_tail(&rsp_info->list_rsp_info,
|
|
&hpb->lh_rsp_info_free);
|
|
spin_unlock_irqrestore(&hpb->rsp_list_lock, flags);
|
|
SKHPB_DRIVER_D("add_list %p -> %p",
|
|
&hpb->lh_rsp_info_free, rsp_info);
|
|
}
|
|
}
|
|
|
|
static void skhpb_probe(struct ufs_hba *hba)
|
|
{
|
|
struct skhpb_lu *hpb;
|
|
int lu;
|
|
|
|
for (lu = 0 ; lu < UFS_UPIU_MAX_GENERAL_LUN ; lu++) {
|
|
hpb = hba->skhpb_lup[lu];
|
|
|
|
if (hpb && hpb->lu_hpb_enable) {
|
|
skhpb_cancel_jobs(hpb);
|
|
skhpb_retrieve_rsp_info(hpb);
|
|
skhpb_purge_active_block(hpb);
|
|
SKHPB_DRIVER_I("SKHPB lun %d reset\n", lu);
|
|
// tasklet_init(&hpb->hpb_work_handler,
|
|
// skhpb_work_handler_fn, (unsigned long)hpb);
|
|
}
|
|
}
|
|
hba->skhpb_state = SKHPB_PRESENT;
|
|
}
|
|
|
|
static void skhpb_destroy_subregion_tbl(struct skhpb_lu *hpb,
|
|
struct skhpb_region *cb)
|
|
{
|
|
int subregion;
|
|
|
|
for (subregion = 0 ; subregion < cb->subregion_count ; subregion++) {
|
|
struct skhpb_subregion *cp;
|
|
|
|
cp = cb->subregion_tbl + subregion;
|
|
cp->subregion_state = SKHPB_SUBREGION_UNUSED;
|
|
skhpb_put_map_ctx(hpb, cp->mctx);
|
|
}
|
|
|
|
kfree(cb->subregion_tbl);
|
|
}
|
|
|
|
static void skhpb_destroy_region_tbl(struct skhpb_lu *hpb)
|
|
{
|
|
int region;
|
|
|
|
if (!hpb->region_tbl)
|
|
return;
|
|
|
|
for (region = 0 ; region < hpb->regions_per_lu ; region++) {
|
|
struct skhpb_region *cb;
|
|
|
|
cb = hpb->region_tbl + region;
|
|
if (cb->region_state == SKHPB_REGION_ACTIVE) {
|
|
cb->region_state = SKHPB_REGION_INACTIVE;
|
|
|
|
skhpb_destroy_subregion_tbl(hpb, cb);
|
|
}
|
|
}
|
|
vfree(hpb->region_tbl);
|
|
}
|
|
|
|
void skhpb_suspend(struct ufs_hba *hba)
|
|
{
|
|
int lun;
|
|
unsigned long flags;
|
|
struct skhpb_lu *hpb;
|
|
struct skhpb_rsp_info *rsp_info;
|
|
struct skhpb_map_req *map_req;
|
|
|
|
if (hba->skhpb_quirk & SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP) {
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
hpb = hba->skhpb_lup[lun];
|
|
if (!hpb)
|
|
continue;
|
|
|
|
while (1) {
|
|
spin_lock_irqsave(&hpb->rsp_list_lock, flags);
|
|
rsp_info = list_first_entry_or_null(&hpb->lh_rsp_info,
|
|
struct skhpb_rsp_info, list_rsp_info);
|
|
if (!rsp_info) {
|
|
spin_unlock_irqrestore(&hpb->rsp_list_lock, flags);
|
|
break;
|
|
}
|
|
list_del_init(&rsp_info->list_rsp_info);
|
|
list_add_tail(&rsp_info->list_rsp_info, &hpb->lh_rsp_info_free);
|
|
atomic64_inc(&hpb->canceled_resp);
|
|
spin_unlock_irqrestore(&hpb->rsp_list_lock, flags);
|
|
}
|
|
while (1) {
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
map_req = list_first_entry_or_null(&hpb->lh_map_req_retry,
|
|
struct skhpb_map_req, list_map_req);
|
|
if (!map_req) {
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
break;
|
|
}
|
|
list_del_init(&map_req->list_map_req);
|
|
list_add_tail(&map_req->list_map_req, &hpb->lh_map_req_free);
|
|
atomic64_inc(&hpb->canceled_map_req);
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
}
|
|
}
|
|
}
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
hpb = hba->skhpb_lup[lun];
|
|
if (!hpb)
|
|
continue;
|
|
skhpb_cancel_jobs(hpb);
|
|
}
|
|
}
|
|
|
|
void skhpb_resume(struct ufs_hba *hba)
|
|
{
|
|
int lun;
|
|
struct skhpb_lu *hpb;
|
|
unsigned long flags;
|
|
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
hpb = hba->skhpb_lup[lun];
|
|
if (!hpb)
|
|
continue;
|
|
spin_lock_irqsave(&hpb->map_list_lock, flags);
|
|
if (!list_empty(&hpb->lh_map_req_retry))
|
|
schedule_delayed_work(&hpb->skhpb_map_req_retry_work, msecs_to_jiffies(1000));
|
|
spin_unlock_irqrestore(&hpb->map_list_lock, flags);
|
|
}
|
|
}
|
|
|
|
void skhpb_release(struct ufs_hba *hba, int state)
|
|
{
|
|
int lun;
|
|
|
|
hba->skhpb_state = SKHPB_FAILED;
|
|
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
struct skhpb_lu *hpb = hba->skhpb_lup[lun];
|
|
|
|
if (!hpb)
|
|
continue;
|
|
|
|
hba->skhpb_lup[lun] = NULL;
|
|
|
|
if (!hpb->lu_hpb_enable)
|
|
continue;
|
|
|
|
hpb->lu_hpb_enable = false;
|
|
|
|
skhpb_cancel_jobs(hpb);
|
|
|
|
skhpb_destroy_region_tbl(hpb);
|
|
skhpb_table_mempool_remove(hpb);
|
|
|
|
vfree(hpb->rsp_info);
|
|
vfree(hpb->map_req);
|
|
|
|
kobject_uevent(&hpb->kobj, KOBJ_REMOVE);
|
|
kobject_del(&hpb->kobj); // TODO --count & del?
|
|
|
|
kfree(hpb);
|
|
}
|
|
|
|
if (skhpb_alloc_mctx != 0)
|
|
SKHPB_DRIVER_E("warning: skhpb_alloc_mctx %d", skhpb_alloc_mctx);
|
|
|
|
hba->skhpb_state = state;
|
|
}
|
|
|
|
void skhpb_init_handler(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct ufs_hba *hba =
|
|
container_of(dwork, struct ufs_hba, skhpb_init_work);
|
|
struct Scsi_Host *shost = hba->host;
|
|
bool async_scan;
|
|
|
|
if (hba->skhpb_state == SKHPB_NOT_SUPPORTED)
|
|
return;
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
async_scan = shost->async_scan;
|
|
spin_unlock_irq(shost->host_lock);
|
|
if (async_scan) {
|
|
schedule_delayed_work(dwork, msecs_to_jiffies(100));
|
|
return;
|
|
}
|
|
|
|
if (hba->skhpb_state == SKHPB_NEED_INIT) {
|
|
int err = skhpb_init(hba);
|
|
|
|
if (hba->skhpb_state == SKHPB_NOT_SUPPORTED)
|
|
SKHPB_DRIVER_E("Run without HPB - err=%d\n", err);
|
|
} else if (hba->skhpb_state == SKHPB_RESET) {
|
|
skhpb_probe(hba);
|
|
}
|
|
}
|
|
|
|
void ufshcd_init_hpb(struct ufs_hba *hba)
|
|
{
|
|
int lun;
|
|
|
|
hba->skhpb_feat = 0;
|
|
hba->skhpb_quirk = 0;
|
|
hba->skhpb_state = SKHPB_NEED_INIT;
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
hba->skhpb_lup[lun] = NULL;
|
|
hba->sdev_ufs_lu[lun] = NULL;
|
|
hba->skhpb_quicklist_lu_enable[lun] = SKHPB_U8_MAX;
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&hba->skhpb_init_work, skhpb_init_handler);
|
|
}
|
|
|
|
static void skhpb_error_handler(struct work_struct *work)
|
|
{
|
|
struct ufs_hba *hba;
|
|
|
|
hba = container_of(work, struct ufs_hba, skhpb_eh_work);
|
|
|
|
SKHPB_DRIVER_E("SKHPB driver runs without SKHPB\n");
|
|
SKHPB_DRIVER_E("SKHPB will be removed from the kernel\n");
|
|
|
|
skhpb_release(hba, SKHPB_FAILED);
|
|
}
|
|
|
|
static void skhpb_stat_init(struct skhpb_lu *hpb)
|
|
{
|
|
atomic64_set(&hpb->hit, 0);
|
|
atomic64_set(&hpb->size_miss, 0);
|
|
atomic64_set(&hpb->region_miss, 0);
|
|
atomic64_set(&hpb->subregion_miss, 0);
|
|
atomic64_set(&hpb->entry_dirty_miss, 0);
|
|
atomic64_set(&hpb->w_map_req_cnt, 0);
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
atomic64_set(&hpb->lc_entry_dirty_miss, 0);
|
|
atomic64_set(&hpb->lc_reg_subreg_miss, 0);
|
|
atomic64_set(&hpb->lc_hit, 0);
|
|
#endif
|
|
atomic64_set(&hpb->rb_noti_cnt, 0);
|
|
atomic64_set(&hpb->reset_noti_cnt, 0);
|
|
atomic64_set(&hpb->map_req_cnt, 0);
|
|
atomic64_set(&hpb->region_evict, 0);
|
|
atomic64_set(&hpb->region_add, 0);
|
|
atomic64_set(&hpb->rb_fail, 0);
|
|
atomic64_set(&hpb->canceled_resp, 0);
|
|
atomic64_set(&hpb->canceled_map_req, 0);
|
|
atomic64_set(&hpb->alloc_map_req_cnt, 0);
|
|
}
|
|
|
|
|
|
static ssize_t skhpb_sysfs_info_from_region_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long long value;
|
|
int region, subregion;
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
|
|
if (kstrtoull(buf, 0, &value)) {
|
|
SKHPB_DRIVER_E("kstrtoul error\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (value >= hpb->regions_per_lu) {
|
|
SKHPB_DRIVER_E("value %llu >= regions_per_lu %d error\n",
|
|
value, hpb->regions_per_lu);
|
|
return -EINVAL;
|
|
}
|
|
|
|
region = (int)value;
|
|
cb = hpb->region_tbl + region;
|
|
|
|
SKHPB_DRIVER_I("get_info_from_region[%d]=", region);
|
|
SKHPB_DRIVER_I("region %u state %s", region,
|
|
((cb->region_state == SKHPB_REGION_INACTIVE) ? "INACTIVE" :
|
|
((cb->region_state == SKHPB_REGION_ACTIVE) ? "ACTIVE" : "INVALID"))
|
|
);
|
|
for (subregion = 0; subregion < cb->subregion_count; subregion++) {
|
|
cp = cb->subregion_tbl + subregion;
|
|
SKHPB_DRIVER_I("subregion %u state %s", subregion,
|
|
((cp->subregion_state == SKHPB_SUBREGION_UNUSED) ? "UNUSED" :
|
|
((cp->subregion_state == SKHPB_SUBREGION_DIRTY) ? "DIRTY" :
|
|
((cp->subregion_state == SKHPB_SUBREGION_CLEAN) ? "CLEAN" :
|
|
(cp->subregion_state == SKHPB_SUBREGION_ISSUED) ?
|
|
"ISSUED" : "INVALID")))
|
|
);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_info_from_lba_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
skhpb_t ppn;
|
|
unsigned long long value;
|
|
unsigned int lpn;
|
|
int region, subregion, subregion_offset;
|
|
struct skhpb_region *cb;
|
|
struct skhpb_subregion *cp;
|
|
unsigned long flags;
|
|
int dirty;
|
|
|
|
if (kstrtoull(buf, 0, &value)) {
|
|
SKHPB_DRIVER_E("kstrtoul error\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (value > hpb->lu_num_blocks * SKHPB_SECTORS_PER_BLOCK) {
|
|
SKHPB_DRIVER_E("value %llu > lu_num_blocks %llu error\n",
|
|
value, hpb->lu_num_blocks);
|
|
return -EINVAL;
|
|
}
|
|
lpn = value / SKHPB_SECTORS_PER_BLOCK;
|
|
|
|
skhpb_get_pos_from_lpn(hpb, lpn, ®ion, &subregion,
|
|
&subregion_offset);
|
|
if (!skhpb_check_region_subregion_validity(hpb, region, subregion))
|
|
return -EINVAL;
|
|
cb = hpb->region_tbl + region;
|
|
cp = cb->subregion_tbl + subregion;
|
|
|
|
if (cb->region_state != SKHPB_REGION_INACTIVE) {
|
|
ppn = skhpb_get_ppn(cp->mctx, subregion_offset);
|
|
spin_lock_irqsave(&hpb->hpb_lock, flags);
|
|
dirty = skhpb_ppn_dirty_check(hpb, cp, subregion_offset);
|
|
spin_unlock_irqrestore(&hpb->hpb_lock, flags);
|
|
} else {
|
|
ppn = 0;
|
|
dirty = -1;
|
|
}
|
|
SKHPB_DRIVER_I("get_info_from_lba[%llu]=", value);
|
|
SKHPB_DRIVER_I("sector %llu region %d state %s subregion %d state %s",
|
|
value, region,
|
|
((cb->region_state == SKHPB_REGION_INACTIVE) ? "INACTIVE" :
|
|
((cb->region_state == SKHPB_REGION_ACTIVE) ? "ACTIVE" : "INVALID")),
|
|
subregion,
|
|
((cp->subregion_state == SKHPB_SUBREGION_UNUSED) ? "UNUSED" :
|
|
((cp->subregion_state == SKHPB_SUBREGION_DIRTY) ? "DIRTY" :
|
|
((cp->subregion_state == SKHPB_SUBREGION_CLEAN) ? "CLEAN" :
|
|
(cp->subregion_state == SKHPB_SUBREGION_ISSUED) ?
|
|
"ISSUED":"INVALID")))
|
|
);
|
|
SKHPB_DRIVER_I("sector %llu lpn %u ppn %llx dirty %d",
|
|
value, lpn, ppn, dirty);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_map_req_show(struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"map_req_count[RB_NOTI RESET_NOTI MAP_REQ]= %lld %lld %lld\n",
|
|
(long long)atomic64_read(&hpb->rb_noti_cnt),
|
|
(long long)atomic64_read(&hpb->reset_noti_cnt),
|
|
(long long)atomic64_read(&hpb->map_req_cnt));
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_count_reset_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long debug;
|
|
|
|
if (kstrtoul(buf, 0, &debug))
|
|
return -EINVAL;
|
|
|
|
skhpb_stat_init(hpb);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_add_evict_show(struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "add_evict_count[ADD EVICT]= %lld %lld\n",
|
|
(long long)atomic64_read(&hpb->region_add),
|
|
(long long)atomic64_read(&hpb->region_evict));
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_statistics_show(struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
long long size_miss, region_miss, subregion_miss, entry_dirty_miss,
|
|
hit, miss_all, rb_fail, canceled_resp, canceled_map_req;
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
long long lc_dirty_miss = 0, lc_state_miss = 0, lc_hit = 0;
|
|
#endif
|
|
int count = 0;
|
|
|
|
hit = atomic64_read(&hpb->hit);
|
|
size_miss = atomic64_read(&hpb->size_miss);
|
|
region_miss = atomic64_read(&hpb->region_miss);
|
|
subregion_miss = atomic64_read(&hpb->subregion_miss);
|
|
entry_dirty_miss = atomic64_read(&hpb->entry_dirty_miss);
|
|
rb_fail = atomic64_read(&hpb->rb_fail);
|
|
canceled_resp = atomic64_read(&hpb->canceled_resp);
|
|
canceled_map_req = atomic64_read(&hpb->canceled_map_req);
|
|
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
lc_dirty_miss = atomic64_read(&hpb->lc_entry_dirty_miss);
|
|
lc_state_miss = atomic64_read(&hpb->lc_reg_subreg_miss);
|
|
lc_hit = atomic64_read(&hpb->lc_hit);
|
|
entry_dirty_miss += lc_dirty_miss;
|
|
subregion_miss += lc_state_miss;
|
|
#endif
|
|
miss_all = size_miss + region_miss + subregion_miss + entry_dirty_miss;
|
|
count += snprintf(buf + count, PAGE_SIZE,
|
|
"Total: %lld\nHit_Counts: %lld\n",
|
|
hit + miss_all, hit);
|
|
count += snprintf(buf + count, PAGE_SIZE,
|
|
"Miss_Counts[ALL SIZE REG SUBREG DIRTY]= %lld %lld %lld %lld %lld\n",
|
|
miss_all, size_miss, region_miss, subregion_miss, entry_dirty_miss);
|
|
|
|
#if defined(SKHPB_READ_LARGE_CHUNK_SUPPORT)
|
|
count += snprintf(buf + count, PAGE_SIZE,
|
|
"LARG_CHNK Hit_Count: %lld, miss_count[ALL DIRTY REG_SUBREG]= %lld %lld %lld\n",
|
|
lc_hit, lc_dirty_miss + lc_state_miss, lc_dirty_miss, lc_state_miss);
|
|
#endif
|
|
if (hpb->hba->skhpb_quirk & SKHPB_QUIRK_PURGE_HINT_INFO_WHEN_SLEEP)
|
|
count += snprintf(buf + count, PAGE_SIZE,
|
|
"READ_BUFFER miss_count[RB_FAIL CANCEL_RESP CANCEL_MAP]= %lld %lld %lld\n",
|
|
rb_fail, canceled_resp, canceled_map_req);
|
|
else
|
|
count += snprintf(buf + count, PAGE_SIZE,
|
|
"READ_BUFFER miss_count[RB_FAIL]= %lld\n", rb_fail);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_version_show(struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"HPBversion[HPB DD]= %x.%x.%x %x.%x.%x\n",
|
|
(hpb->hpb_ver >> 8) & 0xf,
|
|
(hpb->hpb_ver >> 4) & 0xf,
|
|
(hpb->hpb_ver >> 0) & 0xf,
|
|
SKHPB_GET_BYTE_2(SKHPB_DD_VER),
|
|
SKHPB_GET_BYTE_1(SKHPB_DD_VER),
|
|
SKHPB_GET_BYTE_0(SKHPB_DD_VER));
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_active_count_show(
|
|
struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
struct skhpb_region *cb;
|
|
int region;
|
|
int pinned_cnt = 0;
|
|
|
|
for (region = 0 ; region < hpb->regions_per_lu ; region++) {
|
|
cb = hpb->region_tbl + region;
|
|
|
|
if (cb->is_pinned && cb->region_state == SKHPB_REGION_ACTIVE)
|
|
pinned_cnt++;
|
|
}
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"active_count[ACTIVE_CNT PINNED_CNT]= %ld %d\n",
|
|
atomic64_read(&hpb->lru_info.active_count), pinned_cnt);
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_hpb_disable_show(
|
|
struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"[0] BOTH_enabled= %d\
|
|
\n[1] ONLY_HPB_BUFFER_disabled= %d\
|
|
\n[2] ONLY_HPB_READ_disabled= %d\
|
|
\n[3] BOTH_disabled= %d\n",
|
|
(hpb->force_map_req_disable == 0 &&
|
|
hpb->force_hpb_read_disable == 0)?1:0,
|
|
hpb->force_map_req_disable,
|
|
hpb->force_hpb_read_disable,
|
|
(hpb->force_map_req_disable == 1 &&
|
|
hpb->force_hpb_read_disable == 1)?1:0);
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_hpb_disable_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long value;
|
|
|
|
if (kstrtoul(buf, 0, &value))
|
|
return -EINVAL;
|
|
|
|
if (value > 3) {
|
|
SKHPB_DRIVER_E("Error, Only [0-3] is valid:\
|
|
\n[0] BOTH_enable\
|
|
\n[1] ONLY_HPB_BUFFER_disable\
|
|
\n[2] ONLY_HPB_READ_disable\
|
|
\n[3] BOTH_disabie\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
hpb->force_map_req_disable = value & 0x1;
|
|
hpb->force_hpb_read_disable = value & 0x2;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_hpb_reset_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long value;
|
|
unsigned int doorbel;
|
|
struct skhpb_rsp_info *rsp_info;
|
|
unsigned long flags;
|
|
int ret, retries, lun;
|
|
struct skhpb_lu *hpb_lu;
|
|
|
|
if (kstrtoul(buf, 0, &value))
|
|
return -EINVAL;
|
|
if (!value)
|
|
return count;
|
|
|
|
for (retries = 1; retries <= 20; retries++) {
|
|
pm_runtime_get_sync(SKHPB_DEV(hpb));
|
|
ufshcd_hold(hpb->hba, false);
|
|
doorbel = ufshcd_readl(hpb->hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
|
|
ufshcd_release(hpb->hba);
|
|
pm_runtime_mark_last_busy(SKHPB_DEV(hpb));
|
|
pm_runtime_put_noidle(SKHPB_DEV(hpb));
|
|
|
|
if (!doorbel) {
|
|
pm_runtime_get_sync(SKHPB_DEV(hpb));
|
|
ret = ufshcd_query_flag_retry(hpb->hba,
|
|
UPIU_QUERY_OPCODE_SET_FLAG,
|
|
QUERY_FLAG_IDN_HPB_RESET,
|
|
NULL);
|
|
pm_runtime_mark_last_busy(SKHPB_DEV(hpb));
|
|
pm_runtime_put_noidle(SKHPB_DEV(hpb));
|
|
if (!ret) {
|
|
SKHPB_DRIVER_I("Query fHPBReset is successfully sent\n");
|
|
break;
|
|
}
|
|
}
|
|
SKHPB_DRIVER_I("fHPBReset failed and will retry[%d] after some time, DOORBELL: 0x%x\n",
|
|
retries, doorbel);
|
|
msleep(200);
|
|
}
|
|
if (ret == 0) {
|
|
bool fHPBReset = true;
|
|
pm_runtime_get_sync(SKHPB_DEV(hpb));
|
|
ufshcd_query_flag_retry(hpb->hba,
|
|
UPIU_QUERY_OPCODE_READ_FLAG,
|
|
QUERY_FLAG_IDN_HPB_RESET,
|
|
&fHPBReset);
|
|
pm_runtime_mark_last_busy(SKHPB_DEV(hpb));
|
|
pm_runtime_put_noidle(SKHPB_DEV(hpb));
|
|
if (fHPBReset)
|
|
SKHPB_DRIVER_E("fHPBReset is still in progress at device, but keep going\n");
|
|
} else {
|
|
SKHPB_DRIVER_E("Fail to set fHPBReset flag\n");
|
|
goto out;
|
|
}
|
|
flush_delayed_work(&hpb->skhpb_pinned_work);
|
|
for (lun = 0 ; lun < UFS_UPIU_MAX_GENERAL_LUN ; lun++) {
|
|
hpb_lu = hpb->hba->skhpb_lup[lun];
|
|
if (!hpb_lu || !hpb_lu->lu_hpb_enable)
|
|
continue;
|
|
|
|
skhpb_stat_init(hpb_lu);
|
|
|
|
spin_lock_irqsave(&hpb_lu->rsp_list_lock, flags);
|
|
rsp_info = skhpb_get_req_info(hpb_lu);
|
|
spin_unlock_irqrestore(&hpb_lu->rsp_list_lock, flags);
|
|
if (!rsp_info)
|
|
goto out;
|
|
rsp_info->type = SKHPB_RSP_HPB_RESET;
|
|
SKHPB_RSP_TIME(rsp_info->RSP_start);
|
|
spin_lock_irqsave(&hpb_lu->rsp_list_lock, flags);
|
|
list_add_tail(&rsp_info->list_rsp_info, &hpb_lu->lh_rsp_info);
|
|
spin_unlock_irqrestore(&hpb_lu->rsp_list_lock, flags);
|
|
SKHPB_DRIVER_I("Host HPB reset start LU%d - fHPBReset\n", lun);
|
|
schedule_work(&hpb_lu->skhpb_rsp_work);
|
|
}
|
|
out:
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_debug_log_show(
|
|
struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
int value = skhpb_debug_mask;
|
|
|
|
if (value == (SKHPB_LOG_OFF)) {
|
|
value = SKHPB_LOG_LEVEL_OFF;
|
|
} else if (value == (SKHPB_LOG_ERR)) {
|
|
value = SKHPB_LOG_LEVEL_ERR;
|
|
} else if (value == (SKHPB_LOG_ERR | SKHPB_LOG_INFO)) {
|
|
value = SKHPB_LOG_LEVEL_INFO;
|
|
} else if (value == (SKHPB_LOG_ERR | SKHPB_LOG_INFO | SKHPB_LOG_DEBUG)) {
|
|
value = SKHPB_LOG_LEVEL_DEBUG;
|
|
} else if (value == (SKHPB_LOG_ERR | SKHPB_LOG_INFO | SKHPB_LOG_DEBUG | SKHPB_LOG_HEX)) {
|
|
value = SKHPB_LOG_LEVEL_HEX;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE, "[0] : LOG_LEVEL_OFF\
|
|
\n[1] : LOG_LEVEL_ERR\
|
|
\n[2] : LOG_LEVEL_INFO\
|
|
\n[3] : LOG_LEVEL_DEBUG\
|
|
\n[4] : LOG_LEVEL_HEX\
|
|
\n-----------------------\
|
|
\nLog-Level = %d\n", value);
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_debug_log_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long value;
|
|
|
|
if (kstrtoul(buf, 0, &value))
|
|
return -EINVAL;
|
|
|
|
if (value > SKHPB_LOG_LEVEL_HEX) {
|
|
SKHPB_DRIVER_E("Error, Only [0-4] is valid:\
|
|
\n[0] : LOG_LEVEL_OFF\
|
|
\n[1] : LOG_LEVEL_ERR\
|
|
\n[2] : LOG_LEVEL_INFO\
|
|
\n[3] : LOG_LEVEL_DEBUG\
|
|
\n[4] : LOG_LEVEL_HEX\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (value == SKHPB_LOG_LEVEL_OFF) {
|
|
skhpb_debug_mask = SKHPB_LOG_OFF;
|
|
} else if (value == SKHPB_LOG_LEVEL_ERR) {
|
|
skhpb_debug_mask = SKHPB_LOG_ERR;
|
|
} else if (value == SKHPB_LOG_LEVEL_INFO) {
|
|
skhpb_debug_mask = SKHPB_LOG_ERR | SKHPB_LOG_INFO;
|
|
} else if (value == SKHPB_LOG_LEVEL_DEBUG) {
|
|
skhpb_debug_mask = SKHPB_LOG_ERR | SKHPB_LOG_INFO | SKHPB_LOG_DEBUG;
|
|
} else if (value == SKHPB_LOG_LEVEL_HEX) {
|
|
skhpb_debug_mask = SKHPB_LOG_ERR | SKHPB_LOG_INFO | SKHPB_LOG_DEBUG | SKHPB_LOG_HEX;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_rsp_time_show(struct skhpb_lu *hpb, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"map_req_time: %s\n", (debug_map_req ? "Enable":"Disable"));
|
|
}
|
|
|
|
static ssize_t skhpb_sysfs_rsp_time_store(struct skhpb_lu *hpb,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long value;
|
|
|
|
if (kstrtoul(buf, 0, &value))
|
|
return -EINVAL;
|
|
|
|
if (value > 1)
|
|
return count;
|
|
|
|
debug_map_req = value;
|
|
return count;
|
|
}
|
|
|
|
static struct skhpb_sysfs_entry skhpb_sysfs_entries[] = {
|
|
__ATTR(hpb_disable, 0644,
|
|
skhpb_sysfs_hpb_disable_show,
|
|
skhpb_sysfs_hpb_disable_store),
|
|
__ATTR(HPBVersion, 0444, skhpb_sysfs_version_show, NULL),
|
|
__ATTR(statistics, 0444, skhpb_sysfs_statistics_show, NULL),
|
|
__ATTR(active_count, 0444, skhpb_sysfs_active_count_show, NULL),
|
|
__ATTR(add_evict_count, 0444, skhpb_sysfs_add_evict_show, NULL),
|
|
__ATTR(count_reset, 0200, NULL, skhpb_sysfs_count_reset_store),
|
|
__ATTR(map_req_count, 0444, skhpb_sysfs_map_req_show, NULL),
|
|
__ATTR(get_info_from_region, 0200, NULL,
|
|
skhpb_sysfs_info_from_region_store),
|
|
__ATTR(get_info_from_lba, 0200, NULL, skhpb_sysfs_info_from_lba_store),
|
|
__ATTR(hpb_reset, 0200, NULL, skhpb_sysfs_hpb_reset_store),
|
|
__ATTR(debug_log, 0644,
|
|
skhpb_sysfs_debug_log_show,
|
|
skhpb_sysfs_debug_log_store),
|
|
__ATTR(response_time, 0644,
|
|
skhpb_sysfs_rsp_time_show,
|
|
skhpb_sysfs_rsp_time_store),
|
|
__ATTR_NULL
|
|
};
|
|
|
|
static ssize_t skhpb_attr_show(struct kobject *kobj,
|
|
struct attribute *attr, char *page)
|
|
{
|
|
struct skhpb_sysfs_entry *entry;
|
|
struct skhpb_lu *hpb;
|
|
ssize_t error;
|
|
|
|
entry = container_of(attr,
|
|
struct skhpb_sysfs_entry, attr);
|
|
hpb = container_of(kobj, struct skhpb_lu, kobj);
|
|
|
|
if (!entry->show)
|
|
return -EIO;
|
|
|
|
mutex_lock(&hpb->sysfs_lock);
|
|
error = entry->show(hpb, page);
|
|
mutex_unlock(&hpb->sysfs_lock);
|
|
return error;
|
|
}
|
|
|
|
static ssize_t skhpb_attr_store(struct kobject *kobj,
|
|
struct attribute *attr,
|
|
const char *page, size_t length)
|
|
{
|
|
struct skhpb_sysfs_entry *entry;
|
|
struct skhpb_lu *hpb;
|
|
ssize_t error;
|
|
|
|
entry = container_of(attr, struct skhpb_sysfs_entry, attr);
|
|
hpb = container_of(kobj, struct skhpb_lu, kobj);
|
|
|
|
if (!entry->store)
|
|
return -EIO;
|
|
|
|
mutex_lock(&hpb->sysfs_lock);
|
|
error = entry->store(hpb, page, length);
|
|
mutex_unlock(&hpb->sysfs_lock);
|
|
return error;
|
|
}
|
|
|
|
static const struct sysfs_ops skhpb_sysfs_ops = {
|
|
.show = skhpb_attr_show,
|
|
.store = skhpb_attr_store,
|
|
};
|
|
|
|
static struct kobj_type skhpb_ktype = {
|
|
.sysfs_ops = &skhpb_sysfs_ops,
|
|
.release = NULL,
|
|
};
|
|
|
|
static int skhpb_create_sysfs(struct ufs_hba *hba,
|
|
struct skhpb_lu *hpb)
|
|
{
|
|
struct device *dev = hba->dev;
|
|
struct skhpb_sysfs_entry *entry;
|
|
int err;
|
|
|
|
hpb->sysfs_entries = skhpb_sysfs_entries;
|
|
|
|
skhpb_stat_init(hpb);
|
|
|
|
kobject_init(&hpb->kobj, &skhpb_ktype);
|
|
mutex_init(&hpb->sysfs_lock);
|
|
|
|
err = kobject_add(&hpb->kobj, kobject_get(&dev->kobj),
|
|
"ufshpb_lu%d", hpb->lun);
|
|
if (!err) {
|
|
for (entry = hpb->sysfs_entries;
|
|
entry->attr.name != NULL ; entry++) {
|
|
if (sysfs_create_file(&hpb->kobj, &entry->attr))
|
|
break;
|
|
}
|
|
kobject_uevent(&hpb->kobj, KOBJ_ADD);
|
|
}
|
|
return err;
|
|
}
|