| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Universal Flash Storage Ring Buffer |
| * |
| * Copyright (C) 2019-2019 Samsung Electronics Co., Ltd. |
| * |
| * Authors: |
| * Yongmyung Lee <ymhungry.lee@samsung.com> |
| * Jinyoung Choi <j-young.choi@samsung.com> |
| * |
| * 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 |
| * <http://www.gnu.org/licenses/gpl-2.0.html> |
| * |
| * 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"); |