// SPDX-License-Identifier: GPL-2.0 /* * Samsung Specific feature * * Copyright (C) 2021 Samsung Electronics Co., Ltd. * * Authors: * Storage Driver */ #include #include #include #include #include #include "../core/host.h" #include "../core/card.h" #include "mmc-sec-sysfs.h" #define MSDC_EMMC (0) #define MSDC_SD (1) #define MSDC_SDIO (2) #define UNSTUFF_BITS(resp, start, size) \ ({ \ const int __size = size; \ const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \ const int __off = 3 - ((start) / 32); \ const int __shft = (start) & 31; \ u32 __res; \ __res = resp[__off] >> __shft; \ if (__size + __shft > 32) \ __res |= resp[__off-1] << ((32 - __shft) % 32); \ __res & __mask; \ }) static inline void mmc_check_error_count(struct mmc_card_error_log *err_log, unsigned long long *total_c_cnt, unsigned long long *total_t_cnt) { int i = 0; //Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. for (i = 0; i < 6; i++) { if (err_log[i].err_type == -EILSEQ && *total_c_cnt < MAX_CNT_U64) *total_c_cnt += err_log[i].count; if (err_log[i].err_type == -ETIMEDOUT && *total_t_cnt < MAX_CNT_U64) *total_t_cnt += err_log[i].count; } } /* SYSFS about eMMC info */ static struct device *mmc_sec_dev; /* SYSFS about SD Card Detection */ static struct device *sdcard_sec_dev; /* SYSFS about SD Card Information */ static struct device *sdinfo_sec_dev; /* SYSFS about SD Card error Information */ static struct device *sddata_sec_dev; static ssize_t mmc_gen_unique_number_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; char gen_pnm[3]; int i; switch (card->cid.manfid) { case 0x02: /* Sandisk -> [3][4] */ case 0x45: sprintf(gen_pnm, "%.*s", 2, card->cid.prod_name + 3); break; case 0x11: /* Toshiba -> [1][2] */ case 0x90: /* Hynix */ sprintf(gen_pnm, "%.*s", 2, card->cid.prod_name + 1); break; case 0x13: case 0xFE: /* Micron -> [4][5] */ sprintf(gen_pnm, "%.*s", 2, card->cid.prod_name + 4); break; case 0x15: /* Samsung -> [0][1] */ default: sprintf(gen_pnm, "%.*s", 2, card->cid.prod_name + 0); break; } /* Convert to Capital */ for (i = 0 ; i < 2 ; i++) { if (gen_pnm[i] >= 'a' && gen_pnm[i] <= 'z') gen_pnm[i] -= ('a' - 'A'); } return sprintf(buf, "C%s%02X%08X%02X\n", gen_pnm, card->cid.prv, card->cid.serial, UNSTUFF_BITS(card->raw_cid, 8, 8)); } static ssize_t mmc_data_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; struct mmc_card_error_log *err_log; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int len = 0; if (!card) { len = snprintf(buf, PAGE_SIZE, "\"GE\":\"0\",\"CC\":\"0\",\"ECC\":\"0\",\"WP\":\"0\"," "\"OOR\":\"0\",\"CRC\":\"0\",\"TMO\":\"0\"," "\"HALT\":\"0\",\"CQED\":\"0\",\"RPMB\":\"0\"\n"); goto out; } err_log = card->err_log; mmc_check_error_count(err_log, &total_c_cnt, &total_t_cnt); len = snprintf(buf, PAGE_SIZE, "\"GE\":\"%d\",\"CC\":\"%d\",\"ECC\":\"%d\",\"WP\":\"%d\"," "\"OOR\":\"%d\",\"CRC\":\"%lld\",\"TMO\":\"%lld\"," "\"HALT\":\"%d\",\"CQED\":\"%d\",\"RPMB\":\"%d\"\n", err_log[0].ge_cnt, err_log[0].cc_cnt, err_log[0].ecc_cnt, err_log[0].wp_cnt, err_log[0].oor_cnt, total_c_cnt, total_t_cnt, err_log[0].halt_cnt, err_log[0].cq_cnt, err_log[0].rpmb_cnt); out: return len; } static ssize_t mmc_summary_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; char *bus_speed_mode = ""; static const char *const unit[] = {"B", "KB", "MB", "GB", "TB"}; uint64_t size; int digit = 0, pre_size = 1; int len = 0; char ret_size[6]; if (card) { /* SIZE */ size = (uint64_t)card->ext_csd.sectors * card->ext_csd.data_sector_size; /* SIZE - unit */ while (size > 1024) { size /= 1024; digit++; if (digit == 4) break; } /* SIZE - capacity */ while (size > pre_size) { if (pre_size > 1024) break; pre_size = pre_size << 1; } sprintf(ret_size, "%d%s", pre_size, unit[digit]); /* SPEED MODE */ if (mmc_card_hs400(card)) bus_speed_mode = "HS400"; else if (mmc_card_hs200(card)) bus_speed_mode = "HS200"; else if (mmc_card_ddr52(card)) bus_speed_mode = "DDR50"; else if (mmc_card_hs(card)) bus_speed_mode = "HS"; else bus_speed_mode = "LEGACY"; /* SUMMARY */ #if defined(CONFIG_MTK_EMMC_CQ_SUPPORT) || defined(CONFIG_MTK_EMMC_HW_CQ) len = sprintf(buf, "\"MANID\":\"0x%02X\",\"PNM\":\"%s\"," "\"REV\":\"%#x%x%x%x\",\"CQ\":\"%d\"," "\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\"," "\"LIFE\":\"%u\"\n", card->cid.manfid, card->cid.prod_name, (char)card->ext_csd.fwrev[4], (char)card->ext_csd.fwrev[5], (char)card->ext_csd.fwrev[6], (char)card->ext_csd.fwrev[7], (mmc_card_cmdq(card) ? true : false), ret_size, bus_speed_mode, (card->ext_csd.device_life_time_est_typ_a > card->ext_csd.device_life_time_est_typ_b ? card->ext_csd.device_life_time_est_typ_a : card->ext_csd.device_life_time_est_typ_b)); #else len = sprintf(buf, "\"MANID\":\"0x%02X\",\"PNM\":\"%s\","\ "\"REV\":\"%#x%x%x%x\","\ "\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\","\ "\"LIFE\":\"%u\"\n", card->cid.manfid, card->cid.prod_name, (char)card->ext_csd.fwrev[4], (char)card->ext_csd.fwrev[5], (char)card->ext_csd.fwrev[6], (char)card->ext_csd.fwrev[7], ret_size, bus_speed_mode, (card->ext_csd.device_life_time_est_typ_a > card->ext_csd.device_life_time_est_typ_b ? card->ext_csd.device_life_time_est_typ_a : card->ext_csd.device_life_time_est_typ_b)); #endif return len; } else { /* SUMMARY : No MMC Case */ dev_info(dev, "%s : No eMMC Card\n", __func__); return sprintf(buf, "\"MANID\":\"NoCard\",\"PNM\":\"NoCard\",\"REV\":\"NoCard\"" ",\"CQ\":\"NoCard\",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\"" ",\"LIFE\":\"NoCard\"\n"); } } static ssize_t mmc_ext_csd_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; int i = 0; int total_len = 0; u8 ext_csd_rev = 0; u8 ext_pre_eol_info = 0; u8 ext_device_life_time_est = 0; static const char *const ver_str[] = { "4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0", "5.1" }; static const char *const eol_str[] = { "Undefined", "Normal", "Warning (consumed 80% of reserve)", "Urgent (consumed 90% of reserve)" }; static const char *const est_str[] = { "Undefined", "0-10% of device lifetime used", "10-20% of device lifetime used", "20-30% of device lifetime used", "30-40% of device lifetime used", "40-50% of device lifetime used", "50-60% of device lifetime used", "60-70% of device lifetime used", "70-80% of device lifetime used", "80-90% of device lifetime used", "90-100% of device lifetime used", "Exceeded the maximum estimated device lifetime", }; if (card) { // List of interesting offsets ext_csd_rev = card->ext_csd.rev; total_len += snprintf(buf, PAGE_SIZE, "rev 1.%d (MMC %s)\n", ext_csd_rev, (ext_csd_rev < (int)(sizeof(ver_str) / sizeof(ver_str[0]))) ? ver_str[ext_csd_rev] : "Unknown"); if (ext_csd_rev < 8) total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "ext_csd_rev < 8\n"); ext_pre_eol_info = card->ext_csd.pre_eol_info; total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "PRE_EOL_INFO %d (MMC %s)\n", ext_pre_eol_info, eol_str[(ext_pre_eol_info < (int)(sizeof(eol_str) / sizeof(eol_str[0]))) ? ext_pre_eol_info : 0]); for (i = 0; i < 2; i++) { ext_device_life_time_est = i ? card->ext_csd.device_life_time_est_typ_b : card->ext_csd.device_life_time_est_typ_a; total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "DEVICE_LIFE_TIME_EST_TYP_%c %d (MMC %s)\n", i + 'A', ext_device_life_time_est, est_str[(ext_device_life_time_est < (int)(sizeof(est_str) / sizeof(est_str[0]))) ? ext_device_life_time_est : 0]); } return total_len; } else return snprintf(buf, PAGE_SIZE, "No Card\n"); } static ssize_t error_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; struct mmc_card_error_log *err_log; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int total_len = 0; int i = 0; if (!card) { total_len = snprintf(buf, PAGE_SIZE, "no card\n"); goto out; } err_log = card->err_log; total_len += snprintf(buf, PAGE_SIZE, "type: err status: first_issue_time: last_issue_time: count\n"); for (i = 0; i < MAX_ERR_LOG_INDEX; i++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "%5s:%4d 0x%08x %16llu, %16llu, %10d\n", err_log[i].type, err_log[i].err_type, err_log[i].status, err_log[i].first_issue_time, err_log[i].last_issue_time, err_log[i].count); } mmc_check_error_count(err_log, &total_c_cnt, &total_t_cnt); total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "GE:%d,CC:%d,ECC:%d,WP:%d,OOR:%d,CRC:%lld,TMO:%lld," "HALT:%d,CQEN:%d,RPMB:%d,RST:%d\n", err_log[0].ge_cnt, err_log[0].cc_cnt, err_log[0].ecc_cnt, err_log[0].wp_cnt, err_log[0].oor_cnt, total_c_cnt, total_t_cnt, err_log[0].halt_cnt, err_log[0].cq_cnt, err_log[0].rpmb_cnt, err_log[0].hw_rst_cnt); out: return total_len; } static ssize_t sdcard_status_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *mmc = dev_get_drvdata(dev); struct device_node *np = mmc->parent->of_node; int cd_gpio; cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); if (cd_gpio) { if (mmc_gpio_get_cd(mmc)) { if (mmc->card) { pr_err("SD card inserted.\n"); return sprintf(buf, "Insert\n"); } else { pr_err("SD card removed.\n"); return sprintf(buf, "Remove\n"); } } else { pr_err("SD slot tray Removed.\n"); return sprintf(buf, "Notray\n"); } } else { if (mmc->card) { pr_err("SD card inserted.\n"); return sprintf(buf, "Insert\n"); } else { pr_err("SD card removed.\n"); return sprintf(buf, "Remove\n"); } } } static ssize_t sdcard_detect_cnt_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *mmc = dev_get_drvdata(dev); return sprintf(buf, "%u\n", mmc->card_detect_cnt); } static ssize_t sdcard_detect_maxmode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *mmc = dev_get_drvdata(dev); const char *uhs_bus_speed_mode = ""; if (mmc->caps & MMC_CAP_UHS_SDR104) uhs_bus_speed_mode = "SDR104"; else if (mmc->caps & MMC_CAP_UHS_DDR50) uhs_bus_speed_mode = "DDR50"; else if (mmc->caps & MMC_CAP_UHS_SDR50) uhs_bus_speed_mode = "SDR50"; else if (mmc->caps & MMC_CAP_UHS_SDR25) uhs_bus_speed_mode = "SDR25"; else if (mmc->caps & MMC_CAP_UHS_SDR12) uhs_bus_speed_mode = "SDR12"; else uhs_bus_speed_mode = "HS"; return sprintf(buf, "%s\n", uhs_bus_speed_mode); } static ssize_t sdcard_detect_curmode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *mmc = dev_get_drvdata(dev); const char *uhs_bus_speed_mode = ""; static const char *const uhs_speed[] = { [UHS_SDR12_BUS_SPEED] = "SDR12", [UHS_SDR25_BUS_SPEED] = "SDR25", [UHS_SDR50_BUS_SPEED] = "SDR50", [UHS_SDR104_BUS_SPEED] = "SDR104", [UHS_DDR50_BUS_SPEED] = "DDR50", }; if (mmc && mmc->card) { if (mmc_card_uhs(mmc->card)) uhs_bus_speed_mode = uhs_speed[mmc->card->sd_bus_speed]; else uhs_bus_speed_mode = "HS"; } else uhs_bus_speed_mode = "No Card"; return sprintf(buf, "%s\n", uhs_bus_speed_mode); } static ssize_t sdcard_summary_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; const char *uhs_bus_speed_mode = ""; static const char *const uhs_speeds[] = { [UHS_SDR12_BUS_SPEED] = "SDR12", [UHS_SDR25_BUS_SPEED] = "SDR25", [UHS_SDR50_BUS_SPEED] = "SDR50", [UHS_SDR104_BUS_SPEED] = "SDR104", [UHS_DDR50_BUS_SPEED] = "DDR50", }; static const char *const unit[] = {"KB", "MB", "GB", "TB"}; unsigned int size, serial; int digit = 1; int len = 0; char ret_size[6]; if (card) { /* MANID */ /* SERIAL */ serial = card->cid.serial & (0x0000FFFF); /*SIZE*/ if (card->csd.read_blkbits == 9) /* 1 Sector = 512 Bytes */ size = (card->csd.capacity) >> 1; else if (card->csd.read_blkbits == 11) /* 1 Sector = 2048 Bytes */ size = (card->csd.capacity) << 1; else /* 1 Sector = 1024 Bytes */ size = card->csd.capacity; if (size >= 380000000 && size <= 410000000) { /* QUIRK 400GB SD Card */ sprintf(ret_size, "400GB"); } else if (size >= 190000000 && size <= 210000000) { /* QUIRK 200GB SD Card */ sprintf(ret_size, "200GB"); } else { while ((size >> 1) > 0) { size = size >> 1; digit++; } sprintf(ret_size, "%d%s", 1 << (digit % 10), unit[digit / 10]); } /* SPEEDMODE */ if (mmc_card_uhs(card)) uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed]; else if (mmc_card_hs(card)) uhs_bus_speed_mode = "HS"; else uhs_bus_speed_mode = "DS"; /* SUMMARY */ len = sprintf(buf, "\"MANID\":\"0x%02X\",\"SERIAL\":\"%04X\"" ",\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\",\"NOTI\":\"%d\"\n", card->cid.manfid, serial, ret_size, uhs_bus_speed_mode, card->err_log[0].noti_cnt); dev_info(dev, "%s", buf); return len; } else { /* SUMMARY : No SD Card Case */ dev_info(dev, "%s : No SD Card\n", __func__); return sprintf(buf, "\"MANID\":\"NoCard\",\"SERIAL\":\"NoCard\"" ",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\",\"NOTI\":\"NoCard\"\n"); } } static ssize_t sd_cid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; int len = 0; if (!card) { len = snprintf(buf, PAGE_SIZE, "no card\n"); goto out; } len = snprintf(buf, PAGE_SIZE, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1], card->raw_cid[2], card->raw_cid[3]); out: return len; } static ssize_t sd_health_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; struct mmc_card_error_log *err_log; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int len = 0; if (!card) { //There should be no spaces in 'No Card'(Vold Team). len = snprintf(buf, PAGE_SIZE, "NOCARD\n"); goto out; } err_log = card->err_log; mmc_check_error_count(err_log, &total_c_cnt, &total_t_cnt); if (err_log[0].ge_cnt > 100 || err_log[0].ecc_cnt > 0 || err_log[0].wp_cnt > 0 || err_log[0].oor_cnt > 10 || total_t_cnt > 100 || total_c_cnt > 100) len = snprintf(buf, PAGE_SIZE, "BAD\n"); else len = snprintf(buf, PAGE_SIZE, "GOOD\n"); out: return len; } /* SYSFS for service center support */ static ssize_t sd_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; struct mmc_card_error_log *err_log; u64 total_cnt = 0; int len = 0; int i = 0; if (!card) { len = snprintf(buf, PAGE_SIZE, "no card\n"); goto out; } err_log = card->err_log; //Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. for (i = 0; i < 6; i++) { if (total_cnt < MAX_CNT_U64) total_cnt += err_log[i].count; } len = snprintf(buf, PAGE_SIZE, "%lld\n", total_cnt); out: return len; } #define SEC_MMC_STATUS_ERR_INFO_GET_VALUE(member) ({ \ cur_err_log->member = err_log->member - err_log_backup->member; }) static inline void mmc_check_error_count_calc_current(struct mmc_card *card, unsigned long long *total_c_cnt, unsigned long long *total_t_cnt, struct mmc_card_error_log *cur_err_log) { struct mmc_card_error_log *err_log = card->err_log; struct mmc_card_error_log *err_log_backup = card->err_log_backup; int i = 0; //Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. for (i = 0; i < 6; i++) { if (err_log[i].err_type == -EILSEQ && *total_c_cnt < U64_MAX) *total_c_cnt += (err_log[i].count - err_log_backup[i].count); if (err_log[i].err_type == -ETIMEDOUT && *total_t_cnt < U64_MAX) *total_t_cnt += (err_log[i].count - err_log_backup[i].count); } SEC_MMC_STATUS_ERR_INFO_GET_VALUE(ge_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(cc_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(ecc_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(wp_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(oor_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(halt_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(cq_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(rpmb_cnt); SEC_MMC_STATUS_ERR_INFO_GET_VALUE(noti_cnt); } #define SEC_MMC_STATUS_ERR_INFO_BACKUP(member) ({ \ err_log_backup->member = err_log->member; }) static inline void mmc_backup_err_info(struct mmc_card *card) { struct mmc_card_error_log *err_log = card->err_log; struct mmc_card_error_log *err_log_backup = card->err_log_backup; int i = 0; // save current error count for (i = 0; i < MAX_ERR_LOG_INDEX; i++) err_log_backup[i].count = err_log[i].count; SEC_MMC_STATUS_ERR_INFO_BACKUP(ge_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(cc_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(ecc_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(wp_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(oor_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(halt_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(cq_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(rpmb_cnt); SEC_MMC_STATUS_ERR_INFO_BACKUP(noti_cnt); } /* SYSFS for big data support */ static ssize_t sd_data_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; struct mmc_card_error_log cur_err_log; u64 total_c_cnt = 0; u64 total_t_cnt = 0; int len = 0; if (!card) { len = snprintf(buf, PAGE_SIZE, "\"GE\":\"0\",\"CC\":\"0\",\"ECC\":\"0\",\"WP\":\"0\"," "\"OOR\":\"0\",\"CRC\":\"0\",\"TMO\":\"0\"\n"); goto out; } memset(&cur_err_log, 0, sizeof(struct mmc_card_error_log)); mmc_check_error_count_calc_current(card, &total_c_cnt, &total_t_cnt, &cur_err_log); len = snprintf(buf, PAGE_SIZE, "\"GE\":\"%d\",\"CC\":\"%d\",\"ECC\":\"%d\",\"WP\":\"%d\"," "\"OOR\":\"%d\",\"CRC\":\"%lld\",\"TMO\":\"%lld\"\n", cur_err_log.ge_cnt, cur_err_log.cc_cnt, cur_err_log.ecc_cnt, cur_err_log.wp_cnt, cur_err_log.oor_cnt, total_c_cnt, total_t_cnt); out: return len; } static ssize_t sd_data_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mmc_host *host = dev_get_drvdata(dev); struct mmc_card *card = host->card; if (!card) return -ENODEV; if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) return -EINVAL; mmc_backup_err_info(card); return count; } static DEVICE_ATTR(un, 0440, mmc_gen_unique_number_show, NULL); static DEVICE_ATTR(mmc_data, 0444, mmc_data_show, NULL); static DEVICE_ATTR(mmc_summary, 0444, mmc_summary_show, NULL); static DEVICE_ATTR(mmc_ecsd, 0444, mmc_ext_csd_show, NULL); static DEVICE_ATTR(err_count, 0444, error_count_show, NULL); static DEVICE_ATTR(status, 0444, sdcard_status_show, NULL); static DEVICE_ATTR(cd_cnt, 0444, sdcard_detect_cnt_show, NULL); static DEVICE_ATTR(max_mode, 0444, sdcard_detect_maxmode_show, NULL); static DEVICE_ATTR(current_mode, 0444, sdcard_detect_curmode_show, NULL); static DEVICE_ATTR(sdcard_summary, 0444, sdcard_summary_show, NULL); static DEVICE_ATTR(data, 0444, sd_cid_show, NULL); static DEVICE_ATTR(fc, 0444, sd_health_show, NULL); static DEVICE_ATTR(sd_count, 0444, sd_count_show, NULL); static DEVICE_ATTR(sd_data, 0664, sd_data_show, sd_data_store); static struct attribute *mmc_attributes[] = { &dev_attr_un.attr, &dev_attr_mmc_data.attr, &dev_attr_mmc_summary.attr, &dev_attr_mmc_ecsd.attr, &dev_attr_err_count.attr, NULL, }; static struct attribute_group mmc_attr_group = { .attrs = mmc_attributes, }; static struct attribute *sdcard_attributes[] = { &dev_attr_status.attr, &dev_attr_cd_cnt.attr, &dev_attr_max_mode.attr, &dev_attr_current_mode.attr, &dev_attr_sdcard_summary.attr, &dev_attr_err_count.attr, NULL, }; static struct attribute_group sdcard_attr_group = { .attrs = sdcard_attributes, }; static struct attribute *sdinfo_attributes[] = { &dev_attr_data.attr, &dev_attr_fc.attr, &dev_attr_sd_count.attr, NULL, }; static struct attribute_group sdinfo_attr_group = { .attrs = sdinfo_attributes, }; static struct attribute *sddata_attributes[] = { &dev_attr_sd_data.attr, NULL, }; static struct attribute_group sddata_attr_group = { .attrs = sddata_attributes, }; /* Callback function for SD Card IO Error */ static int sdcard_uevent(struct mmc_card *card) { pr_info("%s: Send Notification about SD Card IO Error\n", mmc_hostname(card->host)); return kobject_uevent(&sdcard_sec_dev->kobj, KOBJ_CHANGE); } static int msdc_sdcard_uevent(struct device *dev, struct kobj_uevent_env *env) { struct mmc_host *mmc = dev_get_drvdata(dev); struct mmc_card *card = NULL; struct mmc_card_error_log *err_log = NULL; int retval = 0; bool card_exist = false; add_uevent_var(env, "DEVNAME=%s", dev->kobj.name); if (mmc->card) { card_exist = true; card = mmc->card; err_log = card->err_log; } retval = add_uevent_var(env, "IOERROR=%s", card_exist ? ( ((err_log[0].ge_cnt && !(err_log[0].ge_cnt % 1000)) || (err_log[0].ecc_cnt && !(err_log[0].ecc_cnt % 1000)) || (err_log[0].wp_cnt && !(err_log[0].wp_cnt % 100)) || (err_log[0].oor_cnt && !(err_log[0].oor_cnt % 100))) ? "YES" : "NO") : "NoCard"); return retval; } static struct device_type sdcard_type = { .uevent = msdc_sdcard_uevent, }; void msdc_sec_create_sysfs_group(struct mmc_host *mmc, struct device **dev, const struct attribute_group *dev_attr_group, const char *str) { *dev = sec_device_create(NULL, str); if (IS_ERR(*dev)) pr_err("%s: Failed to create device!\n", __func__); else { if (sysfs_create_group(&(*dev)->kobj, dev_attr_group)) pr_err("%s: Failed to create %s sysfs group\n", __func__, str); else dev_set_drvdata(*dev, mmc); } } void mmc_sec_init_sysfs(struct mmc_host *mmc) { if (mmc->host_function == MSDC_EMMC) msdc_sec_create_sysfs_group(mmc, &mmc_sec_dev, &mmc_attr_group, "mmc"); if (mmc->host_function == MSDC_SD) { msdc_sec_create_sysfs_group(mmc, &sdcard_sec_dev, &sdcard_attr_group, "sdcard"); msdc_sec_create_sysfs_group(mmc, &sdinfo_sec_dev, &sdinfo_attr_group, "sdinfo"); msdc_sec_create_sysfs_group(mmc, &sddata_sec_dev, &sddata_attr_group, "sddata"); if (!IS_ERR(sdcard_sec_dev)) { sdcard_sec_dev->type = &sdcard_type; mmc->sdcard_uevent = sdcard_uevent; } } }