/* * Copyright (C) 2017 MediaTek Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. */ #ifdef pr_fmt #undef pr_fmt #endif #define pr_fmt(fmt) "["KBUILD_MODNAME"]" fmt #include #include #include #include #include #include #include #include #include #include #include #include "ufs-mtk-dbg.h" #include "ufs.h" #include #ifndef FPGA_PLATFORM #ifdef UPMU_READY #include #endif #endif #ifdef CONFIG_MTK_UFS_DEBUG #define MAX_UFS_CMD_HLIST_ENTRY_CNT (500) /* max dump size is 40KB whitch can be adjusted */ #define UFS_AEE_BUFFER_SIZE (100 * 1024) struct ufs_cmd_hlist_struct ufs_cmd_hlist[MAX_UFS_CMD_HLIST_ENTRY_CNT]; int ufs_cmd_ptr = MAX_UFS_CMD_HLIST_ENTRY_CNT - 1; int ufs_cmd_cnt; static spinlock_t ufs_mtk_cmd_dump_lock; static int ufs_mtk_is_cmd_dump_lock_init; static atomic_t cmd_hist_enabled; char ufs_aee_buffer[UFS_AEE_BUFFER_SIZE]; static void ufs_mtk_dbg_dump_feature(struct ufs_hba *hba, struct seq_file *m) { UFS_DEVINFO_PROC_MSG(m, hba->dev, "-- Crypto Features ----\n"); UFS_DEVINFO_PROC_MSG(m, hba->dev, "Features = 0x%x\n", hba->crypto_feature); UFS_DEVINFO_PROC_MSG(m, hba->dev, " HW-FDE = 0x%x\n", UFS_CRYPTO_HW_FDE); UFS_DEVINFO_PROC_MSG(m, hba->dev, " HW-FDE Encrypted = 0x%x\n", UFS_CRYPTO_HW_FDE_ENCRYPTED); UFS_DEVINFO_PROC_MSG(m, hba->dev, " HW-FBE = 0x%x\n", UFS_CRYPTO_HW_FBE); UFS_DEVINFO_PROC_MSG(m, hba->dev, " HW-FBE Encrypted = 0x%x\n", UFS_CRYPTO_HW_FBE_ENCRYPTED); UFS_DEVINFO_PROC_MSG(m, hba->dev, "-----------------------\n"); } #endif void ufs_mtk_dbg_stop_trace(struct ufs_hba *hba) { #ifdef CONFIG_MTK_UFS_DEBUG atomic_set(&cmd_hist_enabled, 0); dev_info(hba->dev, "cmd history off\n"); #endif } void ufs_mtk_dbg_start_trace(struct ufs_hba *hba) { #ifdef CONFIG_MTK_UFS_DEBUG atomic_set(&cmd_hist_enabled, 1); dev_info(hba->dev, "cmd history on\n"); #endif } void ufs_mtk_dbg_add_trace(struct ufs_hba *hba, enum ufs_trace_event event, u32 tag, u8 lun, u32 transfer_len, sector_t lba, u8 opcode, unsigned long long ppn, u32 region, u32 subregion, u32 resv) { #ifdef CONFIG_MTK_UFS_DEBUG int ptr; unsigned long flags; if (!ufs_mtk_is_cmd_dump_lock_init) { spin_lock_init(&ufs_mtk_cmd_dump_lock); ufs_mtk_is_cmd_dump_lock_init = 1; atomic_set(&cmd_hist_enabled, 1); } if (!atomic_read(&cmd_hist_enabled) && event != UFS_TRACE_DEBUG_PROC) return; spin_lock_irqsave(&ufs_mtk_cmd_dump_lock, flags); ufs_cmd_ptr++; if (ufs_cmd_ptr >= MAX_UFS_CMD_HLIST_ENTRY_CNT) ufs_cmd_ptr = 0; ptr = ufs_cmd_ptr; ufs_cmd_hlist[ptr].pid = current->pid; ufs_cmd_hlist[ptr].event = event; ufs_cmd_hlist[ptr].tag = tag; ufs_cmd_hlist[ptr].transfer_len = transfer_len; ufs_cmd_hlist[ptr].lun = lun; ufs_cmd_hlist[ptr].lba = lba; ufs_cmd_hlist[ptr].opcode = opcode; ufs_cmd_hlist[ptr].time = sched_clock(); ufs_cmd_hlist[ptr].duration = 0; ufs_cmd_hlist[ptr].rq = NULL; ufs_cmd_hlist[ptr].cpu = smp_processor_id(); ufs_cmd_hlist[ptr].ppn = ppn; ufs_cmd_hlist[ptr].region = region; ufs_cmd_hlist[ptr].subregion = subregion; ufs_cmd_hlist[ptr].resv = resv; /* keep request pointer to dig out block layer status */ if (((event == UFS_TRACE_SEND) || (event == UFS_TRACE_COMPLETED) || (event == UFS_TRACE_GENERIC)) && tag != 0xFF) { if (hba->lrb[tag].cmd && hba->lrb[tag].cmd->request) { ufs_cmd_hlist[ptr].rq = hba->lrb[tag].cmd->request; ufs_cmd_hlist[ptr].crypted = hba->lrb[tag].crypto_enable; ufs_cmd_hlist[ptr].keyslot = ufs_cmd_hlist[ptr].crypted ? hba->lrb[tag].crypto_key_slot : 0; } } if (event == UFS_TRACE_COMPLETED) { if (ufs_cmd_ptr == 0) ptr = MAX_UFS_CMD_HLIST_ENTRY_CNT - 1; else ptr = ufs_cmd_ptr - 1; while (1) { if (ufs_cmd_hlist[ptr].tag == tag) { ufs_cmd_hlist[ufs_cmd_ptr].duration = sched_clock() - ufs_cmd_hlist[ptr].time; break; } ptr--; if (ptr < 0) ptr = MAX_UFS_CMD_HLIST_ENTRY_CNT - 1; if (ufs_cmd_ptr == 0) { if (ptr == (MAX_UFS_CMD_HLIST_ENTRY_CNT - 1)) break; } else { if (ptr == ufs_cmd_ptr - 1) break; } } /* Over 1 second, record performance warning */ if (ufs_cmd_hlist[ufs_cmd_ptr].duration >= 1000000000) { /* * op code[31:24] * length(4KB)[23:16] * duration(ms)[15:0] */ ufshcd_update_evt_hist(hba, UFS_EVT_PERF_WARN, (u32) ((opcode << 24) | (((transfer_len >> 12) & 0xFF) << 16) | #if BITS_PER_LONG == 32 (div_u64(ufs_cmd_hlist[ufs_cmd_ptr].duration, 1000000)))); #else (ufs_cmd_hlist[ufs_cmd_ptr].duration / 1000000))); #endif } } ufs_cmd_cnt++; spin_unlock_irqrestore(&ufs_mtk_cmd_dump_lock, flags); #endif } void ufs_mtk_dbg_dump_trace(char **buff, unsigned long *size, u32 latest_cnt, struct seq_file *m) { #ifdef CONFIG_MTK_UFS_DEBUG int ptr; int dump_cnt; unsigned long flags; if (!ufs_mtk_is_cmd_dump_lock_init) { spin_lock_init(&ufs_mtk_cmd_dump_lock); ufs_mtk_is_cmd_dump_lock_init = 1; } spin_lock_irqsave(&ufs_mtk_cmd_dump_lock, flags); if (ufs_cmd_cnt > MAX_UFS_CMD_HLIST_ENTRY_CNT) dump_cnt = MAX_UFS_CMD_HLIST_ENTRY_CNT; else dump_cnt = ufs_cmd_cnt; if (latest_cnt) dump_cnt = min_t(u32, latest_cnt, dump_cnt); ptr = ufs_cmd_ptr; SPREAD_PRINTF(buff, size, m, "[ufs] CMD History: req_cnt=%d, real_cnt=%d, ptr=%d\n", latest_cnt, dump_cnt, ptr); while (dump_cnt > 0) { if ((ufs_cmd_hlist[ptr].event >= UFS_TRACE_UIC_SEND) && (ufs_cmd_hlist[ptr].event <= UFS_TRACE_UIC_CMPL_PWR_CTRL)) { SPREAD_PRINTF(buff, size, m, "%3d-u(%d),%5d,%2d,0x%2X,arg1=0x%X,arg2=0x%X,arg3=0x%X,%llu\n", ptr, ufs_cmd_hlist[ptr].cpu, ufs_cmd_hlist[ptr].pid, ufs_cmd_hlist[ptr].event, ufs_cmd_hlist[ptr].opcode, /* command */ ufs_cmd_hlist[ptr].tag, /* argument1 */ ufs_cmd_hlist[ptr].transfer_len, /* argument2 */ (u32)ufs_cmd_hlist[ptr].lba, /* argument3 */ (u64)ufs_cmd_hlist[ptr].time ); } else if (ufs_cmd_hlist[ptr].event == UFS_TRACE_REG_TOGGLE) { SPREAD_PRINTF(buff, size, m, "%3d-g(%d),%5d,state=%d,on=%d,%llu\n", ptr, ufs_cmd_hlist[ptr].cpu, ufs_cmd_hlist[ptr].pid, ufs_cmd_hlist[ptr].tag, /* state */ ufs_cmd_hlist[ptr].transfer_len, /* on or off */ (u64)ufs_cmd_hlist[ptr].time ); } else if (ufs_cmd_hlist[ptr].event == UFS_TRACE_TM_SEND || ufs_cmd_hlist[ptr].event == UFS_TRACE_TM_COMPLETED) { SPREAD_PRINTF(buff, size, m, "%3d-t(%d),%5d,%2d,tm=0x%x,t=%2d,lun=0x%x,data=0x%x,%llu\n", ptr, ufs_cmd_hlist[ptr].cpu, ufs_cmd_hlist[ptr].pid, ufs_cmd_hlist[ptr].event, ufs_cmd_hlist[ptr].opcode, ufs_cmd_hlist[ptr].tag, ufs_cmd_hlist[ptr].lun, ufs_cmd_hlist[ptr].transfer_len, (u64)ufs_cmd_hlist[ptr].time ); } else if (ufs_cmd_hlist[ptr].event == UFS_TRACE_DEV_SEND || ufs_cmd_hlist[ptr].event == UFS_TRACE_DEV_COMPLETED) { SPREAD_PRINTF(buff, size, m, "%3d-d(%d),%5d,%2d,0x%2x,t=%2d,lun=0x%x,idn=0x%x,idx=0x%x,sel=0x%x,%llu\n", ptr, ufs_cmd_hlist[ptr].cpu, ufs_cmd_hlist[ptr].pid, ufs_cmd_hlist[ptr].event, ufs_cmd_hlist[ptr].opcode, ufs_cmd_hlist[ptr].tag, ufs_cmd_hlist[ptr].lun, (u8)ufs_cmd_hlist[ptr].lba & 0xFF, (u8)(ufs_cmd_hlist[ptr].lba >> 8) & 0xFF, (u8)(ufs_cmd_hlist[ptr].lba >> 16) & 0xFF, (u64)ufs_cmd_hlist[ptr].time ); } else if (ufs_cmd_hlist[ptr].event == UFS_TRACE_GENERIC) { SPREAD_PRINTF(buff, size, m, "%3d-G(%d),%5d,%2d,0x%2x,t=%2d,lba=0x%llx,len=%6d,cmd(req)=0x%X,err(time)=%d,line=%d,%llu\n", ptr, ufs_cmd_hlist[ptr].cpu, ufs_cmd_hlist[ptr].pid, ufs_cmd_hlist[ptr].event, ufs_cmd_hlist[ptr].opcode, ufs_cmd_hlist[ptr].tag, (long long int)ufs_cmd_hlist[ptr].lba, ufs_cmd_hlist[ptr].transfer_len, ufs_cmd_hlist[ptr].region, ufs_cmd_hlist[ptr].subregion, ufs_cmd_hlist[ptr].resv, (u64)ufs_cmd_hlist[ptr].time ); } else { SPREAD_PRINTF(buff, size, m, "%3d-r(%d),%5d,%2d,0x%2x,t=%2d,lun=0x%x,crypt:%d,%d,lba=0x%llx,len=%6d,%llu,\t%llu", ptr, ufs_cmd_hlist[ptr].cpu, ufs_cmd_hlist[ptr].pid, ufs_cmd_hlist[ptr].event, ufs_cmd_hlist[ptr].opcode, ufs_cmd_hlist[ptr].tag, ufs_cmd_hlist[ptr].lun, ufs_cmd_hlist[ptr].crypted, ufs_cmd_hlist[ptr].keyslot, (long long int)ufs_cmd_hlist[ptr].lba, ufs_cmd_hlist[ptr].transfer_len, (u64)ufs_cmd_hlist[ptr].time, (u64)ufs_cmd_hlist[ptr].duration ); #if defined(CONFIG_UFSHPB) if (ufs_cmd_hlist[ptr].opcode == READ_16) { SPREAD_PRINTF(buff, size, m, ",\tppn=0x%llx,\tregion=0x%x,\tsubregion=0x%x,\tresv=0x%x", ufs_cmd_hlist[ptr].ppn, ufs_cmd_hlist[ptr].region, ufs_cmd_hlist[ptr].subregion, ufs_cmd_hlist[ptr].resv ); } if (ufs_cmd_hlist[ptr].opcode == UFSHPB_WRITE_BUFFER) { SPREAD_PRINTF(buff, size, m, ",\tlba=0x%llx,\tbuf_len=0x%x", ufs_cmd_hlist[ptr].ppn, ufs_cmd_hlist[ptr].region ); } #endif SPREAD_PRINTF(buff, size, m, "\n"); } dump_cnt--; ptr--; if (ptr < 0) ptr = MAX_UFS_CMD_HLIST_ENTRY_CNT - 1; } spin_unlock_irqrestore(&ufs_mtk_cmd_dump_lock, flags); #endif } void ufs_mtk_dme_cmd_log(struct ufs_hba *hba, struct uic_command *ucmd, enum ufs_trace_event event) { #ifdef CONFIG_MTK_UFS_DEBUG u32 cmd; if (event == UFS_TRACE_UIC_SEND) cmd = ucmd->command; else cmd = ufshcd_readl(hba, REG_UIC_COMMAND); ufs_mtk_dbg_add_trace(hba, event, ufshcd_readl(hba, REG_UIC_COMMAND_ARG_1), 0, ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2), ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3), cmd, 0, 0, 0, 0); #endif } void ufs_mtk_dbg_hang_detect_dump(void) { #ifdef CONFIG_MTK_UFS_DEBUG int boot_type; boot_type = get_boot_type(); if (boot_type != BOOTDEV_UFS) return; /* * do not touch host to get unipro or mphy information via * dme commands during exception handling since interrupt * or preemption may be disabled. */ ufshcd_print_host_state(ufs_mtk_hba, 0, NULL, NULL, NULL); ufs_mtk_dbg_dump_trace(NULL, NULL, ufs_mtk_hba->nutrs + ufs_mtk_hba->nutrs / 2, NULL); ufshcd_print_all_evt_hist(ufs_mtk_hba, NULL, NULL, NULL); #endif } void ufs_mtk_dbg_proc_dump(struct seq_file *m) { #ifdef CONFIG_MTK_UFS_DEBUG ufs_mtk_dbg_dump_feature(ufs_mtk_hba, m); /* * do not touch host to get unipro or mphy information via * dme commands during exception handling since interrupt * or preemption may be disabled. */ ufshcd_print_host_state(ufs_mtk_hba, 0, m, NULL, NULL); ufs_mtk_dbg_dump_trace(NULL, NULL, MAX_UFS_CMD_HLIST_ENTRY_CNT, m); ufshcd_print_all_evt_hist(ufs_mtk_hba, m, NULL, NULL); #endif } void get_ufs_aee_buffer(unsigned long *vaddr, unsigned long *size) { #ifdef CONFIG_MTK_UFS_DEBUG unsigned long free_size = UFS_AEE_BUFFER_SIZE; char *buff; if (ufs_mtk_hba == NULL) { pr_info("====== Null ufs_mtk_hba, dump skipped ======\n"); return; } buff = ufs_aee_buffer; ufshcd_print_host_state(ufs_mtk_hba, 0, NULL, &buff, &free_size); ufs_mtk_dbg_dump_trace(&buff, &free_size, MAX_UFS_CMD_HLIST_ENTRY_CNT, NULL); ufshcd_print_all_evt_hist(ufs_mtk_hba, NULL, &buff, &free_size); /* retrun start location */ *vaddr = (unsigned long)ufs_aee_buffer; *size = UFS_AEE_BUFFER_SIZE - free_size; ufs_mtk_dbg_start_trace(ufs_mtk_hba); #endif } EXPORT_SYMBOL(get_ufs_aee_buffer); static int ufsdbg_dump_health_desc(struct seq_file *file) { #ifdef CONFIG_MTK_UFS_DEBUG int err = 0; int buff_len = QUERY_DESC_HEALTH_MAX_SIZE; u8 desc_buf[QUERY_DESC_HEALTH_MAX_SIZE]; int i; pm_runtime_get_sync(ufs_mtk_hba->dev); err = ufshcd_read_health_desc(ufs_mtk_hba, desc_buf, buff_len); pm_runtime_put_sync(ufs_mtk_hba->dev); if (err) { seq_printf(file, "Reading Health Descriptor failed. err = %d\n", err); goto out; } for (i = 0; i < QUERY_DESC_HEALTH_MAX_SIZE; i++) { seq_printf(file, "Health Descriptor[0x%x] = 0x%x\n", i, (u8)desc_buf[i]); } seq_printf(file, "Health Descriptor[offset 0x02]: %s = 0x%x\n", "bPreEOLInfo", (u8)desc_buf[2]); seq_printf(file, "Health Descriptor[offset 0x03]: %s = 0x%x\n", "bDeviceLifeTimeEstA", (u8)desc_buf[3]); seq_printf(file, "Health Descriptor[offset 0x04]: %s = 0x%x\n", "bDeviceLifeTimeEstB", (u8)desc_buf[4]); out: return err; #else return 0; #endif } static char cmd_buf[256]; static int ufs_help_proc_show(struct seq_file *m, void *v) { seq_puts(m, "\n===============[ufs_help]================\n"); seq_printf(m, "\n Commands dump: echo %x [host_id] > ufs_debug\n", UFS_CMDS_DUMP); seq_printf(m, "\n Get Power Mode Status: echo %x [host_id] > ufs_debug\n", UFS_GET_PWR_MODE); seq_printf(m, "\n Dump Health Descriptors: echo %x [host_id] > ufs_debug\n", UFS_DUMP_HEALTH_DESCRIPTOR); seq_puts(m, "\n NOTE: All input data is Hex number!\n"); seq_puts(m, "\n=============================================\n\n"); return 0; } /* ========== driver proc interface =========== */ static int ufs_debug_proc_show(struct seq_file *m, void *v) { unsigned long cmd; seq_printf(m, "ufsdbg: debug command: %s\n", cmd_buf); if (kstrtoul(cmd_buf, 10, &cmd)) cmd = UFS_CMDS_DUMP; cmd_buf[0] = '\0'; if (cmd == UFS_CMDS_DUMP) { /* * Default print cmd history for aee: * JE/NE/ANR/EE/SWT/system api dump */ seq_puts(m, "==== UFS Debug Info ====\n"); ufs_mtk_dbg_proc_dump(m); } else if (cmd == UFS_GET_PWR_MODE) { seq_puts(m, "(1:FAST 2:SLOW 4:FAST_AUTO 5:SLOW_AUTO 7:UNCHANGE)\n"); seq_printf(m, "Power Mode: tx 0x%x rx 0x%x\n", ufs_mtk_hba->pwr_info.pwr_tx, ufs_mtk_hba->pwr_info.pwr_rx); seq_printf(m, "Gear: tx 0x%x rx 0x%x\n", ufs_mtk_hba->pwr_info.gear_tx, ufs_mtk_hba->pwr_info.gear_rx); seq_printf(m, "HS Rate: 0x%x (1:HS_A 2:HS_B)\n", ufs_mtk_hba->pwr_info.hs_rate); seq_printf(m, "Lanes: tx 0x%x rx 0x%x\n", ufs_mtk_hba->pwr_info.lane_tx, ufs_mtk_hba->pwr_info.lane_rx); } else if (cmd == UFS_DUMP_HEALTH_DESCRIPTOR) { ufsdbg_dump_health_desc(m); } return 0; } static ssize_t ufs_debug_proc_write(struct file *file, const char *buf, size_t count, loff_t *data) { unsigned long op = UFS_CMD_UNKNOWN; bool handled = true; struct ufs_hba *hba = ufs_mtk_hba; struct ufs_mtk_host *host = ufshcd_get_variant(hba); if (count == 0 || count > 255) return -EINVAL; if (copy_from_user(cmd_buf, buf, count)) return -EINVAL; /* * Let's handle simple commands here. * Simple command can be executed immediately * after command is written and do not need further * "read" or "cat" anymore. */ cmd_buf[count] = '\0'; if (kstrtoul(cmd_buf, 10, &op)) return -EINVAL; if (op == UFS_CMD_HIST_BEGIN) { atomic_set(&cmd_hist_enabled, 1); dev_info(hba->dev, "cmd history on\n"); } else if (op == UFS_CMD_HIST_STOP) { atomic_set(&cmd_hist_enabled, 0); dev_info(hba->dev, "cmd history off\n"); } else if (op == UFS_CMD_QOS_ON) { if (host && host->qos_allowed) { host->qos_enabled = true; dev_info(hba->dev, "QoS on\n"); } } else if (op == UFS_CMD_QOS_OFF) { if (host && host->qos_allowed) { host->qos_enabled = false; dev_info(hba->dev, "QoS off\n"); } } else { handled = false; } ufs_mtk_dbg_add_trace(hba, UFS_TRACE_DEBUG_PROC, op, 0, atomic_read(&cmd_hist_enabled), 0, 0, 0, 0, 0, 0); if (handled) cmd_buf[0] = '\0'; return count; } static int ufs_perf_proc_show(struct seq_file *m, void *v) { struct ufs_mtk_host *host = m->private; seq_puts(m, "UFS Performance Mode\n"); seq_printf(m, " - supported: %d\n", ufs_mtk_perf_is_supported(host)); seq_printf(m, " - enabled: %d\n", host->perf_en); seq_printf(m, " - mode: %d\n", host->perf_mode); seq_printf(m, " - crypto_vcore_opp: %d\n", host->crypto_vcore_opp); return 0; } static ssize_t ufs_perf_proc_write(struct file *file, const char *ubuf, size_t count, loff_t *data) { struct ufs_mtk_host *host = PDE_DATA(file->f_mapping->host); unsigned long op = UFS_CMD_UNKNOWN; char cmd[16] = {0}; loff_t buff_pos = 0; int ret = 0, last_mode; ret = simple_write_to_buffer(cmd, 15, &buff_pos, ubuf, count); if (ret < 0) { dev_info(host->hba->dev, "%s: failed to read user data\n", __func__); return -EINVAL; } cmd[ret] = '\0'; if (kstrtoul(cmd, 10, &op)) return -EINVAL; last_mode = host->perf_mode; if (op == PERF_AUTO) { host->perf_mode = PERF_AUTO; ret = 0; } else if (op == PERF_FORCE_ENABLE && host->perf_mode != PERF_FORCE_ENABLE) { host->perf_mode = PERF_FORCE_ENABLE; ret = ufs_mtk_perf_setup_crypto_clk(host, true); } else if (op == PERF_FORCE_DISABLE && host->perf_mode != PERF_FORCE_DISABLE) { host->perf_mode = PERF_FORCE_DISABLE; ret = ufs_mtk_perf_setup_crypto_clk(host, false); } else return -EINVAL; if (ret) host->perf_mode = last_mode; return count; } static int ufs_proc_open(struct inode *inode, struct file *file) { return single_open(file, ufs_debug_proc_show, inode->i_private); } static const struct file_operations ufs_proc_fops = { .open = ufs_proc_open, .write = ufs_debug_proc_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int ufs_help_proc_open(struct inode *inode, struct file *file) { return single_open(file, ufs_help_proc_show, inode->i_private); } static const struct file_operations ufs_help_fops = { .open = ufs_help_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int ufs_perf_proc_open(struct inode *inode, struct file *file) { return single_open(file, ufs_perf_proc_show, PDE_DATA(inode)); } static const struct file_operations ufs_perf_fops = { .open = ufs_perf_proc_open, .write = ufs_perf_proc_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #ifndef USER_BUILD_KERNEL #define PROC_PERM 0660 #else #define PROC_PERM 0440 #endif int ufs_mtk_debug_proc_init(struct ufs_hba *hba) { struct ufs_mtk_host *host; struct proc_dir_entry *prEntry; kuid_t uid; kgid_t gid; if (!hba || !hba->priv) { pr_info("%s: NULL host, exiting\n", __func__); return -EINVAL; } host = hba->priv; uid = make_kuid(&init_user_ns, 0); gid = make_kgid(&init_user_ns, 1001); prEntry = proc_create("ufs_debug", PROC_PERM, NULL, &ufs_proc_fops); if (prEntry) proc_set_user(prEntry, uid, gid); else pr_info("%s: failed to create /proc/ufs_debug\n", __func__); prEntry = proc_create("ufs_help", PROC_PERM, NULL, &ufs_help_fops); if (!prEntry) pr_info("%s: failed to create /proc/ufs_help\n", __func__); /* allow write permission in all builds */ prEntry = proc_create_data("ufs_perf", 0660, NULL, &ufs_perf_fops, host); if (prEntry) proc_set_user(prEntry, uid, gid); else pr_info("%s: failed to create /proc/ufs_perf\n", __func__); return 0; } EXPORT_SYMBOL_GPL(ufs_mtk_debug_proc_init);