// 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 #include #include "ufs-sec-feature.h" #include "ufs-sec-sysfs.h" struct ufs_vendor_dev_info ufs_vdi; struct ufs_sec_err_info ufs_err_info; struct ufs_sec_err_info ufs_err_info_backup; struct ufs_sec_err_info ufs_err_hist; struct ufs_sec_wb_info ufs_wb; struct ufs_sec_feature_info ufs_sec_features; #define NOTI_WORK_DELAY_MS 500 #define set_wb_state(wb, s) \ ({(wb)->state = (s); (wb)->state_ts = jiffies; }) #define SEC_WB_may_on(wb, write_blocks, write_rqs) \ ((write_blocks) > ((wb)->wb_off ? (wb)->lp_up_threshold_block : (wb)->up_threshold_block) || \ (write_rqs) > ((wb)->wb_off ? (wb)->lp_up_threshold_rqs : (wb)->up_threshold_rqs)) #define SEC_WB_may_off(wb, write_blocks, write_rqs) \ ((write_blocks) < ((wb)->wb_off ? (wb)->lp_down_threshold_block : (wb)->down_threshold_block) && \ (write_rqs) < ((wb)->wb_off ? (wb)->lp_down_threshold_rqs : (wb)->down_threshold_rqs)) #define SEC_WB_check_on_delay(wb) \ (time_after_eq(jiffies, \ (wb)->state_ts + ((wb)->wb_off ? (wb)->lp_on_delay : (wb)->on_delay))) #define SEC_WB_check_off_delay(wb) \ (time_after_eq(jiffies, \ (wb)->state_ts + ((wb)->wb_off ? (wb)->lp_off_delay : (wb)->off_delay))) static void ufs_sec_set_unique_number(struct ufs_hba *hba, u8 *desc_buf) { u8 manid; u8 serial_num_index; u8 snum_buf[UFS_UN_MAX_DIGITS]; int buff_len; u8 *str_desc_buf = NULL; int err; /* read string desc */ buff_len = QUERY_DESC_MAX_SIZE; str_desc_buf = kzalloc(buff_len, GFP_KERNEL); if (!str_desc_buf) goto out; serial_num_index = desc_buf[DEVICE_DESC_PARAM_SN]; /* spec is unicode but sec uses hex data */ err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, QUERY_DESC_IDN_STRING, serial_num_index, 0, str_desc_buf, &buff_len); if (err) { dev_err(hba->dev, "%s: Failed reading string descriptor. err %d", __func__, err); goto out; } /* setup unique_number */ manid = desc_buf[DEVICE_DESC_PARAM_MANF_ID + 1]; memset(snum_buf, 0, sizeof(snum_buf)); memcpy(snum_buf, str_desc_buf + QUERY_DESC_HDR_SIZE, SERIAL_NUM_SIZE); memset(ufs_vdi.unique_number, 0, sizeof(ufs_vdi.unique_number)); sprintf(ufs_vdi.unique_number, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", manid, desc_buf[DEVICE_DESC_PARAM_MANF_DATE], desc_buf[DEVICE_DESC_PARAM_MANF_DATE + 1], snum_buf[0], snum_buf[1], snum_buf[2], snum_buf[3], snum_buf[4], snum_buf[5], snum_buf[6]); /* Null terminate the unique number string */ ufs_vdi.unique_number[UFS_UN_20_DIGITS] = '\0'; dev_dbg(hba->dev, "%s: ufs un : %s\n", __func__, ufs_vdi.unique_number); out: if (str_desc_buf) kfree(str_desc_buf); } void ufs_sec_get_health_desc(struct ufs_hba *hba) { int buff_len; u8 *desc_buf = NULL; int err; buff_len = hba->desc_size[QUERY_DESC_IDN_HEALTH]; desc_buf = kmalloc(buff_len, GFP_KERNEL); if (!desc_buf) return; err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, QUERY_DESC_IDN_HEALTH, 0, 0, desc_buf, &buff_len); if (err) { dev_err(hba->dev, "%s: Failed reading health descriptor. err %d", __func__, err); goto out; } /* getting Life Time at Device Health DESC*/ ufs_vdi.lifetime = desc_buf[HEALTH_DESC_PARAM_LIFE_TIME_EST_A]; dev_info(hba->dev, "LT: 0x%02x\n", (desc_buf[3] << 4) | desc_buf[4]); out: if (desc_buf) kfree(desc_buf); } inline bool ufs_sec_is_wb_allowed(void) { return ufs_wb.wb_support; } static void ufs_sec_wb_update_summary_stats(struct ufs_sec_wb_info *wb_info) { int idx; if (unlikely(!wb_info->wb_curr_issued_block)) return; if (wb_info->wb_curr_issued_max_block < wb_info->wb_curr_issued_block) wb_info->wb_curr_issued_max_block = wb_info->wb_curr_issued_block; if (wb_info->wb_curr_issued_min_block > wb_info->wb_curr_issued_block) wb_info->wb_curr_issued_min_block = wb_info->wb_curr_issued_block; /* * count up index * 0 : wb_curr_issued_block < 4GB * 1 : 4GB <= wb_curr_issued_block < 8GB * 2 : 8GB <= wb_curr_issued_block < 16GB * 3 : 16GB <= wb_curr_issued_block */ idx = fls(wb_info->wb_curr_issued_block >> 20); idx = (idx < 4) ? idx : 3; wb_info->wb_issued_size_cnt[idx]++; /* * wb_curr_issued_block : per 4KB * wb_total_issued_mb : MB */ wb_info->wb_total_issued_mb += (wb_info->wb_curr_issued_block >> 8); wb_info->wb_curr_issued_block = 0; } static void ufs_sec_wb_update_state(struct ufs_hba *hba) { if (hba->pm_op_in_progress) { pr_err("%s: pm_op_in_progress.\n", __func__); return; } if (!ufs_sec_is_wb_allowed()) return; switch (ufs_wb.state) { case WB_OFF: if (SEC_WB_may_on(&ufs_wb, ufs_wb.wb_current_block, ufs_wb.wb_current_rqs)) set_wb_state(&ufs_wb, WB_ON_READY); break; case WB_ON_READY: if (SEC_WB_may_off(&ufs_wb, ufs_wb.wb_current_block, ufs_wb.wb_current_rqs)) set_wb_state(&ufs_wb, WB_OFF); else if (SEC_WB_check_on_delay(&ufs_wb)) { set_wb_state(&ufs_wb, WB_ON); // queue work to WB on queue_work(ufs_wb.wb_workq, &ufs_wb.wb_on_work); } break; case WB_OFF_READY: if (SEC_WB_may_on(&ufs_wb, ufs_wb.wb_current_block, ufs_wb.wb_current_rqs)) set_wb_state(&ufs_wb, WB_ON); else if (SEC_WB_check_off_delay(&ufs_wb)) { set_wb_state(&ufs_wb, WB_OFF); // queue work to WB off queue_work(ufs_wb.wb_workq, &ufs_wb.wb_off_work); ufs_sec_wb_update_summary_stats(&ufs_wb); } break; case WB_ON: if (SEC_WB_may_off(&ufs_wb, ufs_wb.wb_current_block, ufs_wb.wb_current_rqs)) set_wb_state(&ufs_wb, WB_OFF_READY); break; default: WARN_ON(1); break; } } static int ufs_sec_wb_ctrl(struct ufs_hba *hba, bool enable, bool force) { int ret = 0; u8 index; enum query_opcode opcode; /* * Do not issue query, return immediately and set prev. state * when workqueue run in suspend/resume */ if (hba->pm_op_in_progress) { pr_err("%s: pm_op_in_progress.\n", __func__); return -EBUSY; } /* * Return error when ufshcd_state is not operational */ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) { pr_err("%s: UFS Host state=%d.\n", __func__, hba->ufshcd_state); return -EBUSY; } /* * Return error when workqueue run in WB disabled state */ if (!ufs_sec_is_wb_allowed() && !force) { pr_err("%s: not allowed.\n", __func__); return 0; } if (!(enable ^ hba->wb_enabled)) { pr_info("%s: write booster is already %s\n", __func__, enable ? "enabled" : "disabled"); return 0; } if (enable) opcode = UPIU_QUERY_OPCODE_SET_FLAG; else opcode = UPIU_QUERY_OPCODE_CLEAR_FLAG; index = ufshcd_wb_get_query_index(hba); pm_runtime_get_sync(hba->dev); ret = ufshcd_query_flag_retry(hba, opcode, QUERY_FLAG_IDN_WB_EN, index, NULL); pm_runtime_put(hba->dev); if (!ret) { hba->wb_enabled = enable; pr_info("%s: SEC write booster %s, current WB state is %d.\n", __func__, enable ? "enable" : "disable", ufs_wb.state); } return ret; } static void ufs_sec_wb_on_work_func(struct work_struct *work) { struct ufs_hba *hba = ufs_wb.hba; int ret = 0; ret = ufs_sec_wb_ctrl(hba, true, false); /* error case, except pm_op_in_progress and no supports */ if (ret) { unsigned long flags; spin_lock_irqsave(hba->host->host_lock, flags); dev_err(hba->dev, "%s: write booster on failed %d, now WB is %s, state is %d.\n", __func__, ret, hba->wb_enabled ? "on" : "off", ufs_wb.state); /* * check only WB_ON state * WB_OFF_READY : WB may off after this condition * WB_OFF or WB_ON_READY : in WB_OFF, off_work should be queued */ /* set WB state to WB_ON_READY to trigger WB ON again */ if (ufs_wb.state == WB_ON) set_wb_state(&ufs_wb, WB_ON_READY); spin_unlock_irqrestore(hba->host->host_lock, flags); } dev_dbg(hba->dev, "%s: WB %s, count %d, ret %d.\n", __func__, hba->wb_enabled ? "on" : "off", ufs_wb.wb_current_rqs, ret); } static void ufs_sec_wb_off_work_func(struct work_struct *work) { struct ufs_hba *hba = ufs_wb.hba; int ret = 0; ret = ufs_sec_wb_ctrl(hba, false, false); /* error case, except pm_op_in_progress and no supports */ if (ret) { unsigned long flags; spin_lock_irqsave(hba->host->host_lock, flags); dev_err(hba->dev, "%s: write booster off failed %d, now WB is %s, state is %d.\n", __func__, ret, hba->wb_enabled ? "on" : "off", ufs_wb.state); /* * check only WB_OFF state * WB_ON_READY : WB may on after this condition * WB_ON or WB_OFF_READY : in WB_ON, on_work should be queued */ /* set WB state to WB_OFF_READY to trigger WB OFF again */ if (ufs_wb.state == WB_OFF) set_wb_state(&ufs_wb, WB_OFF_READY); spin_unlock_irqrestore(hba->host->host_lock, flags); } else if (ufs_vdi.lifetime >= (u8)ufs_wb.wb_disable_threshold_lt) { pr_err("%s: disable WB by LT %u.\n", __func__, ufs_vdi.lifetime); ufs_wb.wb_support = false; } dev_dbg(hba->dev, "%s: WB %s, count %d, ret %d.\n", __func__, hba->wb_enabled ? "on" : "off", ufs_wb.wb_current_rqs, ret); } void ufs_sec_wb_force_off(struct ufs_hba *hba) { if (!ufs_sec_is_wb_allowed()) return; set_wb_state(&ufs_wb, WB_OFF); /* reset wb state and off */ if (ufshcd_is_link_hibern8(hba) && ufs_sec_is_wb_allowed()) { /* reset wb disable count and enable wb */ atomic_set(&ufs_wb.wb_off_cnt, 0); ufs_wb.wb_off = false; queue_work(ufs_wb.wb_workq, &ufs_wb.wb_off_work); } } bool ufs_sec_parse_wb_info(struct ufs_hba *hba) { struct device_node *node = hba->dev->of_node; int temp_delay_ms_value; ufs_wb.wb_support = of_property_read_bool(node, "sec,wb-enable"); if (!ufs_wb.wb_support) return false; ufs_wb.hba = hba; if (of_property_read_u32(node, "sec,wb-up-threshold-block", &ufs_wb.up_threshold_block)) ufs_wb.up_threshold_block = 3072; if (of_property_read_u32(node, "sec,wb-up-threshold-rqs", &ufs_wb.up_threshold_rqs)) ufs_wb.up_threshold_rqs = 30; if (of_property_read_u32(node, "sec,wb-down-threshold-block", &ufs_wb.down_threshold_block)) ufs_wb.down_threshold_block = 1536; if (of_property_read_u32(node, "sec,wb-down-threshold-rqs", &ufs_wb.down_threshold_rqs)) ufs_wb.down_threshold_rqs = 25; if (of_property_read_u32(node, "sec,wb-disable-threshold-lt", &ufs_wb.wb_disable_threshold_lt)) ufs_wb.wb_disable_threshold_lt = 9; if (of_property_read_u32(node, "sec,wb-on-delay-ms", &temp_delay_ms_value)) ufs_wb.on_delay = msecs_to_jiffies(92); else ufs_wb.on_delay = msecs_to_jiffies(temp_delay_ms_value); if (of_property_read_u32(node, "sec,wb-off-delay-ms", &temp_delay_ms_value)) ufs_wb.off_delay = msecs_to_jiffies(4500); else ufs_wb.off_delay = msecs_to_jiffies(temp_delay_ms_value); ufs_wb.wb_curr_issued_block = 0; ufs_wb.wb_total_issued_mb = 0; ufs_wb.wb_issued_size_cnt[0] = 0; ufs_wb.wb_issued_size_cnt[1] = 0; ufs_wb.wb_issued_size_cnt[2] = 0; ufs_wb.wb_issued_size_cnt[3] = 0; ufs_wb.wb_curr_issued_min_block = INT_MAX; /* default values will be used when (wb_off == true) */ ufs_wb.lp_up_threshold_block = 3072; /* 12MB */ ufs_wb.lp_up_threshold_rqs = 30; ufs_wb.lp_down_threshold_block = 3072; ufs_wb.lp_down_threshold_rqs = 30; ufs_wb.lp_on_delay = msecs_to_jiffies(200); ufs_wb.lp_off_delay = msecs_to_jiffies(0); return true; } static void ufs_sec_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set) { int val; u8 index; int ret = 0; if (!(ufs_wb.wb_setup_done && ufs_wb.wb_support)) return; if (set) val = UPIU_QUERY_OPCODE_SET_FLAG; else val = UPIU_QUERY_OPCODE_CLEAR_FLAG; /* ufshcd_wb_toggle_flush_during_h8 */ index = ufshcd_wb_get_query_index(hba); ret = ufshcd_query_flag_retry(hba, val, QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8, index, NULL); dev_info(hba->dev, "%s: %s WB flush during H8 is %s.\n", __func__, set ? "set" : "clear", ret ? "failed" : "done"); } static void ufs_sec_wb_config(struct ufs_hba *hba, bool set) { if (!ufs_sec_is_wb_allowed()) return; set_wb_state(&ufs_wb, WB_OFF); if (ufs_vdi.lifetime >= (u8)ufs_wb.wb_disable_threshold_lt) goto wb_disabled; /* reset wb disable count and enable wb */ atomic_set(&ufs_wb.wb_off_cnt, 0); ufs_wb.wb_off = false; ufs_sec_wb_toggle_flush_during_h8(hba, set); return; wb_disabled: ufs_wb.wb_support = false; } static void ufs_sec_wb_probe(struct ufs_hba *hba, u8 *desc_buf) { struct ufs_dev_info *dev_info = &hba->dev_info; u8 lun; u32 d_lu_wb_buf_alloc = 0; int err = 0; char wq_name[sizeof("SEC_WB_wq")]; if (!ufs_sec_parse_wb_info(hba)) return; if (ufs_wb.wb_setup_done) return; if (hba->desc_size[QUERY_DESC_IDN_DEVICE] < DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 4) goto wb_disabled; dev_info->d_ext_ufs_feature_sup = get_unaligned_be32(desc_buf + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP); if (!(dev_info->d_ext_ufs_feature_sup & UFS_DEV_WRITE_BOOSTER_SUP)) goto wb_disabled; /* * WB may be supported but not configured while provisioning. * The spec says, in dedicated wb buffer mode, * a max of 1 lun would have wb buffer configured. * Now only shared buffer mode is supported. */ dev_info->b_wb_buffer_type = desc_buf[DEVICE_DESC_PARAM_WB_TYPE]; dev_info->b_presrv_uspc_en = desc_buf[DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN]; if (dev_info->b_wb_buffer_type == WB_BUF_MODE_SHARED && (hba->dev_info.wspecversion >= 0x310 || hba->dev_info.wspecversion == 0x220)) { dev_info->d_wb_alloc_units = get_unaligned_be32(desc_buf + DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS); if (!dev_info->d_wb_alloc_units) goto wb_disabled; } else { int buf_len; u8 *desc_buf_temp; buf_len = hba->desc_size[QUERY_DESC_IDN_UNIT]; desc_buf_temp = kmalloc(buf_len, GFP_KERNEL); if (!desc_buf_temp) goto wb_disabled; for (lun = 0; lun < UFS_UPIU_MAX_WB_LUN_ID; lun++) { d_lu_wb_buf_alloc = 0; err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, QUERY_DESC_IDN_UNIT, lun, 0, desc_buf_temp, &buf_len); if (err) { kfree(desc_buf_temp); goto wb_disabled; } memcpy((u8 *)&d_lu_wb_buf_alloc, &desc_buf_temp[UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS], sizeof(d_lu_wb_buf_alloc)); if (d_lu_wb_buf_alloc) { dev_info->wb_dedicated_lu = lun; break; } } kfree(desc_buf_temp); if (!d_lu_wb_buf_alloc) goto wb_disabled; } dev_info(hba->dev, "%s: SEC WB is enabled. type=%x, size=%u.\n", __func__, dev_info->b_wb_buffer_type, d_lu_wb_buf_alloc); ufs_wb.wb_setup_done = true; INIT_WORK(&ufs_wb.wb_on_work, ufs_sec_wb_on_work_func); INIT_WORK(&ufs_wb.wb_off_work, ufs_sec_wb_off_work_func); snprintf(wq_name, sizeof(wq_name), "SEC_WB_wq"); ufs_wb.wb_workq = create_freezable_workqueue(wq_name); return; wb_disabled: ufs_wb.wb_support = false; } /* SEC cmd log : begin */ static void __ufs_sec_log_cmd(struct ufs_hba *hba, int str_idx, unsigned int tag, u8 cmd_id, u8 idn, u8 lun, u32 lba, int transfer_len) { struct ufs_sec_cmd_log_info *ufs_cmd_log = ufs_sec_features.ufs_cmd_log; struct ufs_sec_cmd_log_entry *entry = &ufs_cmd_log->entries[ufs_cmd_log->pos]; int cpu = raw_smp_processor_id(); entry->lun = lun; entry->str = ufs_sec_log_str[str_idx]; entry->cmd_id = cmd_id; entry->lba = lba; entry->transfer_len = transfer_len; entry->idn = idn; entry->tag = tag; entry->tstamp = cpu_clock(cpu); entry->outstanding_reqs = hba->outstanding_reqs; ufs_cmd_log->pos = (ufs_cmd_log->pos + 1) % UFS_SEC_CMD_LOGGING_MAX; } static void ufs_sec_log_cmd(struct ufs_hba *hba, struct ufshcd_lrb *lrbp, int str_t, struct ufs_sec_cmd_info *ufs_cmd, u8 cmd_id) { u8 opcode = 0; u8 idn = 0; if (!ufs_sec_features.ufs_cmd_log) return; switch (str_t) { case UFS_SEC_CMD_SEND: case UFS_SEC_CMD_COMP: __ufs_sec_log_cmd(hba, str_t, lrbp->task_tag, ufs_cmd->opcode, 0, lrbp->lun, ufs_cmd->lba, ufs_cmd->transfer_len); break; case UFS_SEC_QUERY_SEND: case UFS_SEC_QUERY_COMP: opcode = hba->dev_cmd.query.request.upiu_req.opcode; idn = hba->dev_cmd.query.request.upiu_req.idn; __ufs_sec_log_cmd(hba, str_t, lrbp->task_tag, opcode, idn, lrbp->lun, 0, 0); break; case UFS_SEC_NOP_SEND: case UFS_SEC_NOP_COMP: __ufs_sec_log_cmd(hba, str_t, lrbp->task_tag, 0, 0, lrbp->lun, 0, 0); break; case UFS_SEC_TM_SEND: case UFS_SEC_TM_COMP: case UFS_SEC_TM_ERR: case UFS_SEC_UIC_SEND: case UFS_SEC_UIC_COMP: __ufs_sec_log_cmd(hba, str_t, 0, cmd_id, 0, 0, 0, 0); break; default: break; } } static void ufs_sec_init_cmd_logging(struct device *dev) { struct ufs_sec_cmd_log_info *ufs_cmd_log = NULL; ufs_cmd_log = devm_kzalloc(dev, sizeof(struct ufs_sec_cmd_log_info), GFP_KERNEL); if (!ufs_cmd_log) { dev_err(dev, "%s: Failed allocating ufs_cmd_log(%lu)", __func__, sizeof(struct ufs_sec_cmd_log_info)); return; } ufs_cmd_log->entries = devm_kcalloc(dev, UFS_SEC_CMD_LOGGING_MAX, sizeof(struct ufs_sec_cmd_log_entry), GFP_KERNEL); if (!ufs_cmd_log->entries) { dev_err(dev, "%s: Failed allocating cmd log entry(%lu)", __func__, sizeof(struct ufs_sec_cmd_log_entry) * UFS_SEC_CMD_LOGGING_MAX); devm_kfree(dev, ufs_cmd_log); return; } pr_info("SEC UFS cmd logging is initialized.\n"); ufs_sec_features.ufs_cmd_log = ufs_cmd_log; ufs_sec_features.ucmd_complete = true; ufs_sec_features.qcmd_complete = true; } /* SEC cmd log : end */ /*stream id*/ inline bool streamid_is_enabled(void) { return ufs_vdi.stid_en; } static inline void set_streamid_state(bool set) { ufs_vdi.stid_en = set; } /*ufs device support or not*/ static inline bool streamid_is_allowed(void) { return ufs_vdi.stid_support; } /*context id*/ static inline bool contextid_is_allowed(void) { return ufs_vdi.conid_support; } static void ufs_sec_streamid_probe(struct ufs_hba *hba, u8 *desc_buf) { struct ufs_dev_info *dev_info = &hba->dev_info; u32 ext_ufs_feature_sup = 0; struct device_node *node = hba->dev->of_node; /*check stream id feature*/ if (hba->desc_size[QUERY_DESC_IDN_DEVICE] < DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 4) goto not_support; ext_ufs_feature_sup = get_unaligned_be32(desc_buf + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP); if (!(dev_info->d_ext_ufs_feature_sup & UFS_SEC_DEV_STREAMID_SUP)) { dev_info(hba->dev, "stream id is not supported in UFS device\n"); goto not_support; } /* get dt */ ufs_vdi.stid_dt = of_property_read_bool(node, "sec,streamid-enable"); /* read fStreamIdEn */ if (!ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG, QUERY_FLAG_IDN_STREAMID_EN, 0, &ufs_vdi.stid_en)) { dev_info(hba->dev, "streamid_en flag is %s\n", ufs_vdi.stid_en ? "enabled" : "disabled"); } else goto not_support; ufs_vdi.stid_support = true; return; not_support: ufs_vdi.stid_support = false; set_streamid_state(false); } int ufs_sec_streamid_ctrl(struct ufs_hba *hba, bool value) { bool set = value; int ret = 0; enum query_opcode opcode; if (contextid_is_allowed() || !streamid_is_allowed()) { dev_info(hba->dev, "%s: stream id is not supported\n", __func__); return -ENOTSUPP; } if (hba->pm_op_in_progress) { dev_err(hba->dev, "%s: pm_op_in_progress.\n", __func__); return -EBUSY; } if (streamid_is_enabled() != set) { pm_runtime_get_sync(hba->dev); if (set) opcode = UPIU_QUERY_OPCODE_SET_FLAG; else opcode = UPIU_QUERY_OPCODE_CLEAR_FLAG; ret = ufshcd_query_flag_retry(hba, opcode, QUERY_FLAG_IDN_STREAMID_EN, 0, NULL); if (ret) dev_info(hba->dev, "%s stream id is failed\n", set ? "Enabling" : "Disabling"); else { set_streamid_state(set); dev_info(hba->dev, "stream id is %s\n", set ? "enabled" : "disabled"); } pm_runtime_put(hba->dev); } return ret; } static void ufs_sec_contextid_probe(struct ufs_hba *hba) { u8 lun = 0; int ret = 0; for (lun = 0; lun < UFS_SEC_MAX_LUN_ID; lun++) { if (!ufs_is_valid_unit_desc_lun(&hba->dev_info, lun, UNIT_DESC_PARAM_CONTEXT_CAP)) { continue; } ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_UNIT, lun, UNIT_DESC_PARAM_CONTEXT_CAP, (u8 *)&ufs_vdi.conid_cap[lun], 2); if (!ret && ufs_vdi.conid_cap[lun]) { dev_info(hba->dev, "LUN[%d] has context capability. value is 0x%x\n", lun, ufs_vdi.conid_cap[lun]); if (!ufs_vdi.conid_support) ufs_vdi.conid_support = true; } } } /* panic notifier : begin */ static void ufs_sec_print_evt(struct ufs_hba *hba, u32 id, char *err_name) { int i; struct ufs_event_hist *e; if (id >= UFS_EVT_CNT) return; e = &hba->ufs_stats.event[id]; for (i = 0; i < UFS_EVENT_HIST_LENGTH; i++) { int p = (i + e->pos) % UFS_EVENT_HIST_LENGTH; // do not print if dev_reset[0]'s tstamp is in 2 sec. // because that happened on booting seq. if ((id == UFS_EVT_DEV_RESET) && (p == 0) && (ktime_to_us(e->tstamp[p]) < 2000000)) continue; if (e->tstamp[p] == 0) continue; dev_err(hba->dev, "%s[%d] = 0x%x at %lld us\n", err_name, p, e->val[p], ktime_to_us(e->tstamp[p])); } } static void ufs_sec_print_evt_hist(struct ufs_hba *hba) { ufs_sec_print_evt(hba, UFS_EVT_PA_ERR, "pa_err"); ufs_sec_print_evt(hba, UFS_EVT_DL_ERR, "dl_err"); ufs_sec_print_evt(hba, UFS_EVT_NL_ERR, "nl_err"); ufs_sec_print_evt(hba, UFS_EVT_TL_ERR, "tl_err"); ufs_sec_print_evt(hba, UFS_EVT_DME_ERR, "dme_err"); ufs_sec_print_evt(hba, UFS_EVT_AUTO_HIBERN8_ERR, "auto_hibern8_err"); ufs_sec_print_evt(hba, UFS_EVT_FATAL_ERR, "fatal_err"); ufs_sec_print_evt(hba, UFS_EVT_LINK_STARTUP_FAIL, "link_startup_fail"); ufs_sec_print_evt(hba, UFS_EVT_RESUME_ERR, "resume_fail"); ufs_sec_print_evt(hba, UFS_EVT_SUSPEND_ERR, "suspend_fail"); ufs_sec_print_evt(hba, UFS_EVT_DEV_RESET, "dev_reset"); ufs_sec_print_evt(hba, UFS_EVT_HOST_RESET, "host_reset"); ufs_sec_print_evt(hba, UFS_EVT_ABORT, "task_abort"); } /* * ufs_sec_check_and_is_err - Check and compare UFS Error counts * @buf: has to be filled by using SEC_UFS_ERR_SUM() or SEC_UFS_ERR_HIST_SUM() * * Returns * 0 - If the buf has no error counts * other - If the buf is invalid or has error count value */ static int ufs_sec_check_and_is_err(char *buf) { const char *no_err = "U0I0H0L0X0Q0R0W0F0SM0SH0"; if (!buf || strlen(buf) < strlen(no_err)) return -EINVAL; return strncmp(buf, no_err, strlen(no_err)); } /** * ufs_sec_panic_callback - Print and Send UFS Error Information to AP * Format : U0I0H0L0X0Q0R0W0F0SM0SH0 * U : UTP cmd error count * I : UIC error count * H : HWRESET count * L : Link startup failure count * X : Link Lost Error count * Q : UTMR QUERY_TASK error count * R : READ error count * W : WRITE error count * F : Device Fatal Error count * SM : Sense Medium error count * SH : Sense Hardware error count **/ static int ufs_sec_panic_callback(struct notifier_block *nfb, unsigned long event, void *panic_msg) { char err_buf[ERR_SUM_SIZE]; char hist_buf[ERR_HIST_SUM_SIZE]; char *str = (char *)panic_msg; bool is_FS_panic = !strncmp(str, "F2FS", 4) || !strncmp(str, "EXT4", 4); SEC_UFS_ERR_SUM(err_buf); SEC_UFS_ERR_HIST_SUM(hist_buf); pr_info("ufs: %s hist: %s", err_buf, hist_buf); secdbg_exin_set_ufs(err_buf); if (ufs_vdi.hba && is_FS_panic && ufs_sec_check_and_is_err(err_buf)) ufs_sec_print_evt_hist(ufs_vdi.hba); return NOTIFY_OK; } static struct notifier_block ufs_sec_panic_notifier = { .notifier_call = ufs_sec_panic_callback, .priority = 1, }; /* panic notifier : end */ /* reboot notifier : begin */ static int ufs_sec_reboot_notify(struct notifier_block *notify_block, unsigned long event, void *unused) { char buf[ERR_HIST_SUM_SIZE]; SEC_UFS_ERR_HIST_SUM(buf); pr_info("%s: UFS Info : %s", __func__, buf); return NOTIFY_OK; } /* reboot notifier : end */ /* I/O error uevent : begin */ static int ufs_sec_check_no_err(char *buf) { const char *no_err = "U0I0H0L0X0Q0R0W0F0SM0SH0"; if (!buf || !strncmp(buf, no_err, sizeof(no_err) - 1)) return -EINVAL; return 0; } void ufs_sec_print_err(void) { char err_buf[ERR_SUM_SIZE]; char hist_buf[ERR_HIST_SUM_SIZE]; SEC_UFS_ERR_SUM(err_buf); SEC_UFS_ERR_HIST_SUM(hist_buf); if (!ufs_sec_check_no_err(hist_buf)) pr_info("ufs: %s, hist : %s", err_buf, hist_buf); } static void ufs_sec_err_noti_work(struct work_struct *work) { int ret; char buf[ERR_SUM_SIZE]; SEC_UFS_ERR_SUM(buf); pr_info("%s: UFS Info: %s\n", __func__, buf); ret = kobject_uevent(&sec_ufs_cmd_dev->kobj, KOBJ_CHANGE); if (ret) pr_err("%s: Failed to send uevent with err %d\n", __func__, ret); } static int ufs_sec_err_uevent(struct device *dev, struct kobj_uevent_env *env) { char buf[ERR_SUM_SIZE]; add_uevent_var(env, "DEVNAME=%s", dev->kobj.name); add_uevent_var(env, "NAME=UFSINFO"); SEC_UFS_ERR_SUM(buf); return add_uevent_var(env, "DATA=%s", buf); } static struct device_type ufs_type = { .uevent = ufs_sec_err_uevent, }; static inline bool ufs_sec_is_uevent_condition(u32 hw_rst_count) { return ((hw_rst_count == 1) || !(hw_rst_count % 10)); } static void ufs_sec_trigger_err_noti_uevent(struct ufs_hba *hba) { const char *no_err = "U0I0H0L0X0Q0R0W0F0SM0SH0"; u32 hw_rst_count = SEC_UFS_ERR_INFO_GET_VALUE(op_count, HW_RESET_count); char buf[ERR_SUM_SIZE]; /* eh_flags is only set during error handling */ if (!hba->eh_flags) return; SEC_UFS_ERR_SUM(buf); if (!strncmp(buf, no_err, sizeof(buf) - 1)) return; if (!ufs_sec_is_uevent_condition(hw_rst_count)) return; if (sec_ufs_cmd_dev) schedule_delayed_work(&ufs_sec_features.noti_work, msecs_to_jiffies(NOTI_WORK_DELAY_MS)); } /* I/O error uevent : end */ void ufs_sec_init_logging(struct device *dev) { ufs_sec_init_cmd_logging(dev); } void ufs_set_sec_features(struct ufs_hba *hba) { int buff_len; u8 *desc_buf = NULL; int err; if (ufs_vdi.hba) return; ufs_vdi.hba = hba; /* read device desc */ buff_len = hba->desc_size[QUERY_DESC_IDN_DEVICE]; desc_buf = kmalloc(buff_len, GFP_KERNEL); if (!desc_buf) return; err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, QUERY_DESC_IDN_DEVICE, 0, 0, desc_buf, &buff_len); if (err) { dev_err(hba->dev, "%s: Failed reading device descriptor. err %d", __func__, err); goto out; } ufs_sec_set_unique_number(hba, desc_buf); ufs_sec_get_health_desc(hba); ufs_sec_wb_probe(hba, desc_buf); ufs_sec_contextid_probe(hba); ufs_sec_streamid_probe(hba, desc_buf); ufs_sysfs_add_sec_nodes(hba); atomic_notifier_chain_register(&panic_notifier_list, &ufs_sec_panic_notifier); ufs_sec_features.reboot_notify.notifier_call = ufs_sec_reboot_notify; register_reboot_notifier(&ufs_sec_features.reboot_notify); sec_ufs_cmd_dev->type = &ufs_type; INIT_DELAYED_WORK(&ufs_sec_features.noti_work, ufs_sec_err_noti_work); out: if (desc_buf) kfree(desc_buf); } void ufs_sec_feature_config(struct ufs_hba *hba) { ufs_sec_wb_config(hba, true); ufs_sec_trigger_err_noti_uevent(hba); if (contextid_is_allowed() && streamid_is_enabled()) ufs_sec_streamid_ctrl(hba, false); else { /* set streamid_en flag with dt value*/ ufs_sec_streamid_ctrl(hba, ufs_vdi.stid_dt); } } void ufs_remove_sec_features(struct ufs_hba *hba) { ufs_sysfs_remove_sec_nodes(hba); unregister_reboot_notifier(&ufs_sec_features.reboot_notify); } /* check error info : begin */ void ufs_sec_check_hwrst_cnt(void) { struct SEC_UFS_op_count *op_cnt = &ufs_err_info.op_count; SEC_UFS_ERR_COUNT_INC(op_cnt->HW_RESET_count, UINT_MAX); SEC_UFS_ERR_COUNT_INC(op_cnt->op_err, UINT_MAX); #if IS_ENABLED(CONFIG_SEC_ABC) if ((op_cnt->HW_RESET_count % 10) == 0) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=storage@INFO=ufs_hwreset_err"); #else sec_abc_send_event("MODULE=storage@WARN=ufs_hwreset_err"); #endif #endif } static void ufs_sec_check_link_startup_error_cnt(void) { struct SEC_UFS_op_count *op_cnt = &ufs_err_info.op_count; SEC_UFS_ERR_COUNT_INC(op_cnt->link_startup_count, UINT_MAX); SEC_UFS_ERR_COUNT_INC(op_cnt->op_err, UINT_MAX); } static void ufs_sec_check_medium_error_region(struct ufs_sec_cmd_info *ufs_cmd) { struct SEC_SCSI_SENSE_err_log *sense_err_log = &ufs_err_info.sense_err_log; unsigned int lba_count = sense_err_log->issue_LBA_count; unsigned long region_bit = 0; int i = 0; if (ufs_cmd->lun == 0) { if (lba_count < SEC_MAX_LBA_LOGGING) { for (i = 0; i < SEC_MAX_LBA_LOGGING; i++) { if (sense_err_log->issue_LBA_list[i] == ufs_cmd->lba) return; } sense_err_log->issue_LBA_list[lba_count] = ufs_cmd->lba; sense_err_log->issue_LBA_count++; } region_bit = ufs_cmd->lba / SEC_ISSUE_REGION_STEP; if (region_bit > 51) region_bit = 52; } else if (ufs_cmd->lun < UFS_UPIU_WLUN_ID) { region_bit = (unsigned long)(64 - ufs_cmd->lun); } sense_err_log->issue_region_map |= ((u64)1 << region_bit); } static void ufs_sec_uic_cmd_error_check(struct ufs_hba *hba, u32 cmd, bool timeout) { struct SEC_UFS_UIC_cmd_count *uic_cmd_cnt = &ufs_err_info.UIC_cmd_count; struct SEC_UFS_op_count *op_cnt = &ufs_err_info.op_count; int uic_cmd_result; uic_cmd_result = hba->active_uic_cmd->argument2 & MASK_UIC_COMMAND_RESULT; /* check UIC CMD result */ if (!timeout && (uic_cmd_result == UIC_CMD_RESULT_SUCCESS)) return; switch (cmd & COMMAND_OPCODE_MASK) { case UIC_CMD_DME_GET: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_GET_err, U8_MAX); break; case UIC_CMD_DME_SET: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_SET_err, U8_MAX); break; case UIC_CMD_DME_PEER_GET: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_PEER_GET_err, U8_MAX); break; case UIC_CMD_DME_PEER_SET: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_PEER_SET_err, U8_MAX); break; case UIC_CMD_DME_POWERON: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_POWERON_err, U8_MAX); break; case UIC_CMD_DME_POWEROFF: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_POWEROFF_err, U8_MAX); break; case UIC_CMD_DME_ENABLE: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_ENABLE_err, U8_MAX); break; case UIC_CMD_DME_RESET: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_RESET_err, U8_MAX); break; case UIC_CMD_DME_END_PT_RST: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_END_PT_RST_err, U8_MAX); break; case UIC_CMD_DME_LINK_STARTUP: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_LINK_STARTUP_err, U8_MAX); break; case UIC_CMD_DME_HIBER_ENTER: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_HIBER_ENTER_err, U8_MAX); SEC_UFS_ERR_COUNT_INC(op_cnt->Hibern8_enter_count, UINT_MAX); SEC_UFS_ERR_COUNT_INC(op_cnt->op_err, UINT_MAX); break; case UIC_CMD_DME_HIBER_EXIT: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_HIBER_EXIT_err, U8_MAX); SEC_UFS_ERR_COUNT_INC(op_cnt->Hibern8_exit_count, UINT_MAX); SEC_UFS_ERR_COUNT_INC(op_cnt->op_err, UINT_MAX); break; case UIC_CMD_DME_TEST_MODE: SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->DME_TEST_MODE_err, U8_MAX); break; default: break; } SEC_UFS_ERR_COUNT_INC(uic_cmd_cnt->UIC_cmd_err, UINT_MAX); } static void ufs_sec_uic_fatal_check(u32 errors) { struct SEC_UFS_Fatal_err_count *fatal_err_cnt = &ufs_err_info.Fatal_err_count; if (errors & DEVICE_FATAL_ERROR) { SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->DFE, U8_MAX); SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->Fatal_err, UINT_MAX); } if (errors & CONTROLLER_FATAL_ERROR) { SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->CFE, U8_MAX); SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->Fatal_err, UINT_MAX); } if (errors & SYSTEM_BUS_FATAL_ERROR) { SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->SBFE, U8_MAX); SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->Fatal_err, UINT_MAX); } if (errors & CRYPTO_ENGINE_FATAL_ERROR) { SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->CEFE, U8_MAX); SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->Fatal_err, UINT_MAX); } /* UIC_LINK_LOST : can not be checked in ufshcd.c */ if (errors & UIC_LINK_LOST) { SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->LLE, U8_MAX); SEC_UFS_ERR_COUNT_INC(fatal_err_cnt->Fatal_err, UINT_MAX); } } static void ufs_sec_uic_error_check(enum ufs_event_type evt, u32 reg) { struct SEC_UFS_UIC_err_count *uic_err_cnt = &ufs_err_info.UIC_err_count; int check_val = 0; switch (evt) { case UFS_EVT_PA_ERR: SEC_UFS_ERR_COUNT_INC(uic_err_cnt->PA_ERR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); if (reg & UIC_PHY_ADAPTER_LAYER_GENERIC_ERROR) SEC_UFS_ERR_COUNT_INC(uic_err_cnt->PA_ERR_linereset, UINT_MAX); check_val = reg & UIC_PHY_ADAPTER_LAYER_LANE_ERR_MASK; if (check_val) SEC_UFS_ERR_COUNT_INC(uic_err_cnt->PA_ERR_lane[check_val - 1], UINT_MAX); break; case UFS_EVT_DL_ERR: if (reg & UIC_DATA_LINK_LAYER_ERROR_PA_INIT) { SEC_UFS_ERR_COUNT_INC(uic_err_cnt->DL_PA_INIT_ERROR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); } if (reg & UIC_DATA_LINK_LAYER_ERROR_NAC_RECEIVED) { SEC_UFS_ERR_COUNT_INC(uic_err_cnt->DL_NAC_RECEIVED_ERROR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); } if (reg & UIC_DATA_LINK_LAYER_ERROR_TCx_REPLAY_TIMEOUT) { SEC_UFS_ERR_COUNT_INC(uic_err_cnt->DL_TC_REPLAY_ERROR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); } break; case UFS_EVT_NL_ERR: SEC_UFS_ERR_COUNT_INC(uic_err_cnt->NL_ERROR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); break; case UFS_EVT_TL_ERR: SEC_UFS_ERR_COUNT_INC(uic_err_cnt->TL_ERROR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); break; case UFS_EVT_DME_ERR: SEC_UFS_ERR_COUNT_INC(uic_err_cnt->DME_ERROR_cnt, U8_MAX); SEC_UFS_ERR_COUNT_INC(uic_err_cnt->UIC_err, UINT_MAX); break; default: break; } } static void ufs_sec_tm_error_check(u8 tm_cmd) { struct SEC_UFS_UTP_count *utp_err = &ufs_err_info.UTP_count; switch (tm_cmd) { case UFS_QUERY_TASK: SEC_UFS_ERR_COUNT_INC(utp_err->UTMR_query_task_count, U8_MAX); break; case UFS_ABORT_TASK: SEC_UFS_ERR_COUNT_INC(utp_err->UTMR_abort_task_count, U8_MAX); break; case UFS_LOGICAL_RESET: SEC_UFS_ERR_COUNT_INC(utp_err->UTMR_logical_reset_count, U8_MAX); break; default: break; } SEC_UFS_ERR_COUNT_INC(utp_err->UTP_err, UINT_MAX); #if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) if (tm_cmd == UFS_LOGICAL_RESET) ufs_vdi.device_stuck = true; #endif } void ufs_sec_check_device_stuck(void) { #if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) if (ufs_vdi.device_stuck) { /* waiting for cache flush and make a panic */ ssleep(2); panic("UFS TM ERROR\n"); } #endif #if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) if (ufs_vdi.hba && ufs_vdi.hba->eh_flags) /* do not recover system if test mode is enabled */ /* 1. reset recovery is in progress from ufshcd_err_handler */ /* 2. exynos_ufs_init_host has been succeeded */ BUG(); #endif } static void ufs_sec_utp_error_check(struct ufs_hba *hba, int tag) { struct SEC_UFS_UTP_count *utp_err = &ufs_err_info.UTP_count; struct ufshcd_lrb *lrbp = NULL; int opcode = 0; /* check tag value */ if (tag >= hba->nutrs) return; lrbp = &hba->lrb[tag]; /* check lrbp */ if (!lrbp || !lrbp->cmd || (lrbp->task_tag != tag)) return; opcode = lrbp->cmd->cmnd[0]; switch (opcode) { case WRITE_10: SEC_UFS_ERR_COUNT_INC(utp_err->UTR_write_err, U8_MAX); break; case READ_10: case READ_16: SEC_UFS_ERR_COUNT_INC(utp_err->UTR_read_err, U8_MAX); break; case SYNCHRONIZE_CACHE: SEC_UFS_ERR_COUNT_INC(utp_err->UTR_sync_cache_err, U8_MAX); break; case UNMAP: SEC_UFS_ERR_COUNT_INC(utp_err->UTR_unmap_err, U8_MAX); break; default: SEC_UFS_ERR_COUNT_INC(utp_err->UTR_etc_err, U8_MAX); break; } SEC_UFS_ERR_COUNT_INC(utp_err->UTP_err, UINT_MAX); } static void ufs_sec_query_error_check(struct ufs_hba *hba, struct ufshcd_lrb *lrbp, bool timeout) { struct SEC_UFS_QUERY_count *query_cnt = &ufs_err_info.query_count; struct ufs_query_req *request = &hba->dev_cmd.query.request; enum query_opcode opcode = request->upiu_req.opcode; enum dev_cmd_type cmd_type = hba->dev_cmd.type; int ocs; ocs = le32_to_cpu(lrbp->utr_descriptor_ptr->header.dword_2) & MASK_OCS; /* check Overall Command Status */ if (!timeout && (ocs == OCS_SUCCESS)) return; if (timeout) { opcode = ufs_sec_features.last_qcmd; cmd_type = ufs_sec_features.qcmd_type; } if (cmd_type == DEV_CMD_TYPE_NOP) { SEC_UFS_ERR_COUNT_INC(query_cnt->NOP_err, U8_MAX); } else { switch (opcode) { case UPIU_QUERY_OPCODE_READ_DESC: SEC_UFS_ERR_COUNT_INC(query_cnt->R_Desc_err, U8_MAX); break; case UPIU_QUERY_OPCODE_WRITE_DESC: SEC_UFS_ERR_COUNT_INC(query_cnt->W_Desc_err, U8_MAX); break; case UPIU_QUERY_OPCODE_READ_ATTR: SEC_UFS_ERR_COUNT_INC(query_cnt->R_Attr_err, U8_MAX); break; case UPIU_QUERY_OPCODE_WRITE_ATTR: SEC_UFS_ERR_COUNT_INC(query_cnt->W_Attr_err, U8_MAX); break; case UPIU_QUERY_OPCODE_READ_FLAG: SEC_UFS_ERR_COUNT_INC(query_cnt->R_Flag_err, U8_MAX); break; case UPIU_QUERY_OPCODE_SET_FLAG: SEC_UFS_ERR_COUNT_INC(query_cnt->Set_Flag_err, U8_MAX); break; case UPIU_QUERY_OPCODE_CLEAR_FLAG: SEC_UFS_ERR_COUNT_INC(query_cnt->Clear_Flag_err, U8_MAX); break; case UPIU_QUERY_OPCODE_TOGGLE_FLAG: SEC_UFS_ERR_COUNT_INC(query_cnt->Toggle_Flag_err, U8_MAX); break; default: break; } } SEC_UFS_ERR_COUNT_INC(query_cnt->Query_err, UINT_MAX); } void ufs_sec_check_op_err(struct ufs_hba *hba, enum ufs_event_type evt, void *data) { u32 error_val = *(u32 *)data; switch (evt) { case UFS_EVT_LINK_STARTUP_FAIL: ufs_sec_check_link_startup_error_cnt(); break; case UFS_EVT_DEV_RESET: break; case UFS_EVT_PA_ERR: case UFS_EVT_DL_ERR: case UFS_EVT_NL_ERR: case UFS_EVT_TL_ERR: case UFS_EVT_DME_ERR: if (error_val) ufs_sec_uic_error_check(evt, error_val); break; case UFS_EVT_FATAL_ERR: if (error_val) ufs_sec_uic_fatal_check(error_val); break; case UFS_EVT_ABORT: ufs_sec_utp_error_check(hba, (int)error_val); break; case UFS_EVT_HOST_RESET: break; case UFS_EVT_SUSPEND_ERR: case UFS_EVT_RESUME_ERR: break; case UFS_EVT_AUTO_HIBERN8_ERR: break; default: break; } } static void ufs_sec_sense_err_check(struct ufshcd_lrb *lrbp, struct ufs_sec_cmd_info *ufs_cmd) { struct SEC_SCSI_SENSE_count *sense_err = &ufs_err_info.sense_count; u8 sense_key = 0; u8 asc = 0; u8 ascq = 0; bool secdbgMode = false; sense_key = lrbp->ucd_rsp_ptr->sr.sense_data[2] & 0x0F; if (sense_key != MEDIUM_ERROR && sense_key != HARDWARE_ERROR) return; #if IS_ENABLED(CONFIG_SEC_DEBUG) secdbgMode = secdbg_mode_enter_upload(); #endif asc = lrbp->ucd_rsp_ptr->sr.sense_data[12]; ascq = lrbp->ucd_rsp_ptr->sr.sense_data[13]; pr_err("UFS : sense key 0x%x(asc 0x%x, ascq 0x%x), opcode 0x%x, lba 0x%x, len 0x%x.\n", sense_key, asc, ascq, ufs_cmd->opcode, ufs_cmd->lba, ufs_cmd->transfer_len); if (sense_key == MEDIUM_ERROR) { sense_err->scsi_medium_err++; ufs_sec_check_medium_error_region(ufs_cmd); #if IS_ENABLED(CONFIG_SEC_ABC) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=storage@INFO=ufs_medium_err"); #else sec_abc_send_event("MODULE=storage@WARN=ufs_medium_err"); #endif #endif if (secdbgMode) panic("ufs medium error\n"); } else { sense_err->scsi_hw_err++; if (secdbgMode) panic("ufs hardware error\n"); } } void ufs_sec_print_err_info(struct ufs_hba *hba) { dev_err(hba->dev, "Count: %u UIC: %u UTP: %u QUERY: %u\n", SEC_UFS_ERR_INFO_GET_VALUE(op_count, HW_RESET_count), SEC_UFS_ERR_INFO_GET_VALUE(UIC_err_count, UIC_err), SEC_UFS_ERR_INFO_GET_VALUE(UTP_count, UTP_err), SEC_UFS_ERR_INFO_GET_VALUE(query_count, Query_err)); dev_err(hba->dev, "Sense Key: medium: %u, hw: %u\n", SEC_UFS_ERR_INFO_GET_VALUE(sense_count, scsi_medium_err), SEC_UFS_ERR_INFO_GET_VALUE(sense_count, scsi_hw_err)); } /* check error info : end */ static bool ufs_sec_get_scsi_cmd_info(struct ufshcd_lrb *lrbp, struct ufs_sec_cmd_info *ufs_cmd) { struct scsi_cmnd *cmd; if (!lrbp || !lrbp->cmd || !ufs_cmd) return false; cmd = lrbp->cmd; ufs_cmd->opcode = (u8)(*cmd->cmnd); ufs_cmd->lba = (cmd->cmnd[2] << 24) | (cmd->cmnd[3] << 16) | (cmd->cmnd[4] << 8) | cmd->cmnd[5]; ufs_cmd->transfer_len = (cmd->cmnd[7] << 8) | cmd->cmnd[8]; ufs_cmd->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun); return true; } /* * vendor hooks * check include/trace/hooks/ufshcd.h */ static void sec_android_vh_ufs_prepare_command(void *data, struct ufs_hba *hba, struct request *req, struct ufshcd_lrb *lrbp, int *err) { struct scsi_cmnd *cmd = lrbp->cmd; /* set the group number with write hint when stream id is enabled */ if ((cmd->cmnd[0] == WRITE_10 || cmd->cmnd[0] == WRITE_16) && (streamid_is_enabled() && !contextid_is_allowed()) && req->bio->bi_write_hint) { if (cmd->cmnd[0] == WRITE_10) cmd->cmnd[6] = req->bio->bi_write_hint & 0x1f; else cmd->cmnd[14] = req->bio->bi_write_hint & 0x1f; } *err = 0; } static void sec_android_vh_ufs_send_command(void *data, struct ufs_hba *hba, struct ufshcd_lrb *lrbp) { struct ufs_sec_cmd_info ufs_cmd = { 0, }; bool is_scsi_cmd = false; unsigned long flags; struct ufs_query_req *request = NULL; enum dev_cmd_type cmd_type; enum query_opcode opcode; is_scsi_cmd = ufs_sec_get_scsi_cmd_info(lrbp, &ufs_cmd); if (is_scsi_cmd) { ufs_sec_log_cmd(hba, lrbp, UFS_SEC_CMD_SEND, &ufs_cmd, 0); if (ufs_sec_is_wb_allowed() && (ufs_cmd.opcode == WRITE_10)) { spin_lock_irqsave(hba->host->host_lock, flags); ufs_wb.wb_current_block += ufs_cmd.transfer_len; ufs_wb.wb_current_rqs++; ufs_sec_wb_update_state(hba); spin_unlock_irqrestore(hba->host->host_lock, flags); } } else { if (hba->dev_cmd.type == DEV_CMD_TYPE_NOP) ufs_sec_log_cmd(hba, lrbp, UFS_SEC_NOP_SEND, NULL, 0); else if (hba->dev_cmd.type == DEV_CMD_TYPE_QUERY) ufs_sec_log_cmd(hba, lrbp, UFS_SEC_QUERY_SEND, NULL, 0); /* in timeout error case, last cmd is not completed */ if (!ufs_sec_features.qcmd_complete) ufs_sec_query_error_check(hba, lrbp, true); request = &hba->dev_cmd.query.request; opcode = request->upiu_req.opcode; cmd_type = hba->dev_cmd.type; ufs_sec_features.last_qcmd = opcode; ufs_sec_features.qcmd_type = cmd_type; ufs_sec_features.qcmd_complete = false; } } static void sec_android_vh_ufs_compl_command(void *data, struct ufs_hba *hba, struct ufshcd_lrb *lrbp) { struct ufs_sec_cmd_info ufs_cmd = { 0, }; bool is_scsi_cmd = false; unsigned long flags; is_scsi_cmd = ufs_sec_get_scsi_cmd_info(lrbp, &ufs_cmd); if (is_scsi_cmd) { ufs_sec_log_cmd(hba, lrbp, UFS_SEC_CMD_COMP, &ufs_cmd, 0); ufs_sec_sense_err_check(lrbp, &ufs_cmd); if (ufs_sec_is_wb_allowed() && (ufs_cmd.opcode == WRITE_10)) { spin_lock_irqsave(hba->host->host_lock, flags); ufs_wb.wb_current_block -= ufs_cmd.transfer_len; ufs_wb.wb_current_rqs--; if (hba->wb_enabled) ufs_wb.wb_curr_issued_block += (unsigned int)ufs_cmd.transfer_len; ufs_sec_wb_update_state(hba); spin_unlock_irqrestore(hba->host->host_lock, flags); } /* * check hba->req_abort_count, if the cmd is aborting * it's the one way to check aborting * hba->req_abort_count is cleared in queuecommand and after * error handling */ if (hba->req_abort_count > 0) ufs_sec_utp_error_check(hba, lrbp->task_tag); } else { if (hba->dev_cmd.type == DEV_CMD_TYPE_NOP) ufs_sec_log_cmd(hba, lrbp, UFS_SEC_NOP_COMP, NULL, 0); else if (hba->dev_cmd.type == DEV_CMD_TYPE_QUERY) ufs_sec_log_cmd(hba, lrbp, UFS_SEC_QUERY_COMP, NULL, 0); ufs_sec_features.qcmd_complete = true; /* check and count error, except timeout */ ufs_sec_query_error_check(hba, lrbp, false); } } static void sec_android_vh_ufs_send_uic_command(void *data, struct ufs_hba *hba, struct uic_command *ucmd, const char *str) { u32 cmd; u8 cmd_id = 0; if (!strcmp(str, "send")) { /* in timeout error case, last cmd is not completed */ if (!ufs_sec_features.ucmd_complete) { ufs_sec_uic_cmd_error_check(hba, ufs_sec_features.last_ucmd, true); } cmd = ucmd->command; ufs_sec_features.last_ucmd = cmd; ufs_sec_features.ucmd_complete = false; cmd_id = (u8)(cmd & COMMAND_OPCODE_MASK); ufs_sec_log_cmd(hba, NULL, UFS_SEC_UIC_SEND, NULL, cmd_id); } else { cmd = ufshcd_readl(hba, REG_UIC_COMMAND); ufs_sec_features.ucmd_complete = true; /* check and count error, except timeout */ ufs_sec_uic_cmd_error_check(hba, cmd, false); cmd_id = (u8)(cmd & COMMAND_OPCODE_MASK); ufs_sec_log_cmd(hba, NULL, UFS_SEC_UIC_COMP, NULL, cmd_id); } } static void sec_android_vh_ufs_send_tm_command(void *data, struct ufs_hba *hba, int tag, const char *str) { struct utp_task_req_desc treq = { { 0 }, }; u8 tm_func = 0; int sec_log_str_t = 0; memcpy(&treq, hba->utmrdl_base_addr + tag, sizeof(treq)); tm_func = (be32_to_cpu(treq.req_header.dword_1) >> 16) & 0xFF; if (!strncmp(str, "tm_send", sizeof("tm_send"))) sec_log_str_t = UFS_SEC_TM_SEND; else if (!strncmp(str, "tm_complete", sizeof("tm_complete"))) sec_log_str_t = UFS_SEC_TM_COMP; else if (!strncmp(str, "tm_complete_err", sizeof("tm_complete_err"))) { ufs_sec_tm_error_check(tm_func); sec_log_str_t = UFS_SEC_TM_ERR; } else { dev_err(hba->dev, "%s: undefind ufs tm cmd\n", __func__); return; } ufs_sec_log_cmd(hba, NULL, sec_log_str_t, NULL, tm_func); #if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) if (!strncmp(str, "tm_complete", sizeof("tm_complete"))) { dev_err(hba->dev, "%s: ufs tm cmd is succeeded and forced BUG called\n", __func__); ssleep(2); BUG(); } #endif } static void sec_android_vh_ufs_check_int_errors(void *data, struct ufs_hba *hba, bool queue_eh_work) { } static void sec_android_vh_ufs_update_sdev(void *data, struct scsi_device *sdev) { /* set req timeout */ blk_queue_rq_timeout(sdev->request_queue, SCSI_UFS_TIMEOUT); } static void sec_android_vh_ufs_update_sysfs(void *data, struct ufs_hba *hba) { } void ufs_sec_register_vendor_hooks(void) { register_trace_android_vh_ufs_check_int_errors(sec_android_vh_ufs_check_int_errors, NULL); register_trace_android_vh_ufs_compl_command(sec_android_vh_ufs_compl_command, NULL); register_trace_android_vh_ufs_prepare_command(sec_android_vh_ufs_prepare_command, NULL); register_trace_android_vh_ufs_send_command(sec_android_vh_ufs_send_command, NULL); register_trace_android_vh_ufs_send_tm_command(sec_android_vh_ufs_send_tm_command, NULL); register_trace_android_vh_ufs_send_uic_command(sec_android_vh_ufs_send_uic_command, NULL); register_trace_android_vh_ufs_update_sdev(sec_android_vh_ufs_update_sdev, NULL); register_trace_android_vh_ufs_update_sysfs(sec_android_vh_ufs_update_sysfs, NULL); }