new file mode 100644
@@ -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, ¶ms[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, <f_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);
+}