kernel_samsung_a53x/drivers/mmc/host/mmc-sec-feature.c
2024-06-15 16:02:09 -03:00

339 lines
9.6 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Samsung Specific feature
*
* Copyright (C) 2021 Samsung Electronics Co., Ltd.
*
* Authors:
* Storage Driver <storage.sec@samsung.com>
*/
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/sec_class.h>
#include "dw_mmc-exynos.h"
#include "mmc-sec-feature.h"
#include "mmc-sec-sysfs.h"
struct mmc_sec_op_info sd_op_info;
/*
* These are for SDcard error counting.
*
* sd_err_info
* : accumulated err sum while SDcard is presented.
* sd_status_err_info
* : accumulated status err sum while SDcard is presented.
*
* sd_err_info_backup
* : backup for sd_err_info after HQM reads sd_err_info.
* sd_status_err_info_backup
* : backup for sd_status_err_info after HQM reads sd_status_err_info.
*/
struct mmc_sec_err_info sd_err_info[MAX_MMC_HOST_IDX][MAX_ERR_LOG_INDEX];
struct mmc_sec_status_err_info sd_status_err_info[MAX_MMC_HOST_IDX];
struct mmc_sec_err_info sd_err_info_backup[MAX_MMC_HOST_IDX][MAX_ERR_LOG_INDEX];
struct mmc_sec_status_err_info sd_status_err_info_backup[MAX_MMC_HOST_IDX];
struct device *sd_detection_cmd_dev;
struct device *sd_data_cmd_dev;
struct device *sd_info_cmd_dev;
static int mmc_sec_sdcard_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct dw_mci *host = dev_get_drvdata(dev);
int host_idx;
int retval;
bool card_exist;
add_uevent_var(env, "DEVNAME=%s", dev->kobj.name);
if (host->slot && host->slot->mmc && host->slot->mmc->card) {
card_exist = true;
host_idx = host->slot->mmc->index;
} else
card_exist = false;
retval = add_uevent_var(env, "IOERROR=%s", card_exist ? (
((sd_status_err_info[host_idx].ge_cnt && !(sd_status_err_info[host_idx].ge_cnt % 1000)) ||
(sd_status_err_info[host_idx].ecc_cnt && !(sd_status_err_info[host_idx].ecc_cnt % 1000)) ||
(sd_status_err_info[host_idx].wp_cnt && !(sd_status_err_info[host_idx].wp_cnt % 100)) ||
(sd_status_err_info[host_idx].oor_cnt && !(sd_status_err_info[host_idx].oor_cnt % 100)))
? "YES" : "NO") : "NoCard");
return retval;
}
static void mmc_sec_sdcard_noti_work(struct work_struct *work)
{
struct mmc_sec_op_info *op_info = container_of(work, struct mmc_sec_op_info, noti_work);
int host_idx = op_info->mmc->index;
int ret;
if (!op_info->mmc->card)
return;
pr_info("%s: Send Notification about SD Card IO Error\n", mmc_hostname(op_info->mmc));
sd_status_err_info[host_idx].noti_cnt++;
ret = kobject_uevent(&sd_detection_cmd_dev->kobj, KOBJ_CHANGE);
if (ret)
pr_err("%s: Failed to Send Uevent with err %d\n", __func__, ret);
}
static struct device_type sdcard_type = {
.uevent = mmc_sec_sdcard_uevent,
};
static void mmc_error_count_log(int host_idx, int index, int error, u32 status)
{
struct mmc_sec_err_info *err_log = sd_err_info[host_idx];
int i = 0;
int cpu = raw_smp_processor_id();
if (!error)
return;
/*
* -EIO (-5) : SDMMC_INT_RESP_ERR error case. So log as CRC.
* -ENOMEDIUM (-123), etc : SW timeout and other error. So log as TIMEOUT.
*/
switch (error) {
case -EIO:
error = -EILSEQ;
break;
case -EILSEQ:
break;
default:
error = -ETIMEDOUT;
break;
}
for (i = 0; i < MAX_ERR_TYPE_INDEX; i++) {
if (err_log[index + i].err_type == error) {
index += i;
break;
}
}
if (i >= MAX_ERR_TYPE_INDEX)
return;
// 1st status logging or not trans
if (!err_log[index].status || !(R1_CURRENT_STATE(status) & R1_STATE_TRAN))
err_log[index].status = status;
if (!err_log[index].first_issue_time)
err_log[index].first_issue_time = cpu_clock(cpu);
err_log[index].last_issue_time = cpu_clock(cpu);
err_log[index].count++;
}
static void mmc_check_status_err(struct mmc_card *card, int host_idx, u32 status)
{
bool noti = false;
if (status & R1_ERROR) {
sd_status_err_info[host_idx].ge_cnt++;
if (!(sd_status_err_info[host_idx].ge_cnt % 1000))
noti = true;
}
if (status & R1_CC_ERROR)
sd_status_err_info[host_idx].cc_cnt++;
if (status & R1_CARD_ECC_FAILED) {
sd_status_err_info[host_idx].ecc_cnt++;
if (!(sd_status_err_info[host_idx].ecc_cnt % 1000))
noti = true;
}
if (status & R1_WP_VIOLATION) {
sd_status_err_info[host_idx].wp_cnt++;
if (!(sd_status_err_info[host_idx].wp_cnt % 100))
noti = true;
}
if (status & R1_OUT_OF_RANGE) {
sd_status_err_info[host_idx].oor_cnt++;
if (!(sd_status_err_info[host_idx].oor_cnt % 100))
noti = true;
}
/*
* Make Notification about SD Card Errors
*
* Condition :
* GE, ECC : Every 1000 errors
* WP, OOR : Every 100 errors
*/
if (noti && mmc_card_sd(card) && sd_detection_cmd_dev)
schedule_work(&sd_op_info.noti_work);
}
static void mmc_card_error_logging(struct mmc_host *mmc, struct mmc_request *mrq)
{
int host_idx = mmc->index;
u32 status = 0;
status = (mrq->sbc ? mrq->sbc->resp[0] : 0) |
(mrq->stop ? mrq->stop->resp[0] : 0) |
(mrq->cmd ? mrq->cmd->resp[0] : 0);
if (status & STATUS_MASK)
mmc_check_status_err(mmc->card, host_idx, status);
if (mrq->sbc && mrq->sbc->error)
mmc_error_count_log(host_idx, MMC_LOG_SBC_OFFSET, mrq->sbc->error, status);
if (mrq->cmd && mrq->cmd->error)
mmc_error_count_log(host_idx, MMC_LOG_CMD_OFFSET, mrq->cmd->error, status);
if (mrq->data && mrq->data->error)
mmc_error_count_log(host_idx, MMC_LOG_DATA_OFFSET, mrq->data->error, status);
if (mrq->stop && mrq->stop->error)
mmc_error_count_log(host_idx, MMC_LOG_STOP_OFFSET, mrq->stop->error, status);
/*
* in block.c
* #define MMC_BLK_TIMEOUT_MS (20 * 1000)
* refer to card_busy_detect()
* so, check CMD13's response(status)
* if there is no other CMD for 18secs or more.
*/
if (mrq->cmd->opcode == MMC_SEND_STATUS &&
time_after(jiffies, sd_op_info.tstamp_last_cmd + msecs_to_jiffies(18 * 1000))) {
if (status && (!(status & R1_READY_FOR_DATA) ||
(R1_CURRENT_STATE(status) == R1_STATE_PRG))) {
// card stuck in prg state
mmc_error_count_log(host_idx, MMC_LOG_BUSY_OFFSET, -ETIMEDOUT, status);
// not to check card busy again
sd_op_info.tstamp_last_cmd = jiffies;
}
}
}
void mmc_sec_check_request_error(struct dw_mci *host, struct mmc_request *mrq)
{
struct mmc_host *mmc = host->slot->mmc;
if (!mmc->card || !mrq || !mrq->cmd)
return;
/* return if the cmd is tuning block */
if (((mrq->cmd->opcode == MMC_SEND_TUNING_BLOCK) ||
(mrq->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200)))
return;
/* set CMD(except CMD13) timestamp to check card stuck */
if (mrq->cmd->opcode != MMC_SEND_STATUS)
sd_op_info.tstamp_last_cmd = jiffies;
/*
* cmd->flags info
* #define MMC_CMD_MASK (3 << 5) : non-SPI command type
* #define MMC_CMD_AC (0 << 5) : Addressed (point-to-point) commands
* #define MMC_CMD_ADTC (1 << 5) : Addressed (point-to-point) data transfer commands
* #define MMC_CMD_BC (2 << 5) : Broadcast commands
* #define MMC_CMD_BCR (3 << 5) : Broadcast commands with response
*
* We should log the errors in case of AC or ADTC type
*/
if ((mrq->cmd->flags & MMC_RSP_PRESENT) &&
!(mrq->cmd->flags & MMC_CMD_BC)) {
/* no need to check cmd flag has MMC_RSP_136 set */
/*
* return immediately if the cmd->opcode is MMC_APP_CMD
* in SDcard case, CMD55 is sent with MMC_CMD_AC flag
* in eMMC case, CMD55 is not used
*/
if ((mrq->cmd->flags & MMC_RSP_136) ||
(mrq->cmd->opcode == MMC_APP_CMD))
return;
mmc_card_error_logging(mmc, mrq);
}
}
void mmc_sec_sdcard_clear_err_count(struct mmc_host *host)
{
int host_idx = host->index;
struct mmc_sec_err_info *err_log = sd_err_info[host_idx];
int i = 0;
for (i = 0; i < MAX_ERR_LOG_INDEX; i++) {
err_log[i].status = 0;
err_log[i].first_issue_time = 0;
err_log[i].last_issue_time = 0;
err_log[i].count = 0;
}
memset(sd_err_info_backup[host_idx], 0,
sizeof(struct mmc_sec_err_info) * MAX_ERR_LOG_INDEX);
memset(&sd_status_err_info[host_idx], 0, sizeof(struct mmc_sec_status_err_info));
memset(&sd_status_err_info_backup[host_idx], 0, sizeof(struct mmc_sec_status_err_info));
}
void mmc_sec_init_err_count(struct dw_mci *host)
{
struct mmc_host *mmc = host->slot->mmc;
int host_idx = mmc->index;
static const char *const req_types[] = {
"sbc ", "cmd ", "data ", "stop ", "busy "
};
int i = 0;
struct mmc_sec_err_info *err_log = sd_err_info[host_idx];
/*
* Init err_log[]
* //sbc
* card->err_log[0].err_type = -EILSEQ;
* card->err_log[1].err_type = -ETIMEDOUT;
* ...
*/
for (i = 0; i < MAX_ERR_LOG_INDEX; i++) {
snprintf(err_log[i].type, sizeof(char) * 5, "%s",
req_types[i / MAX_ERR_TYPE_INDEX]);
err_log[i].err_type =
(i % MAX_ERR_TYPE_INDEX == 0) ? -EILSEQ : -ETIMEDOUT;
}
}
void mmc_sec_detect_interrupt(struct dw_mci *host)
{
struct mmc_host *mmc;
if (sd_op_info.card_detect_cnt < UINT_MAX)
sd_op_info.card_detect_cnt++;
if (host->slot && host->slot->mmc) {
mmc = host->slot->mmc;
mmc->trigger_card_event = true;
mmc_sec_sdcard_clear_err_count(mmc);
}
}
void mmc_set_sec_features(struct platform_device *pdev)
{
struct dw_mci *host = dev_get_drvdata(&pdev->dev);
struct device_node *np = host->dev->of_node;
struct dw_mci_exynos_priv_data *priv = host->priv;
if (of_get_property(np, "sec-sd-slot-type", NULL))
of_property_read_u32(np,
"sec-sd-slot-type", &priv->sec_sd_slot_type);
else {
if (priv->cd_gpio != -1) /* treat default SD slot if cd_gpio is defined */
priv->sec_sd_slot_type = SEC_HOTPLUG_SD_SLOT;
else
priv->sec_sd_slot_type = -1;
}
mmc_sec_init_err_count(host);
mmc_sec_init_sysfs(host);
/* SDcard uevent register. */
if ((priv->sec_sd_slot_type) >= SEC_NO_DET_SD_SLOT) {
sd_op_info.mmc = host->slot->mmc;
sd_detection_cmd_dev->type = &sdcard_type;
INIT_WORK(&sd_op_info.noti_work, mmc_sec_sdcard_noti_work);
}
}