// SPDX-License-Identifier: GPL-2.0 /* * Universal Flash Storage Ring Buffer * * Copyright (C) 2019-2019 Samsung Electronics Co., Ltd. * * Authors: * Yongmyung Lee * Jinyoung Choi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * See the COPYING file in the top-level directory or visit * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This program is provided "AS IS" and "WITH ALL FAULTS" and * without warranty of any kind. You are solely responsible for * determining the appropriateness of using and distributing * the program and assume all risks associated with your exercise * of rights with respect to the program, including but not limited * to infringement of third party rights, the risks and costs of * program errors, damage to or loss of data, programs or equipment, * and unavailability or interruption of operations. Under no * circumstances will the contributor of this Program be liable for * any damages of any kind arising from your use or distribution of * this program. * * The Linux Foundation chooses to take subject only to the GPLv2 * license terms, and distributes only under these terms. */ #include "ufshcd.h" #include "ufsfeature.h" #include "ufsringbuf.h" static void ufsringbuf_reset_work_fn(struct work_struct *work); static int ufsringbuf_issue_vendor_mode_retry(struct ufsf_feature *ufsf, u8 code); static int ufsringbuf_create_sysfs(struct ufsringbuf_dev *ringbuf); static int ufsringbuf_create_procfs(struct ufsf_feature *ufsf); inline int ufsringbuf_get_state(struct ufsf_feature *ufsf) { return atomic_read(&ufsf->ringbuf_state); } inline void ufsringbuf_set_state(struct ufsf_feature *ufsf, int state) { atomic_set(&ufsf->ringbuf_state, state); } static inline int ufsringbuf_is_not_present(struct ufsringbuf_dev *ringbuf) { enum UFSRINGBUF_STATE cur_state = ufsringbuf_get_state(ringbuf->ufsf); if (cur_state != RINGBUF_PRESENT) { INFO_MSG("ringbuf_state != RINGBUF_PRESENT (%d)", cur_state); return -ENODEV; } return 0; } static int ufsringbuf_set_rtc(struct ufsf_feature *ufsf) { struct ufs_hba *hba = ufsf->hba; u32 rtc_a, rtc_b; int ret_a, ret_b; s64 rtc_ktime = ktime_get(); rtc_a = (rtc_ktime >> 32) & 0xffffffff; rtc_b = rtc_ktime & 0xffffffff; INFO_MSG("ktime attr %lld(%.16llX) -> %d(%.8X) %d(%.8X)", rtc_ktime, rtc_ktime, rtc_a, rtc_a, rtc_b, rtc_b); ret_a = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, QUERY_ATTR_IDN_RINGBUF_RTCA, 0, UFSFEATURE_SELECTOR, &rtc_a); if (ret_a) ERR_MSG("set RTCA query request fail (%d)", ret_a); else INFO_MSG("RTCA write query success"); ret_b = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, QUERY_ATTR_IDN_RINGBUF_RTCB, 0, UFSFEATURE_SELECTOR, &rtc_b); if (ret_b) ERR_MSG("set RTCB query request fail (%d)", ret_b); else INFO_MSG("RTCB write query success"); if (ret_a) return ret_a; return ret_b; } struct scsi_device *ufsringbuf_get_sdev(struct ufsf_feature *ufsf, int target_lun) { struct scsi_device *sdev = NULL; if (target_lun >= 0 && target_lun < UFS_UPIU_MAX_GENERAL_LUN) sdev = ufsf->sdev_ufs_lu[target_lun]; if (!sdev) ERR_MSG("get scsi_device fail"); else INFO_MSG("get scsi_device lun (%llu)", sdev->lun); return sdev; } static inline ktime_t ufsringbuf_get_rtc_ktime(struct history_block_desc *desc) { return ((u64)LI_EN_32(&desc->host_time_a) << 32 | LI_EN_32(&desc->host_time_b)); } static void ufsringbuf_print_scsi_cmd(struct seq_file *file, int line, struct history_block_desc *desc, struct utp_upiu_req *upiu_req) { unsigned char buf[255]; int count = 0; u8 opcode = 0; struct timespec64 tv_rtc = ktime_to_timespec64(ufsringbuf_get_rtc_ktime(desc)); count += snprintf(buf + count, 11, "scsi_cmd: "); count += snprintf(buf + count, 19, "[%8ld.%.6ld] ", (long)tv_rtc.tv_sec, ((long)tv_rtc.tv_nsec/1000)); count += snprintf(buf + count, 21, "dev_time=%-10u ", LI_EN_32(&desc->device_time)); count += snprintf(buf + count, 10, "lun=0x%-2x ", GET_BYTE_2(upiu_req->header.dword_0)); count += snprintf(buf + count, 8, "tag=%-2d ", GET_BYTE_3(upiu_req->header.dword_0)); opcode = upiu_req->sc.cdb[0]; count += snprintf(buf + count, 13, "cmd_id=0x%.2x ", opcode); if ((opcode == READ_10) || (opcode == WRITE_10)) { count += snprintf(buf + count, 16, "lba=0x%.8x ", LI_EN_32(&upiu_req->sc.cdb[2])); count += snprintf(buf + count, 18, "txfer_len=%-6d ", LI_EN_32(&upiu_req->sc.exp_data_transfer_len)); } else { int size = COMMAND_SIZE(opcode); int i; count += snprintf(buf + count, 5, "cdb="); for (i = 0; i < size; i++) count += snprintf(buf + count, 4, "%.2x ", upiu_req->sc.cdb[i]); } count += snprintf(buf + count, 34, "state=0x%.2x %.2x %.2x sense=0x%.2x %.2x %.2x", desc->command_state, desc->response, desc->status, desc->sense_key, desc->asc, desc->ascq); RINGBUF_MSG(file, "%.4d %s", line, buf); } static void ufsringbuf_print_task_req(struct seq_file *file, int line, struct history_block_desc *desc, struct utp_upiu_req *upiu_req) { struct utp_upiu_task_req *upiu_task_req = (struct utp_upiu_task_req *)upiu_req; unsigned char buf[255]; int count = 0; struct timespec64 tv_rtc = ktime_to_timespec64(ufsringbuf_get_rtc_ktime(desc)); count += snprintf(buf + count, 10, "task_rq: "); count += snprintf(buf + count, 19, "[%8ld.%.6ld] ", (long)tv_rtc.tv_sec, ((long)tv_rtc.tv_nsec/1000)); count += snprintf(buf + count, 21, "dev_time=%-10u ", LI_EN_32(&desc->device_time)); count += snprintf(buf + count, 10, "lun=0x%-2x ", GET_BYTE_2(upiu_task_req->header.dword_0)); count += snprintf(buf + count, 8, "tag=%-2d ", GET_BYTE_3(upiu_task_req->header.dword_0)); count += snprintf(buf + count, 13, "cmd_id=0x%.2x ", GET_BYTE_1(upiu_task_req->header.dword_1)); count += snprintf(buf + count, 34, "state=0x%.2x %.2x %.2x sense=0x%.2x %.2x %.2x", desc->command_state, desc->response, desc->status, desc->sense_key, desc->asc, desc->ascq); RINGBUF_MSG(file, "%.4d %s", line, buf); } static void ufsringbuf_print_query_req(struct seq_file *file, int line, struct history_block_desc *desc, struct utp_upiu_req *upiu_req) { unsigned char buf[255]; int count = 0; struct timespec64 tv_rtc = ktime_to_timespec64(ufsringbuf_get_rtc_ktime(desc)); count += snprintf(buf + count, 11, "query_rq: "); count += snprintf(buf + count, 19, "[%8ld.%.6ld] ", (long)tv_rtc.tv_sec, ((long)tv_rtc.tv_nsec/1000)); count += snprintf(buf + count, 21, "dev_time=%-10u ", LI_EN_32(&desc->device_time)); count += snprintf(buf + count, 10, "lun=0x%-2x ", GET_BYTE_2(upiu_req->header.dword_0)); count += snprintf(buf + count, 8, "tag=%-2d ", GET_BYTE_3(upiu_req->header.dword_0)); count += snprintf(buf + count, 13, "cmd_id=0x%.2x ", upiu_req->qr.opcode); count += snprintf(buf + count, 10, "idn=0x%.2x ", upiu_req->qr.idn); count += snprintf(buf + count, 12, "index=0x%.2x ", upiu_req->qr.index); count += snprintf(buf + count, 12, "selector=%1d ", upiu_req->qr.selector); count += snprintf(buf + count, 34, "state=0x%.2x %.2x %.2x sense=0x%.2x %.2x %.2x", desc->command_state, desc->response, desc->status, desc->sense_key, desc->asc, desc->ascq); RINGBUF_MSG(file, "%.4d %s", line, buf); } static void ufsringbuf_print_upiu(struct seq_file *file, int line, struct history_block_desc *desc) { struct timespec64 tv_rtc = ktime_to_timespec64(ufsringbuf_get_rtc_ktime(desc)); RINGBUF_MSG(file, "INFO %d [%8ld.%.6ld] dev_time=%u state 0x%.2x 0x%.2x 0x%.2x sense 0x%.2x 0x%.2x 0x%.2x", line, (long)tv_rtc.tv_sec, ((long)tv_rtc.tv_nsec/1000), LI_EN_32(&desc->device_time), desc->command_state, desc->response, desc->status, desc->sense_key, desc->asc, desc->ascq); RINGBUF_MSG(file, "UPIU %d upiu[0..3] %.8X %.8X %.8X %.8X upiu [4..7] %.8X %.8X %.8X %.8X", line, desc->upiu[0], desc->upiu[1], desc->upiu[2], desc->upiu[3], desc->upiu[4], desc->upiu[5], desc->upiu[6], desc->upiu[7]); } static void ufsringbuf_print_upiu_parsing(struct seq_file *file, int line, struct history_block_desc *desc) { struct utp_upiu_req *upiu_req = (struct utp_upiu_req *)&desc->upiu; if (GET_BYTE_0(upiu_req->header.dword_0) == UPIU_TRANSACTION_COMMAND) ufsringbuf_print_scsi_cmd(file, line, desc, upiu_req); else if (GET_BYTE_0(upiu_req->header.dword_0) == UPIU_TRANSACTION_TASK_REQ) ufsringbuf_print_task_req(file, line, desc, upiu_req); else if (GET_BYTE_0(upiu_req->header.dword_0) == UPIU_TRANSACTION_QUERY_REQ) ufsringbuf_print_query_req(file, line, desc, upiu_req); else RINGBUF_MSG(file, "UPIU seqno %d upiu[0..3] %.8X %.8X %.8X %.8X upiu [4..7] %.8X %.8X %.8X %.8X", line, desc->upiu[0], desc->upiu[1], desc->upiu[2], desc->upiu[3], desc->upiu[4], desc->upiu[5], desc->upiu[6], desc->upiu[7]); } static void ufsringbuf_print_buf(struct seq_file *file, void *buf, int max_lines, bool is_upiu) { struct history_block_desc *desc; int line; void *p = buf; p += HIST_BLK_DESC_START; for (line = 0; line < max_lines; line++, p += HIST_BLOCK_DESC_BYTES) { desc = (struct history_block_desc *)p; if (is_upiu) ufsringbuf_print_upiu(file, line, desc); else ufsringbuf_print_upiu_parsing(file, line, desc); } } static void ufsringbuf_show_msg(struct ufsf_feature *ufsf, struct seq_file *file, void *buf) { struct ufsringbuf_dev *ringbuf; int max_lines, data_length; unsigned char *p; ringbuf = ufsf->ringbuf_dev; p = (unsigned char *)buf; data_length = ((u32)p[0] << 16 | (u32)p[1] << 8 | (u32)p[2]); if (data_length == 0) RINGBUF_MSG(file, "Check sense_key in dmesg!"); INFO_MSG("data_length %d", data_length); max_lines = (u32)data_length / HIST_BLOCK_DESC_BYTES; INFO_MSG("print lines %d", max_lines); RINGBUF_MSG(file, "History block data_length:%d lines:%d", data_length, max_lines); if (!ringbuf->parsing) { ufsringbuf_print_buf(file, buf, max_lines, true); ufsringbuf_print_buf(file, buf, max_lines, false); } else { ufsringbuf_print_buf(file, buf, max_lines, false); } } static inline void ufsringbuf_set_vendor_mode_cmd(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { unsigned char *cdb = lrbp->cmd->cmnd; u32 entry_2, entry_6, entry_12 = 0; entry_2 = ufsf->ringbuf_dev->input_signature; put_unaligned_be32(entry_2, &cdb[2]); entry_6 = ufsf->ringbuf_dev->input_parameter; put_unaligned_be32(entry_6, &cdb[6]); put_unaligned_be32(entry_12, &cdb[12]); } #if defined(CONFIG_UFSRINGBUF_POC) static inline void ufsringbuf_set_passwd_cmd(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { unsigned char *cdb = lrbp->cmd->cmnd; put_unaligned_be32(ufsf->ringbuf_dev->input_parameter, &cdb[2]); } #endif /* * This function is for vendor cmd. * cdb[10] -> cdb[16] */ void ufsringbuf_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) { unsigned char *cdb = lrbp->cmd->cmnd; u32 ringbuf_vendor_sig; if (!ufsf->ringbuf_dev) return; if (cdb[0] != VENDOR_CMD_OP) return; ringbuf_vendor_sig = get_unaligned_be32(&cdb[2]); if (ringbuf_vendor_sig != RINGBUF_VENDOR_SIG) return; if (cdb[1] == ENTER_VENDOR || cdb[1] == EXIT_VENDOR) ufsringbuf_set_vendor_mode_cmd(ufsf, lrbp); #if defined(CONFIG_UFSRINGBUF_POC) else if (cdb[1] == SET_PASSWD) ufsringbuf_set_passwd_cmd(ufsf, lrbp); #endif lrbp->cmd->cmd_len = MAX_CDB_SIZE; } /* * scsi_execute() will copy cdb by 10-byte due to opcode. * so it will be changed in ufsf_ringbuf_prep_fn(). */ static int ufsringbuf_issue_vendor_mode(struct ufsf_feature *ufsf, u8 code) { struct scsi_sense_hdr sshdr; unsigned char cdb[10] = { 0 }; struct scsi_device *sdev = NULL; int ret; sdev = ufsringbuf_get_sdev(ufsf, GET_DEFAULT_LU); if (!sdev) return -ENODEV; if (code != ENTER_VENDOR && code != EXIT_VENDOR) { ERR_MSG("vendor command code (%d)", code); return -EINVAL; } cdb[0] = VENDOR_CMD_OP; cdb[1] = code; /* Signature of Ringbuf Vendor Command */ put_unaligned_be32(RINGBUF_VENDOR_SIG, &cdb[2]); ret = scsi_execute(sdev, cdb, DMA_NONE, NULL, 0, NULL, &sshdr, VENDOR_CMD_TIMEOUT, 0, 0, 0, NULL); INFO_MSG("vendor(%s) command %s", code == ENTER_VENDOR ? "ENTER" : "EXIT", ret ? "fail" : "success"); if (ret) { ERR_MSG("code %x sense_key %x asc %x ascq %x", sshdr.response_code, sshdr.sense_key, sshdr.asc, sshdr.ascq); ERR_MSG("byte4 %x byte5 %x byte6 %x additional_len %x", sshdr.byte4, sshdr.byte5, sshdr.byte6, sshdr.additional_length); } return ret; } static int ufsringbuf_issue_vendor_mode_retry(struct ufsf_feature *ufsf, u8 code) { int ret, retries; for (retries = 0; retries < 3; retries++) { ret = ufsringbuf_issue_vendor_mode(ufsf, code); if (ret) ERR_MSG("vendor_mode[%s] issue fail. retries %d", code == ENTER_VENDOR ? "ENTER" : "EXIT", retries + 1); else break; } return ret; } static int ufsringbuf_issue_read_buffer(struct ufsf_feature *ufsf, void *buf, int transfer_bytes) { struct scsi_device *sdev; struct scsi_sense_hdr sshdr; unsigned char cdb[10] = { 0 }; int ret = 0, ret_issue = 0, retries; sdev = ufsringbuf_get_sdev(ufsf, GET_DEFAULT_LU); if (!sdev) return -ENODEV; ret = ufsringbuf_issue_vendor_mode_retry(ufsf, ENTER_VENDOR); if (ret) { ERR_MSG("enter vendor_mode fail. (%d)", ret); ret_issue = -EACCES; goto out; } cdb[0] = READ_BUFFER; cdb[1] = RING_BUFFER_MODE; cdb[2] = (ufsf->ringbuf_dev->volatile_hist) ? GET_VOLATILE_BUF : GET_NON_VOLATILE_BUF; cdb[6] = GET_BYTE_2(transfer_bytes); cdb[7] = GET_BYTE_1(transfer_bytes); cdb[8] = GET_BYTE_0(transfer_bytes); for (retries = 0; retries < 3; retries++) { ret = scsi_execute(sdev, cdb, DMA_FROM_DEVICE, buf, transfer_bytes, NULL, &sshdr, msecs_to_jiffies(30000), 3, 0, 0, NULL); if (ret) INFO_MSG("RB for Ringbuffer fail ret %d retries %d", ret, retries); else break; } INFO_MSG("RB for Ringbuffer %s", ret ? "fail" : "success"); if (ret) { ERR_MSG("code %x sense_key %x asc %x ascq %x", sshdr.response_code, sshdr.sense_key, sshdr.asc, sshdr.ascq); ERR_MSG("byte4 %x byte5 %x byte6 %x additional_len %x", sshdr.byte4, sshdr.byte5, sshdr.byte6, sshdr.additional_length); ret_issue = ret; } out: ret = ufsringbuf_issue_vendor_mode_retry(ufsf, EXIT_VENDOR); if (ret) ERR_MSG("exit vendor_mode fail. (%d)", ret); /* clear header location */ if (ret_issue || ret) memset(buf, 0x00, HIST_BUFFER_HEADER_BYTES); return ret_issue ? ret_issue : ret; } static inline int ufsringbuf_version_check(int spec_version) { INFO_MSG("Support RingBuffer Spec : Driver = (%.4x), Device = (%.4x)", UFSRINGBUF_VER, spec_version); INFO_MSG("Ringbuf D/D version (%.6X%s)", UFSRINGBUF_DD_VER, UFSRINGBUF_DD_VER_POST); if (spec_version != UFSRINGBUF_VER) { ERR_MSG("UFS RingBuffer version mismatched"); return -ENODEV; } return 0; } void ufsringbuf_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf) { int ret = 0, spec_version; ufsf->ringbuf_dev = NULL; if (!(LI_EN_32(&desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP]) & UFS_FEATURE_SUPPORT_RINGBUF_BIT)) { INFO_MSG("bUFSExFeaturesSupport: RingBuffer not support"); goto err_out; } INFO_MSG("bUFSExFeaturesSupport: RingBuffer support"); spec_version = LI_EN_16(&desc_buf[DEVICE_DESC_PARAM_RING_BUF_VER]); ret = ufsringbuf_version_check(spec_version); if (ret) goto err_out; ufsf->ringbuf_dev = kzalloc(sizeof(struct ufsringbuf_dev), GFP_KERNEL); if (!ufsf->ringbuf_dev) { ERR_MSG("ringbuf_dev memalloc fail"); goto err_out; } ufsf->ringbuf_dev->ufsf = ufsf; return; err_out: ufsringbuf_set_state(ufsf, RINGBUF_FAILED); } void ufsringbuf_get_geo_info(struct ufsf_feature *ufsf, u8 *geo_buf) { struct ufsringbuf_dev *ringbuf = ufsf->ringbuf_dev; int hist_buf_size; hist_buf_size = LI_EN_16(&geo_buf[GEOMETRY_DESC_RINGBUF_MAX_HIST_BUFSIZE]); if (!hist_buf_size) { ERR_MSG("history buffer size is 0. so ringbuf disabled"); kfree(ringbuf); ufsringbuf_set_state(ufsf, RINGBUF_FAILED); return; } ringbuf->transfer_bytes = hist_buf_size * HIST_BUFFER_UNIT; INFO_MSG("[57:58] wMaxCommandHistoryBufferSize %u", hist_buf_size); INFO_MSG("RingBuffer Size %u (Bytes)", ringbuf->transfer_bytes); INFO_MSG("max_lines_on_print %d", (ringbuf->transfer_bytes / HIST_BLOCK_DESC_BYTES) - 1); } static inline void ufsringbuf_remove_sysfs(struct ufsringbuf_dev *ringbuf) { int ret; ret = kobject_uevent(&ringbuf->kobj, KOBJ_REMOVE); INFO_MSG("kobject removed (%d)", ret); kobject_del(&ringbuf->kobj); } void ufsringbuf_init(struct ufsf_feature *ufsf) { struct ufsringbuf_dev *ringbuf; int ret; INFO_MSG("RINGBUF_INIT_START"); ringbuf = ufsf->ringbuf_dev; if (!ringbuf) { ERR_MSG("ringbuf_dev was not allocated. so disable ringbuf."); ufsringbuf_set_state(ufsf, RINGBUF_FAILED); return; } if (!ringbuf->transfer_bytes) { ERR_MSG("ringbuf transfer_bytes is zero. so disable ringbuf. "); goto memalloc_fail; } ringbuf->msg_buffer = kzalloc(ringbuf->transfer_bytes, GFP_KERNEL); if (!ringbuf->msg_buffer) { ERR_MSG("ringbuf msg_buffer allocation fail"); goto memalloc_fail; } ringbuf->volatile_hist = false; ringbuf->parsing = false; ringbuf->record_en_drv = false; ringbuf->input_signature = 0; ringbuf->input_parameter = 0; #if defined(CONFIG_UFSRINGBUF_POC) ringbuf->input_signature = INPUT_SIG; ringbuf->input_parameter = INPUT_PARAM; #endif INIT_DELAYED_WORK(&ringbuf->ringbuf_reset_work, ufsringbuf_reset_work_fn); ret = ufsringbuf_create_sysfs(ringbuf); if (ret) { ERR_MSG("Creating UFS Ringbuffer sysfs files. (%d)", ret); goto create_sysfs_fail; } ret = ufsringbuf_create_procfs(ufsf); if (ret) { ERR_MSG("Creating UFS Ringbuffer procfs files. (%d)", ret); goto create_procfs_fail; } INFO_MSG("UFS RingBuffer create sysfs & procfs finished"); ufsringbuf_set_state(ufsf, RINGBUF_PRESENT); return; create_procfs_fail: ufsringbuf_remove_sysfs(ringbuf); create_sysfs_fail: kfree(ringbuf->msg_buffer); memalloc_fail: kfree(ufsf->ringbuf_dev); ufsf->ringbuf_dev = NULL; ufsringbuf_set_state(ufsf, RINGBUF_FAILED); } static void ufsringbuf_print_to_kern(struct ufsf_feature *ufsf) { struct ufsringbuf_dev *ringbuf; int ret; void *buf; ringbuf = ufsf->ringbuf_dev; buf = ringbuf->msg_buffer; ret = ufsringbuf_issue_read_buffer(ufsf, buf, ringbuf->transfer_bytes); if (ret) { ERR_MSG("issue READ_BUFFER for ringbuffer fail. (%d)", ret); return; } ufsringbuf_show_msg(ufsf, NULL, buf); } static void ufsringbuf_reset_work_fn(struct work_struct *dwork) { struct ufsringbuf_dev *ringbuf; struct ufsf_feature *ufsf; ringbuf = container_of(dwork, struct ufsringbuf_dev, ringbuf_reset_work.work); ufsf = ringbuf->ufsf; if (ufsringbuf_get_state(ufsf) != RINGBUF_RESET) return; INFO_MSG("reset work."); pm_runtime_get_sync(ufsf->hba->dev); ufsringbuf_print_to_kern(ufsf); pm_runtime_put_sync(ufsf->hba->dev); /* * The record_en flag was set by user * must be re-enabled by the user when reset occurs. */ ringbuf->record_en_drv = false; /* * The ringbuffer in dev will be init * when user issues record_en by sysfs * So state changes to PRESENT after we get the ringbuffer data from dev * Otherwise, we couldn't get the ringbuffer data before reset */ ufsringbuf_set_state(ufsf, RINGBUF_PRESENT); } void ufsringbuf_reset_host(struct ufsf_feature *ufsf) { ufsringbuf_set_state(ufsf, RINGBUF_RESET); } void ufsringbuf_reset(struct ufsf_feature *ufsf) { struct ufsringbuf_dev *ringbuf = ufsf->ringbuf_dev; if (!ringbuf) return; INFO_MSG("record_en(drv) %d", ringbuf->record_en_drv); if (!ringbuf->record_en_drv) { ufsringbuf_set_state(ufsf, RINGBUF_PRESENT); } else { if (delayed_work_pending(&ringbuf->ringbuf_reset_work)) cancel_delayed_work(&ringbuf->ringbuf_reset_work); schedule_delayed_work(&ringbuf->ringbuf_reset_work, RESET_DELAY); } } static inline void ufsringbuf_remove_procfs(struct ufsringbuf_dev *ringbuf) { struct proc_dir_entry *ringbuf_proc_root = ringbuf->ringbuf_proc_root; if (ringbuf_proc_root) { remove_proc_entry("print", ringbuf_proc_root); remove_proc_entry("ufsringbuf", NULL); ringbuf->ringbuf_proc_root = NULL; INFO_MSG("procfs is removed"); } } void ufsringbuf_remove(struct ufsf_feature *ufsf) { struct ufsringbuf_dev *ringbuf = ufsf->ringbuf_dev; if (!ringbuf) return; INFO_MSG("start RingBuffer release"); ufsringbuf_set_state(ufsf, RINGBUF_FAILED); ufsringbuf_remove_sysfs(ringbuf); ufsringbuf_remove_procfs(ringbuf); kfree(ringbuf->msg_buffer); kfree(ringbuf); ufsf->ringbuf_dev = NULL; INFO_MSG("end RingBuffer release"); } void ufsringbuf_resume(struct ufsf_feature *ufsf) { struct ufsringbuf_dev *ringbuf = ufsf->ringbuf_dev; int ret; if (!ringbuf || !ringbuf->record_en_drv) return; INFO_MSG("ringbuf resume.. record_en_drv(%s)", ufsf->ringbuf_dev->record_en_drv ? "SET_RTC" : "NOT_YET"); ret = ufsringbuf_set_rtc(ufsf); if (ret) ERR_MSG("RTC SET fail. (%d)", ret); else INFO_MSG("RTC SET query success"); } /*********************************************************************** * There is function for PROCFS in below. **********************************************************************/ static int ufsringbuf_proc_print_show(struct seq_file *file, void *data) { struct ufsf_feature *ufsf = (struct ufsf_feature *)file->private; INFO_MSG("print.."); ufsringbuf_show_msg(ufsf, file, ufsf->ringbuf_dev->msg_buffer); return 0; } static int ufsringbuf_proc_print_open(struct inode *inode, struct file *file) { struct ufsf_feature *ufsf = (struct ufsf_feature *)PDE_DATA(inode); struct ufsringbuf_dev *ringbuf = ufsf->ringbuf_dev; struct ufs_hba *hba = ufsf->hba; int transfer_bytes; void *buf; int ret; if (ufsringbuf_is_not_present(ringbuf)) return -ENODEV; transfer_bytes = ringbuf->transfer_bytes; buf = ringbuf->msg_buffer; BUG_ON(!buf); mutex_lock(&ringbuf->sysfs_lock); pm_runtime_get_sync(hba->dev); ret = ufsringbuf_issue_read_buffer(ufsf, buf, transfer_bytes); if (ret) ERR_MSG("read buffer fail. check response code. (%d)", ret); pm_runtime_put_sync(hba->dev); mutex_unlock(&ringbuf->sysfs_lock); return ret ? ret : single_open(file, ufsringbuf_proc_print_show, PDE_DATA(inode)); } static const struct proc_ops fops_proc_print = { .proc_open = ufsringbuf_proc_print_open, .proc_read = seq_read, .proc_release = single_release, }; /*********************************************************************** * There are functions for SYSFS in below. **********************************************************************/ static ssize_t ufsringbuf_sysfs_show_record_en(struct ufsringbuf_dev *ringbuf, char *buf) { struct ufsf_feature *ufsf = ringbuf->ufsf; struct ufs_hba *hba = ufsf->hba; int ret = 0, ret_issue = 0; bool flag_res = false; pm_runtime_get_sync(hba->dev); ret = ufsringbuf_issue_vendor_mode_retry(ufsf, ENTER_VENDOR); if (ret) { ERR_MSG("enter vendor_mode fail. (%d)", ret); ret_issue = -EACCES; goto out; } ret = ufsf_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG, QUERY_FLAG_IDN_CMD_HISTORY_RECORD_EN, 0, UFSFEATURE_SELECTOR, &flag_res); if (ret) { ERR_MSG("query fail. (%d)", ret); ret_issue = ret; } out: ret = ufsringbuf_issue_vendor_mode_retry(ufsf, EXIT_VENDOR); if (ret) ERR_MSG("exit vendor_mode fail. (%d)", ret); pm_runtime_put_sync(hba->dev); if (ret_issue) { INFO_MSG("record_en read query fail! flag_res %d", flag_res); return -ENODEV; } INFO_MSG("record_en read query success! flag_res %d", flag_res); ringbuf->record_en_drv = (flag_res == true) ? 1 : 0; return snprintf(buf, PAGE_SIZE, "%d\n", ringbuf->record_en_drv); } static int __set_record_en(struct ufsf_feature *ufsf, bool set) { int ret = 0, ret_issue = 0; enum query_opcode op = 0; op = set ? (UPIU_QUERY_OPCODE_SET_FLAG) : (UPIU_QUERY_OPCODE_CLEAR_FLAG); INFO_MSG("record_en %s flag query request", set ? "SET" : "CLEAR"); ret = ufsringbuf_issue_vendor_mode_retry(ufsf, ENTER_VENDOR); if (ret) { ERR_MSG("enter vendor_mode fail. (%d)", ret); ret_issue = -EACCES; goto out; } ret = ufsf_query_flag_retry(ufsf->hba, op, QUERY_FLAG_IDN_CMD_HISTORY_RECORD_EN, 0, UFSFEATURE_SELECTOR, NULL); if (ret) { ERR_MSG("query fail. (%d)", ret); ret_issue = ret; } else { INFO_MSG("query success"); ufsf->ringbuf_dev->record_en_drv = set; } out: ret = ufsringbuf_issue_vendor_mode_retry(ufsf, EXIT_VENDOR); if (ret) ERR_MSG("exit vendor_mode fail. (%d)", ret); return ret_issue ? ret_issue : ret; } static ssize_t ufsringbuf_sysfs_store_record_en(struct ufsringbuf_dev *ringbuf, const char *buf, size_t count) { struct ufsf_feature *ufsf = ringbuf->ufsf; struct ufs_hba *hba = ufsf->hba; unsigned long val; int ret = 0; if (kstrtoul(buf, 0, &val)) return -EINVAL; if (!(val == 0 || val == 1)) return -EINVAL; pm_runtime_get_sync(hba->dev); /* * Don't need to set the rtc for unset record_en */ if (val) { ret = ufsringbuf_set_rtc(ufsf); if (ret) { ERR_MSG("RTC SET fail. Stop record_en issue (%d)", ret); pm_runtime_put_sync(hba->dev); return ret; } INFO_MSG("RTC SET query success"); } ret = __set_record_en(ufsf, val ? true : false); pm_runtime_put_sync(hba->dev); return ret ? ret : count; } static int ufsringbuf_check_hex_input(const char *buf, int cnt) { int i; for (i = 0; i < cnt; i++) { if (!((buf[i] >= '0' && buf[i] <= '9') || (buf[i] >= 'A' && buf[i] <= 'F') || (buf[i] >= 'a' && buf[i] <= 'f'))) return -EINVAL; } return 0; } #define ufsringbuf_sysfs_show_func(_name) \ static ssize_t ufsringbuf_sysfs_show_##_name(struct ufsringbuf_dev *ringbuf,\ char *buf) \ { \ INFO_MSG("read "#_name" %u (0x%X)", ringbuf->_name, ringbuf->_name);\ \ return snprintf(buf, PAGE_SIZE, "%u\n", ringbuf->_name); \ } #define ufsringbuf_sysfs_store_func(_name) \ static ssize_t ufsringbuf_sysfs_store_##_name(struct ufsringbuf_dev *ringbuf,\ const char *buf, \ size_t count) \ { \ unsigned long val; \ \ if (kstrtoul(buf, 0, &val)) \ return -EINVAL; \ \ if (!(val == 0 || val == 1)) \ return -EINVAL; \ \ ringbuf->_name = val; \ \ INFO_MSG(#_name " success = %d", ringbuf->_name); \ return count; \ } #define ufsringbuf_sysfs_store_func_vendor(_name) \ static ssize_t ufsringbuf_sysfs_store_##_name(struct ufsringbuf_dev *ringbuf,\ const char *buf, \ size_t count) \ { \ int ret, size; \ \ size = strlen(buf); \ \ if (size != count || size - 1 != VENDOR_INPUT_LEN) { \ ERR_MSG("buf size(%d) is not match to count(%ld)", \ size, count); \ ERR_MSG("(vendor input size is (%d))", \ VENDOR_INPUT_LEN); \ return -EINVAL; \ } \ ret = ufsringbuf_check_hex_input(buf, count - 1); \ if (ret) { \ ERR_MSG("input is not hex value. input (%s)", buf); \ return ret; \ } \ ret = kstrtouint(buf, 16, &ringbuf->_name); \ if (ret) { \ ERR_MSG("input is not valid. (%d)", ret); \ return ret; \ } \ INFO_MSG(#_name" (0x%08X)", ringbuf->_name); \ return count; \ } ufsringbuf_sysfs_show_func(volatile_hist); ufsringbuf_sysfs_store_func(volatile_hist); ufsringbuf_sysfs_show_func(parsing); ufsringbuf_sysfs_store_func(parsing); ufsringbuf_sysfs_store_func_vendor(input_signature); ufsringbuf_sysfs_store_func_vendor(input_parameter); #if defined(CONFIG_UFSRINGBUF_POC) /* * For POC. */ static ssize_t ufsringbuf_sysfs_store_debug_set_pwd(struct ufsringbuf_dev *ringbuf, const char *buf, size_t count) { struct ufsf_feature *ufsf = ringbuf->ufsf; struct scsi_sense_hdr sshdr; unsigned char cdb[10] = { 0 }; struct scsi_device *sdev = NULL; int ret = 0; pm_runtime_get_sync(ufsf->hba->dev); sdev = ufsringbuf_get_sdev(ufsf, GET_DEFAULT_LU); if (!sdev) { ret = -ENODEV; goto out; } cdb[0] = VENDOR_CMD_OP; cdb[1] = SET_PASSWD; /* Signature of Ringbuf Vendor Command */ put_unaligned_be32(RINGBUF_VENDOR_SIG, &cdb[2]); ret = scsi_execute(sdev, cdb, DMA_NONE, NULL, 0, NULL, &sshdr, VENDOR_CMD_TIMEOUT, 0, 0, 0, NULL); INFO_MSG("vendor(SET_PWD) command %s", ret ? "fail" : "successful"); if (ret) { ERR_MSG("code %x sense_key %x asc %x ascq %x", sshdr.response_code, sshdr.sense_key, sshdr.asc, sshdr.ascq); ERR_MSG("byte4 %x byte5 %x byte6 %x additional_len %x", sshdr.byte4, sshdr.byte5, sshdr.byte6, sshdr.additional_length); } out: pm_runtime_put_sync(ufsf->hba->dev); return ret ? ret : count; } static ssize_t ufsringbuf_sysfs_show_issue_abort_cmd(struct ufsringbuf_dev *ringbuf, char *buf) { struct ufsf_feature *ufsf = ringbuf->ufsf; u8 resp = 0xF; int err; pm_runtime_get_sync(ufsf->hba->dev); err = ufsf_issue_tm_cmd(ufsf->hba, 0, 0, UFS_ABORT_TASK, &resp); pm_runtime_put_sync(ufsf->hba->dev); if (err) { pr_err("%s: issued. tag = 0, fail\n", __func__); return err; } if (resp != UPIU_TASK_MANAGEMENT_FUNC_COMPL) { err = resp; /* service response error */ pr_err("%s: issued. tag = 0, err %d\n", __func__, err); return -EIO; } return snprintf(buf, PAGE_SIZE, "TMF COMMAND ABORT success\n"); } #endif /* SYSFS DEFINE */ #define define_sysfs_rw(_name) __ATTR(_name, 0644, \ ufsringbuf_sysfs_show_##_name, \ ufsringbuf_sysfs_store_##_name) #define define_sysfs_ro(_name) __ATTR(_name, 0444, \ ufsringbuf_sysfs_show_##_name, NULL) #define define_sysfs_wo(_name) __ATTR(_name, 0220, NULL, \ ufsringbuf_sysfs_store_##_name) static struct ufsringbuf_sysfs_entry ufsringbuf_sysfs_entries[] = { define_sysfs_rw(record_en), define_sysfs_rw(volatile_hist), define_sysfs_rw(parsing), define_sysfs_wo(input_signature), define_sysfs_wo(input_parameter), #if defined(CONFIG_UFSRINGBUF_POC) /* * This function will be removed in released version. * It exists only for POC purpose. */ define_sysfs_wo(debug_set_pwd), define_sysfs_ro(issue_abort_cmd), #endif __ATTR_NULL }; static ssize_t ufsringbuf_attr_show(struct kobject *kobj, struct attribute *attr, char *page) { struct ufsringbuf_sysfs_entry *entry; struct ufsringbuf_dev *ringbuf; ssize_t error; entry = container_of(attr, struct ufsringbuf_sysfs_entry, attr); if (!entry->show) return -EIO; ringbuf = container_of(kobj, struct ufsringbuf_dev, kobj); if (ufsringbuf_is_not_present(ringbuf)) return -ENODEV; mutex_lock(&ringbuf->sysfs_lock); error = entry->show(ringbuf, page); mutex_unlock(&ringbuf->sysfs_lock); return error; } static ssize_t ufsringbuf_attr_store(struct kobject *kobj, struct attribute *attr, const char *page, size_t length) { struct ufsringbuf_sysfs_entry *entry; struct ufsringbuf_dev *ringbuf; ssize_t error; entry = container_of(attr, struct ufsringbuf_sysfs_entry, attr); if (!entry->store) return -EIO; ringbuf = container_of(kobj, struct ufsringbuf_dev, kobj); if (ufsringbuf_is_not_present(ringbuf)) return -ENODEV; mutex_lock(&ringbuf->sysfs_lock); error = entry->store(ringbuf, page, length); mutex_unlock(&ringbuf->sysfs_lock); return error; } static const struct sysfs_ops ufsringbuf_sysfs_ops = { .show = ufsringbuf_attr_show, .store = ufsringbuf_attr_store, }; static struct kobj_type ufsringbuf_ktype = { .sysfs_ops = &ufsringbuf_sysfs_ops, .release = NULL, }; static int ufsringbuf_create_sysfs(struct ufsringbuf_dev *ringbuf) { struct device *dev = ringbuf->ufsf->hba->dev; struct ufsringbuf_sysfs_entry *entry; int err; ringbuf->sysfs_entries = ufsringbuf_sysfs_entries; kobject_init(&ringbuf->kobj, &ufsringbuf_ktype); mutex_init(&ringbuf->sysfs_lock); INFO_MSG("ufsringbuf creates sysfs ufsringbuf %p dev->kobj %p", &ringbuf->kobj, &dev->kobj); err = kobject_add(&ringbuf->kobj, kobject_get(&dev->kobj), "ufsringbuf"); if (!err) { for (entry = ringbuf->sysfs_entries; entry->attr.name != NULL; entry++) { INFO_MSG("ufsringbuf sysfs attr creates: %s", entry->attr.name); err = sysfs_create_file(&ringbuf->kobj, &entry->attr); if (err) { ERR_MSG("create entry(%s) failed", entry->attr.name); goto kobj_del; } } kobject_uevent(&ringbuf->kobj, KOBJ_ADD); } else { ERR_MSG("kobject_add failed"); } return err; kobj_del: err = kobject_uevent(&ringbuf->kobj, KOBJ_REMOVE); INFO_MSG("kobject removed (%d)", err); kobject_del(&ringbuf->kobj); return -EINVAL; } static int ufsringbuf_create_procfs(struct ufsf_feature *ufsf) { struct proc_dir_entry *ringbuf_proc_root; ringbuf_proc_root = proc_mkdir("ufsringbuf", NULL); if (!ringbuf_proc_root) { ERR_MSG("Create ringbuf directory fail"); return -ENODEV; } proc_create_data("print", 0444, ringbuf_proc_root, &fops_proc_print, ufsf); ufsf->ringbuf_dev->ringbuf_proc_root = ringbuf_proc_root; return 0; } MODULE_LICENSE("GPL v2");