diff mbox series

[RFC,v1,055/256] cl8k: add debugfs.c

Message ID 20210617160223.160998-56-viktor.barna@celeno.com (mailing list archive)
State RFC
Delegated to: Kalle Valo
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna June 17, 2021, 3:59 p.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@celeno.com>
---
 drivers/net/wireless/celeno/cl8k/debugfs.c | 957 +++++++++++++++++++++
 1 file changed, 957 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/debugfs.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/debugfs.c b/drivers/net/wireless/celeno/cl8k/debugfs.c
new file mode 100644
index 000000000000..79854dfd85ea
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/debugfs.c
@@ -0,0 +1,957 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/list.h>
+
+#include "debugfs.h"
+#include "chip.h"
+#include "fw/msg_tx.h"
+#include "fw/msg_cfm.h"
+#include "tx/tx.h"
+#include "rate_ctrl.h"
+#include "utils/utils.h"
+#include "coredump.h"
+#include "fw/fw_dbg.h"
+#include "dbgfile.h"
+
+#define DEBUGFS_ADD_FILE(name, parent, mode)                                          \
+do {                                                                                  \
+       if (!debugfs_create_file(#name, mode, parent, cl_hw, &cl_dbgfs_##name##_ops)) \
+               goto err;                                                             \
+} while (0)
+
+/* File operation */
+#define DEBUGFS_READ_FUNC(name)                                  \
+       static ssize_t cl_dbgfs_##name##_read(struct file *file, \
+                       char __user *user_buf,                   \
+                       size_t count, loff_t *ppos)
+
+#define DEBUGFS_WRITE_FUNC(name)                                  \
+       static ssize_t cl_dbgfs_##name##_write(struct file *file, \
+                       const char __user *user_buf,              \
+                       size_t count, loff_t *ppos)
+
+#define DEBUGFS_READ_FILE_OPS(name)                                   \
+       DEBUGFS_READ_FUNC(name);                                      \
+       static const struct file_operations cl_dbgfs_##name##_ops = { \
+               .read   = cl_dbgfs_##name##_read,                     \
+               .open   = simple_open,                                \
+               .llseek = generic_file_llseek,                        \
+       }
+
+#define DEBUGFS_WRITE_FILE_OPS(name)                                  \
+       DEBUGFS_WRITE_FUNC(name);                                     \
+       static const struct file_operations cl_dbgfs_##name##_ops = { \
+               .write  = cl_dbgfs_##name##_write,                    \
+               .open   = simple_open,                                \
+               .llseek = generic_file_llseek,                        \
+       }
+
+#define DEBUGFS_READ_WRITE_FILE_OPS(name)                             \
+       DEBUGFS_READ_FUNC(name);                                      \
+       DEBUGFS_WRITE_FUNC(name);                                     \
+       static const struct file_operations cl_dbgfs_##name##_ops = { \
+               .write  = cl_dbgfs_##name##_write,                    \
+               .read   = cl_dbgfs_##name##_read,                     \
+               .open   = simple_open,                                \
+               .llseek = generic_file_llseek,                        \
+       }
+
+static ssize_t cl_dbgfs_set_debug_write(struct file *file,
+                                       const char __user *user_buf,
+                                       size_t count,
+                                       loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+       char buf[32];
+       char *usage_str = "Usage:\n"
+                         "echo [mod_filter] > set_debug\n";
+       unsigned long long mod_filter;
+       int ret;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf))
+               return -EFAULT;
+
+       ret = kstrtoull(buf, 0, &mod_filter);
+       if (ret) {
+               cl_dbg_verbose(cl_hw, "%s", usage_str);
+               return ret;
+       }
+
+       cl_dbg_verbose(cl_hw, "Send to FW: dbg_module=0x%x, dbg_severity=%u, mod_filter=0x%x\n",
+                      conf->ci_fw_dbg_module, conf->ci_fw_dbg_severity, (u32)mod_filter);
+
+       ret = cl_msg_tx_dbg_set_ce_mod_filter(cl_hw, conf->ci_fw_dbg_module);
+       if (ret)
+               return ret;
+
+       ret = cl_msg_tx_dbg_set_sev_filter(cl_hw, conf->ci_fw_dbg_severity);
+       if (ret)
+               return ret;
+
+       ret = cl_msg_tx_dbg_set_mod_filter(cl_hw, mod_filter);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+DEBUGFS_WRITE_FILE_OPS(set_debug);
+
+static ssize_t cl_dbgfs_tx_trace_debug_flag_write(struct file *file,
+                                                 const char __user *user_buf,
+                                                 size_t count,
+                                                 loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       int ret;
+       unsigned long long result;
+       u8 read_write_cmd;
+       char *sptr, *token;
+       char *usage_str = "Usage: echo  [w\\r] [value] > tx_trace_debug_flag\n";
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf)) {
+               cl_dbg_err(cl_hw, "illegal input\n %s", usage_str);
+               return -EFAULT;
+       }
+       sptr = buf;
+       token = strsep(&sptr, " ");
+       if (!token) {
+               cl_dbg_err(cl_hw, "illegal input\n %s", usage_str);
+               return -EINVAL;
+       }
+
+       if (!strncasecmp(token, "w", strlen("w"))) {
+               read_write_cmd = 1;
+               ret = kstrtoull(sptr, 0, &result);
+               if (ret) {
+                       cl_dbg_err(cl_hw, "illegal input\n %s", usage_str);
+                       return ret;
+               }
+               cl_dbg_trace(cl_hw, "(write value=0x%x)\n", (u32)result);
+       } else if (!strncasecmp(token, "r", strlen("r"))) {
+               read_write_cmd = 0;
+               result = 0;
+               cl_dbg_trace(cl_hw, "(read)\n");
+       } else {
+               cl_dbg_err(cl_hw, "illegal input\n %s", usage_str);
+               return -EFAULT;
+       }
+
+       ret = cl_msg_tx_dbg_tx_trace_debug_flag(cl_hw, (u32)result, (u8)read_write_cmd);
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(tx_trace_debug_flag);
+
+static ssize_t cl_dbgfs_test_mode_write(struct file *file,
+                                       const char __user *user_buf,
+                                       size_t count,
+                                       loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       int ret;
+       unsigned long long params[TEST_MODE_PARAM_MAX + 1] = {0};
+       u32 test_params[TEST_MODE_PARAM_MAX + 1] = {0};
+       char *usage_str = "Usage:\n"
+                         "debugfsh <chip> <tcv> test_mode <command> <params> <params> ..\n";
+       char *token, *sptr;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+       int i = 0;
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf)) {
+               cl_dbg_err(cl_hw, "string %s count %zu eobuf %d\n", buf, count, eobuf);
+               return -EFAULT;
+       }
+       sptr = buf;
+       token = strsep(&sptr, " ");
+
+       for (i = 0; i < TEST_MODE_PARAM_MAX + 1; i++) {
+               if (token) {
+                       ret = kstrtoull(token, 0, &params[i]);
+                       if (ret) {
+                               cl_dbg_trace(cl_hw, "Token %s illegal convert %s\n",
+                                            token, usage_str);
+                               return ret;
+                       }
+                       test_params[i] = (u32)params[i];
+                       cl_dbg_trace(cl_hw, "param[%d]=%llu\n", i, params[i]);
+               } else {
+                       break;
+               }
+               token = strsep(&sptr, " ");
+       }
+
+       ret = cl_msg_tx_dbg_test_mode(cl_hw, test_params);
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(test_mode);
+
+static ssize_t cl_dbgfs_fixed_rate_write(struct file *file,
+                                        const char __user *user_buf,
+                                        size_t count,
+                                        loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       int ret;
+       unsigned long long mcs = 0, bw = 0, gi = 0, format = 0, ltf_field = 0,
+               pre_type_or_stbc = 0, sta_idx = 0xff;
+       char *sptr, *token1, *token2, *token3, *token4, *token5, *token6, *token7;
+       char *usage_str = "Usage:\n"
+                         "echo [mcs] [bw] [gi] [format] [ltf_field] [pre_type_or_stbc(opt)] "
+                         "[sta_idx(opt)] > fixed_rate\n";
+       u8 op_mode = RATE_OP_MODE_FIXED;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+       union cl_rate_ctrl_info rate_ctrl;
+       union cl_rate_ctrl_info_he rate_ctrl_he;
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf)) {
+               cl_dbg_err(cl_hw, "illegal input\n %s", usage_str);
+               return -EFAULT;
+       }
+
+       sptr = buf;
+
+       /* Token1 - mcs */
+       token1 = strsep(&sptr, " ");
+       if (!token1) {
+               cl_dbg_err(cl_hw, "token1 illegal %s\n", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoull(token1, 0, &mcs);
+       if (ret) {
+               cl_dbg_err(cl_hw, "token1 %s illegal convert %s\n", token1, usage_str);
+               return ret;
+       }
+
+       /* Token2 - bw */
+       token2 = strsep(&sptr, " ");
+       if (!token2) {
+               cl_dbg_err(cl_hw, "token2 illegal %s\n", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoull(token2, 0, &bw);
+       if (ret) {
+               cl_dbg_err(cl_hw, "token2 %s illegal convert %s\n", token2, usage_str);
+               return ret;
+       }
+
+       /* Token3 - gi */
+       token3 = strsep(&sptr, " ");
+       if (!token3) {
+               cl_dbg_err(cl_hw, "token3 illegal %s\n", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoull(token3, 0, &gi);
+       if (ret) {
+               cl_dbg_err(cl_hw, "token3 %s illegal convert %s\n", token3, usage_str);
+               return ret;
+       }
+
+       /* Token4 - format */
+       token4 = strsep(&sptr, " ");
+       if (!token4) {
+               cl_dbg_err(cl_hw, "token4 illegal %s\n", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoull(token4, 0, &format);
+       if (ret) {
+               cl_dbg_err(cl_hw, "token4 %s illegal convert %s\n", token4, usage_str);
+               return ret;
+       }
+
+       /* Token5 - ltf_field */
+       token5 = strsep(&sptr, " ");
+       if (!token5) {
+               cl_dbg_err(cl_hw, "token5 illegal %s\n", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoull(token5, 0, &ltf_field);
+       if (ret) {
+               cl_dbg_err(cl_hw, "token5 %s illegal convert %s\n", token5, usage_str);
+               return ret;
+       }
+
+       /* Token6 - pre_type_or_stbc (optional) */
+       token6 = strsep(&sptr, " ");
+       if (token6) {
+               ret = kstrtoull(token6, 0, &pre_type_or_stbc);
+               if (ret) {
+                       cl_dbg_err(cl_hw, "token6 %s illegal convert %s\n", token6, usage_str);
+                       return ret;
+               }
+       }
+
+       /* Token7 - sta_idx (optional) */
+       token7 = strsep(&sptr, " ");
+       if (token7) {
+               ret = kstrtoull(token7, 0, &sta_idx);
+               if (ret) {
+                       cl_dbg_err(cl_hw, "token7 %s illegal convert %s\n", token7, usage_str);
+                       return ret;
+               }
+       }
+
+       /* Sanity tests */
+       if (sta_idx != STA_IDX_INVALID && !cl_sta_is_assoc(cl_hw, sta_idx)) {
+               cl_dbg_err(cl_hw, "Invalid station index [%llu]\n", sta_idx);
+               return -EINVAL;
+       }
+
+       if (format > FORMATMOD_HE_SU) {
+               cl_dbg_err(cl_hw, "Invalid format [%llu]\n", format);
+               return -EINVAL;
+       }
+
+       if (format < FORMATMOD_HE_SU)
+               ltf_field = 0;
+
+       /* Build rate_ctrl and rate_ctrl_he */
+       if (mcs == 0xff || bw == 0xff || gi == 0xff || format == 0xff) {
+               cl_hw->entry_fixed_rate = false;
+               cl_dbg_trace(cl_hw, "Fixed rate turned off\n");
+       } else {
+               rate_ctrl.word = 0;
+               rate_ctrl.field.mcs_index = mcs;
+               rate_ctrl.field.bw = bw;
+               rate_ctrl.field.gi = gi;
+               rate_ctrl.field.format_mod = format;
+
+               if (rate_ctrl.field.format_mod != FORMATMOD_NON_HT)
+                       rate_ctrl.field.pre_type_or_stbc = pre_type_or_stbc;
+
+               /* rate_ctrl_he is relevant only for HE format mode. */
+               if (format > FORMATMOD_VHT)
+                       rate_ctrl_he.field.spatial_conf = RATE_CNTRL_HE_SPATIAL_CONF_DEF;
+               else
+                       rate_ctrl_he.word = 0;
+
+               cl_dbg_trace(cl_hw, "mcs=%llu bw=%llu gi=%llu format=%llu --> rate_ctrl 0x%x\n",
+                            mcs, bw, gi, format, rate_ctrl.word);
+
+               /* Set entry_fixed_rate to true only if there is no specific station */
+               cl_hw->entry_fixed_rate = (sta_idx == 0xff);
+
+               if (!cl_rate_ctrl_set_dbgfs(cl_hw, sta_idx, rate_ctrl.word, rate_ctrl_he.word,
+                                           op_mode, bw, ltf_field))
+                       return -EFAULT;
+       }
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(fixed_rate);
+
+static ssize_t cl_dbgfs_mactrace_read(struct file *file,
+                                     char __user *user_buf,
+                                     size_t count,
+                                     loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (cl_hw->debugfs.trace_prst) {
+               read = simple_read_from_buffer(user_buf, count, ppos,
+                                              &cl_hw->dbginfo.buf->u.dump.la_mem[0],
+                                              ARRAY_SIZE(cl_hw->dbginfo.buf->u.dump.la_mem[0]));
+       } else {
+               pr_debug("No dump!\n");
+               read = 0;
+       }
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+
+       return read;
+}
+
+static ssize_t cl_dbgfs_mactrace_write(struct file *file,
+                                      const char __user *user_buf,
+                                      size_t count,
+                                      loff_t *ppos)
+{
+       /* Write "1" to diags/mactrace triggers dump, type 0 (recoverable). */
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       unsigned long v;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+
+       if (cl_hw->debugfs.unregistering)
+               return -EFAULT;
+
+       if (copy_from_user(&buf, user_buf, eobuf))
+               return -EFAULT;
+
+       buf[eobuf] = '\0';
+       if (kstrtoul(buf, 0, &v) || v != 1) {
+               pr_debug("Usage: echo 1 > mactrace : trigger firmware dump\n");
+               return -EFAULT;
+       }
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (cl_hw->debugfs.trace_prst) {
+               pr_debug("Dump already waiting\n");
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return -EFAULT;
+       }
+
+       scnprintf(buf, sizeof(buf), "Force trigger\n");
+       cl_msg_tx_dbg_trigger(cl_hw, buf);
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+
+       return count;
+}
+
+DEBUGFS_READ_WRITE_FILE_OPS(mactrace);
+
+static ssize_t cl_dbgfs_phytrace_read(struct file *file,
+                                     char __user *user_buf,
+                                     size_t count,
+                                     loff_t *ppos)
+{
+#if LA_CNT < 2
+       return -EFAULT; /* La_mem[1] does not exist */
+#else
+       struct cl_hw *cl_hw = file->private_data;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       read = simple_read_from_buffer(user_buf, count, ppos,
+                                      &cl_hw->dbginfo.buf->u.dump.la_mem[1],
+                                      ARRAY_SIZE(cl_hw->dbginfo.buf->u.dump.la_mem[1]));
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return read;
+#endif
+}
+
+DEBUGFS_READ_FILE_OPS(phytrace);
+
+static ssize_t cl_dbgfs_macdiags_read(struct file *file,
+                                     char __user *user_buf,
+                                     size_t count,
+                                     loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       read = simple_read_from_buffer(user_buf, count, ppos,
+                                      cl_hw->dbginfo.buf->u.dump.general_data.diags_mac,
+                                      DBG_DIAGS_MAC_MAX * 2); // FIXME: Why * 2?? BUG?
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+
+       return read;
+}
+
+DEBUGFS_READ_FILE_OPS(macdiags);
+
+static ssize_t cl_dbgfs_hwdiags_read(struct file *file,
+                                    char __user *user_buf,
+                                    size_t count,
+                                    loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[16];
+       int ret;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+                       "%08X\n", cl_hw->dbginfo.buf->u.dump.general_data.hw_diag);
+
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+}
+
+DEBUGFS_READ_FILE_OPS(hwdiags);
+
+static ssize_t cl_dbgfs_swdiags_read(struct file *file,
+                                    char __user *user_buf,
+                                    size_t count,
+                                    loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       read = simple_read_from_buffer(user_buf, count, ppos,
+                                      &cl_hw->dbginfo.buf->u.dump.general_data.sw_diag,
+                                      cl_hw->dbginfo.buf->u.dump.general_data.sw_diag_len);
+
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return read;
+}
+
+DEBUGFS_READ_FILE_OPS(swdiags);
+
+static ssize_t cl_dbgfs_lamacconf_read(struct file *file,
+                                      char __user *user_buf,
+                                      size_t count,
+                                      loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       read = simple_read_from_buffer(user_buf, count, ppos,
+                                      &cl_hw->dbginfo.buf->u.dump.general_data.la_conf[0],
+                                      sizeof(struct la_conf_tag));
+
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return read;
+}
+
+DEBUGFS_READ_FILE_OPS(lamacconf);
+
+static ssize_t cl_dbgfs_laphyconf_read(struct file *file,
+                                      char __user *user_buf,
+                                      size_t count,
+                                      loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       read = simple_read_from_buffer(user_buf, count, ppos,
+                                      &cl_hw->dbginfo.buf->u.dump.general_data.la_conf[1],
+                                      sizeof(struct la_conf_tag));
+
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return read;
+}
+
+DEBUGFS_READ_FILE_OPS(laphyconf);
+
+static ssize_t cl_dbgfs_chaninfo_read(struct file *file,
+                                     char __user *user_buf,
+                                     size_t count,
+                                     loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       struct phy_channel_info *chan_info = &cl_hw->dbginfo.buf->u.dump.general_data.chan_info;
+       char buf[4 * 32];
+       int ret;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count),
+                       "type:         %u\n"
+                       "prim20_freq:  %u MHz\n"
+                       "center1_freq: %u MHz\n"
+                       "center2_freq: %u MHz\n",
+                       (chan_info->info1 >> 8) & 0xFF,
+                       (chan_info->info1 >> 16) & 0xFFFF,
+                       (chan_info->info2 >> 0) & 0xFFFF,
+                       (chan_info->info2 >> 16) & 0xFFFF);
+
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+}
+
+DEBUGFS_READ_FILE_OPS(chaninfo);
+
+#define MAX_BUF_SIZE 512
+
+static ssize_t cl_dbgfs_error_read(struct file *file,
+                                  char __user *user_buf,
+                                  size_t count,
+                                  loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       struct dbg_print_ind *ind =
+               &cl_hw->dbginfo.buf->u.dump.fw_dump.common_info.error_info;
+       ssize_t read;
+
+       mutex_lock(&cl_hw->dbginfo.mutex);
+       if (!cl_hw->debugfs.trace_prst) {
+               mutex_unlock(&cl_hw->dbginfo.mutex);
+               return 0;
+       }
+
+       /* If an assert message, search for assert string */
+       if (ind->file_id && ind->line) {
+               u16 file_id = le16_to_cpu(ind->file_id);
+               u16 line = le16_to_cpu(ind->line);
+               const char *assert_string = cl_dbgfile_get_msg_txt(&cl_hw->dbg_data, file_id, line);
+
+               /* If string not found. use FW error string. */
+               if (!assert_string)
+                       assert_string = cl_hw->dbginfo.buf->u.dump.general_data.error;
+
+               read = simple_read_from_buffer(user_buf, count, ppos, assert_string,
+                                              strnlen(assert_string, MAX_BUF_SIZE));
+       } else {
+               char *error = cl_hw->dbginfo.buf->u.dump.general_data.error;
+
+               read = simple_read_from_buffer(user_buf, count, ppos, error,
+                                              strnlen(error, DBG_ERROR_TRACE_SIZE));
+       }
+
+       mutex_unlock(&cl_hw->dbginfo.mutex);
+       return read;
+}
+
+DEBUGFS_READ_FILE_OPS(error);
+
+static ssize_t cl_dbgfs_mpifmask_write(struct file *file,
+                                      const char __user *user_buf,
+                                      size_t count,
+                                      loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       int ret;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+       unsigned long long result;
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf))
+               return -EFAULT;
+
+       ret = kstrtoull(buf, 0, &result);
+       if (ret)
+               return ret;
+
+       cl_dbg_trace(cl_hw, "Set LA mpif mask = 0x%08x\n", (u32)result);
+
+       cl_msg_tx_dbg_set_la_mpif_mask(cl_hw, result);
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(mpifmask);
+
+static ssize_t cl_dbgfs_trigpoint_write(struct file *file,
+                                       const char __user *user_buf,
+                                       size_t count,
+                                       loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       int ret;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+       unsigned long long result;
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf))
+               return -EFAULT;
+
+       ret = kstrtoull(buf, 0, &result);
+       if (ret)
+               return ret;
+
+       cl_dbg_trace(cl_hw, "Set LA trigger point = 0x%x\n", (u32)result);
+
+       cl_msg_tx_dbg_set_la_trig_point(cl_hw, result);
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(trigpoint);
+
+enum {
+       CL_LA_MPIF_DEBUG_MODE_GEN, /* Generic MPIF sampling mask */
+       CL_LA_MPIF_DEBUG_MODE_TX,  /* Tx oriented MPIF sampling mask */
+       CL_LA_MPIF_DEBUG_MODE_RX,  /* Rx oriented MPIF sampling mask */
+
+       CL_LA_MPIF_DEBUG_MODE_NUM
+};
+
+const char *ce_la_mpif_debug_mode_str[CL_LA_MPIF_DEBUG_MODE_NUM] = {
+       [CL_LA_MPIF_DEBUG_MODE_GEN] = "LA_MPIF_DEBUG_MODE_GEN",
+       [CL_LA_MPIF_DEBUG_MODE_TX]  = "LA_MPIF_DEBUG_MODE_TX",
+       [CL_LA_MPIF_DEBUG_MODE_RX]  = "LA_MPIF_DEBUG_MODE_RX"
+};
+
+static ssize_t cl_dbgfs_mpif_debug_mode_write(struct file *file,
+                                             const char __user *user_buf,
+                                             size_t count,
+                                             loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[32];
+       int ret;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+       unsigned long long result;
+       u8 mode;
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf))
+               return -EFAULT;
+
+       ret = kstrtoull(buf, 0, &result);
+       if (ret)
+               return ret;
+
+       mode = (u8)result;
+       if (mode >= CL_LA_MPIF_DEBUG_MODE_NUM)
+               return -EINVAL;
+
+       cl_dbg_trace(cl_hw, "Set LA MPIF mode = %u (%s)\n", mode, ce_la_mpif_debug_mode_str[mode]);
+
+       cl_msg_tx_dbg_set_la_mpif_debug_mode(cl_hw, result);
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(mpif_debug_mode);
+
+char *la_trig_oper_str[LA_TRIG_OPER_MAX] = {
+       [LA_TRIG_OPER_EQ] = "eq",
+       [LA_TRIG_OPER_NOT_EQ] = "neq",
+       [LA_TRIG_OPER_GT] = "gt",
+       [LA_TRIG_OPER_GT_EQ] = "gte",
+       [LA_TRIG_OPER_LT] = "lt",
+       [LA_TRIG_OPER_LT_EQ] = "lte"
+};
+
+static ssize_t cl_dbgfs_trig_rule_write(struct file *file,
+                                       const char __user *user_buf,
+                                       size_t count,
+                                       loff_t *ppos)
+{
+       struct cl_hw *cl_hw = file->private_data;
+       char buf[100];
+       int ret = 0;
+       int eobuf = min_t(size_t, sizeof(buf) - 1, count);
+       char *sptr = NULL, *token = NULL;
+       unsigned long val;
+       u32 address = 0, value = 0, mask = 0, duration = 0;
+       u8 idx = 0, oper = 0;
+       bool enable = false;
+       char *usage_str = "Usage:\n"
+                         "echo  <rule-idx> <enable {1|0}> <address> <oper {eq|neq|gt|gte|lt|lte}>"
+                         " <value> <mask> [duration] > trig_rule\n";
+
+       buf[eobuf] = '\0';
+       if (copy_from_user(&buf, user_buf, eobuf))
+               return -EFAULT;
+
+       sptr = buf;
+
+       token = strsep(&sptr, " ");
+       if (!token) {
+               pr_err("Illegal token.\n%s", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoul(token, 0, &val);
+       if (ret) {
+               pr_err("<rule-idx> illegal value (%s)\n%s", token, usage_str);
+               return -EINVAL;
+       }
+
+       idx = (u8)val;
+
+       token = strsep(&sptr, " ");
+       if (!token) {
+               pr_err("Illegal token.\n%s", usage_str);
+               return -EINVAL;
+       }
+
+       ret = kstrtoul(token, 0, &val);
+       if (ret) {
+               pr_err("<enable> illegal value (%s)\n%s", token, usage_str);
+               return -EINVAL;
+       }
+
+       enable = (bool)val;
+
+       if (enable) {
+               /* Address token */
+               token = strsep(&sptr, " ");
+               if (!token) {
+                       pr_err("Illegal token.\n%s", usage_str);
+                       return -EINVAL;
+               }
+
+               ret = kstrtoul(token, 0, &val);
+               if (ret) {
+                       pr_err("<address> illegal value (%s)\n%s", token, usage_str);
+                       return -EINVAL;
+               }
+
+               address = val;
+
+               /* Comparison Operator token */
+               token = strsep(&sptr, " ");
+               if (!token) {
+                       pr_err("Illegal token.\n%s", usage_str);
+                       return -EINVAL;
+               }
+
+               for (oper = LA_TRIG_OPER_EQ; oper < LA_TRIG_OPER_MAX; ++oper)
+                       if (!strcmp(token, la_trig_oper_str[oper]))
+                               break;
+
+               if (oper == LA_TRIG_OPER_MAX) {
+                       pr_err("<oper> illegal value (%s)\n%s", token, usage_str);
+                       return -EINVAL;
+               }
+
+               /* Value token */
+               token = strsep(&sptr, " ");
+               if (!token) {
+                       pr_err("Illegal token.\n%s", usage_str);
+                       return -EINVAL;
+               }
+
+               ret = kstrtoul(token, 0, &val);
+               if (ret) {
+                       pr_err("<value> illegal value (%s)\n%s", token, usage_str);
+                       return -EINVAL;
+               }
+
+               value = val;
+
+               /* Mask token */
+               token = strsep(&sptr, " ");
+               if (!token) {
+                       pr_err("Illegal token.\n%s", usage_str);
+                       return -EINVAL;
+               }
+
+               ret = kstrtoul(token, 0, &val);
+               if (ret) {
+                       pr_err("<mask> illegal value (%s)\n%s", token, usage_str);
+                       return -EINVAL;
+               }
+
+               mask = val;
+
+               /* Duration token (optional) */
+               token = strsep(&sptr, " ");
+               if (token) {
+                       ret = kstrtoul(token, 0, &val);
+                       if (ret) {
+                               pr_err("<duration> illegal value (%s)\n%s", token, usage_str);
+                               return -EINVAL;
+                       }
+
+                       duration = val;
+               }
+       }
+
+       if (enable)
+               pr_debug("Set trigger rule: idx=%u, addr=0x%08x, oper=%s, value=0x%08x, "
+                        "mask=0x%08x, duration=%u\n",
+                        idx, address, la_trig_oper_str[oper], value, mask, duration);
+       else
+               pr_debug("Disable trigger rule: idx=%u\n", idx);
+
+       if (cl_msg_tx_dbg_set_la_trig_rule(cl_hw, idx, enable, address, oper,
+                                          value, mask, duration))
+               return -EFAULT;
+
+       return count;
+}
+
+DEBUGFS_WRITE_FILE_OPS(trig_rule);
+
+int cl_dbgfs_register(struct cl_hw *cl_hw, const char *name)
+{
+       struct dentry *phyd = cl_hw->hw->wiphy->debugfsdir;
+       struct dentry *dir_drv, *dir_diags, *dir_cl;
+
+       cl_dbg_trace(cl_hw, "/sys/kernel/debug/%s/%s/%s.\n",
+                    phyd->d_parent->d_name.name, phyd->d_name.name, name);
+
+       dir_drv = debugfs_create_dir(name, phyd);
+       if (!dir_drv)
+               return -ENOMEM;
+
+       dir_diags = debugfs_create_dir("diags", dir_drv);
+       if (!dir_diags)
+               goto err;
+
+       dir_cl = debugfs_create_dir("cl8k", dir_drv);
+       if (!dir_cl) {
+               cl_dbg_err(cl_hw, "%s\n", name);
+               goto err;
+       }
+
+       cl_coredump_init(cl_hw, dir_drv);
+
+       DEBUGFS_ADD_FILE(mactrace,            dir_diags, 0400);
+       DEBUGFS_ADD_FILE(phytrace,            dir_diags, 0400);
+       DEBUGFS_ADD_FILE(macdiags,            dir_diags, 0400);
+       DEBUGFS_ADD_FILE(hwdiags,             dir_diags, 0400);
+       DEBUGFS_ADD_FILE(swdiags,             dir_diags, 0400);
+       DEBUGFS_ADD_FILE(error,               dir_diags, 0400);
+       DEBUGFS_ADD_FILE(lamacconf,           dir_diags, 0400);
+       DEBUGFS_ADD_FILE(laphyconf,           dir_diags, 0400);
+       DEBUGFS_ADD_FILE(chaninfo,            dir_diags, 0400);
+       DEBUGFS_ADD_FILE(trigpoint,           dir_diags, 0200);
+       DEBUGFS_ADD_FILE(mpifmask,            dir_diags, 0200);
+       DEBUGFS_ADD_FILE(mpif_debug_mode,     dir_diags, 0200);
+       DEBUGFS_ADD_FILE(set_debug,           dir_cl,    0200);
+       DEBUGFS_ADD_FILE(test_mode,           dir_cl,    0200);
+       DEBUGFS_ADD_FILE(fixed_rate,          dir_cl,    0200);
+       DEBUGFS_ADD_FILE(tx_trace_debug_flag, dir_cl,    0200);
+       DEBUGFS_ADD_FILE(trig_rule,           dir_cl,    0200);
+       return 0;
+
+err:
+       cl_dbgfs_unregister(cl_hw);
+       return -ENOMEM;
+}
+
+void cl_dbgfs_unregister(struct cl_hw *cl_hw)
+{
+       cl_coredump_close(cl_hw);
+}