From f1ea1257a6186dd096cb5c865395879b7bc90bfb Mon Sep 17 00:00:00 2001 From: Yue Ma Date: Fri, 21 Feb 2020 16:05:36 -0800 Subject: [PATCH] cnss2: Add debugfs support to send WFC call status QMI message Expose a debugfs node so that user can manually input WFC call status QMI message for debug purpose. Change-Id: I02d2b0064a0ba5622c2ee3523e577ec2f3fec36b Signed-off-by: Yue Ma --- drivers/net/wireless/cnss2/debug.c | 111 ++++++++++++++++++++++++++++- drivers/net/wireless/cnss2/qmi.c | 4 +- drivers/net/wireless/cnss2/qmi.h | 18 ++++- 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/cnss2/debug.c b/drivers/net/wireless/cnss2/debug.c index 4d577aa1d285..21ceda6e53a4 100644 --- a/drivers/net/wireless/cnss2/debug.c +++ b/drivers/net/wireless/cnss2/debug.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #include #include @@ -10,10 +10,28 @@ #include "pci.h" #define MMIO_REG_ACCESS_MEM_TYPE 0xFF +#define HEX_DUMP_ROW_SIZE 16 void *cnss_ipc_log_context; void *cnss_ipc_log_long_context; +static void cnss_print_hex_dump(const void *buf, int len) +{ + const u8 *ptr = buf; + int i, linelen, remaining = len, rowsize = HEX_DUMP_ROW_SIZE; + unsigned char linebuf[HEX_DUMP_ROW_SIZE * 3 + 1]; + + for (i = 0; i < len; i += rowsize) { + linelen = min(remaining, rowsize); + remaining -= rowsize; + + hex_dump_to_buffer(ptr + i, linelen, rowsize, 1, + linebuf, sizeof(linebuf), false); + + cnss_pr_dbg("%.8x: %s\n", i, linebuf); + } +} + static int cnss_pin_connect_show(struct seq_file *s, void *data) { struct cnss_plat_data *cnss_priv = s->private; @@ -750,6 +768,95 @@ static const struct file_operations cnss_dynamic_feature_fops = { .llseek = seq_lseek, }; +static int cnss_wfc_call_status_debug_show(struct seq_file *s, void *data) +{ + seq_puts(s, "\nUsage: echo > /cnss/wfc_call_status\n"); + seq_puts(s, "e.g. Send 4 bytes of hex data for WFC call status using QMI message:\n"); + seq_puts(s, "echo '0x4 0xA 0xB 0xC 0xD' > /d/cnss/wfc_call_status\n"); + + return 0; +} + +static ssize_t cnss_wfc_call_status_debug_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct cnss_plat_data *plat_priv = + ((struct seq_file *)fp->private_data)->private; + char buf[(QMI_WLFW_MAX_WFC_CALL_STATUS_DATA_SIZE_V01 + 1) * 5]; + char *sptr, *token; + unsigned int len = 0; + const char *delim = " "; + u32 data_len; + u8 data[QMI_WLFW_MAX_WFC_CALL_STATUS_DATA_SIZE_V01] = {0}, data_byte; + int ret = 0, i; + + if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { + cnss_pr_err("Firmware is not ready yet\n"); + return -EINVAL; + } + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token || !sptr) + return -EINVAL; + + if (kstrtou32(token, 0, &data_len)) + return -EINVAL; + + cnss_pr_dbg("Parsing 0x%x bytes data for WFC call status\n", data_len); + + if (data_len > QMI_WLFW_MAX_WFC_CALL_STATUS_DATA_SIZE_V01 || + data_len == 0) { + cnss_pr_err("Invalid data length 0x%x\n", data_len); + return -EINVAL; + } + + for (i = 0; i < data_len; i++) { + token = strsep(&sptr, delim); + if (!token || (!sptr && i < data_len - 1)) { + cnss_pr_err("Input data is less than length\n"); + return -EINVAL; + } + + if (kstrtou8(token, 0, &data_byte)) { + cnss_pr_err("Data format is incorrect\n"); + return -EINVAL; + } + + data[i] = data_byte; + } + + cnss_print_hex_dump(data, data_len); + + ret = cnss_wlfw_wfc_call_status_send_sync(plat_priv, data_len, data); + if (ret) + return ret; + + return count; +} + +static int cnss_wfc_call_status_debug_open(struct inode *inode, + struct file *file) +{ + return single_open(file, cnss_wfc_call_status_debug_show, + inode->i_private); +} + +static const struct file_operations cnss_wfc_call_status_debug_fops = { + .read = seq_read, + .write = cnss_wfc_call_status_debug_write, + .open = cnss_wfc_call_status_debug_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + #ifdef CONFIG_CNSS2_DEBUG static int cnss_create_debug_only_node(struct cnss_plat_data *plat_priv) { @@ -767,6 +874,8 @@ static int cnss_create_debug_only_node(struct cnss_plat_data *plat_priv) &cnss_control_params_debug_fops); debugfs_create_file("dynamic_feature", 0600, root_dentry, plat_priv, &cnss_dynamic_feature_fops); + debugfs_create_file("wfc_call_status", 0600, root_dentry, plat_priv, + &cnss_wfc_call_status_debug_fops); return 0; } diff --git a/drivers/net/wireless/cnss2/qmi.c b/drivers/net/wireless/cnss2/qmi.c index 22f059c3c166..3904b2c59213 100644 --- a/drivers/net/wireless/cnss2/qmi.c +++ b/drivers/net/wireless/cnss2/qmi.c @@ -1469,8 +1469,8 @@ out: return ret; } -static int cnss_wlfw_wfc_call_status_send_sync(struct cnss_plat_data *plat_priv, - u32 data_len, const void *data) +int cnss_wlfw_wfc_call_status_send_sync(struct cnss_plat_data *plat_priv, + u32 data_len, const void *data) { struct wlfw_wfc_call_status_req_msg_v01 *req; struct wlfw_wfc_call_status_resp_msg_v01 *resp; diff --git a/drivers/net/wireless/cnss2/qmi.h b/drivers/net/wireless/cnss2/qmi.h index fc2a2c677c1e..90a9dbddcbed 100644 --- a/drivers/net/wireless/cnss2/qmi.h +++ b/drivers/net/wireless/cnss2/qmi.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. */ #ifndef _CNSS_QMI_H #define _CNSS_QMI_H @@ -61,6 +61,8 @@ int cnss_wlfw_antenna_grant_send_sync(struct cnss_plat_data *plat_priv); int cnss_wlfw_dynamic_feature_mask_send_sync(struct cnss_plat_data *plat_priv); int cnss_wlfw_get_info_send_sync(struct cnss_plat_data *plat_priv, int type, void *cmd, int cmd_len); +int cnss_wlfw_wfc_call_status_send_sync(struct cnss_plat_data *plat_priv, + u32 data_len, const void *data); int cnss_register_coex_service(struct cnss_plat_data *plat_priv); void cnss_unregister_coex_service(struct cnss_plat_data *plat_priv); int coex_antenna_switch_to_wlan_send_sync_msg(struct cnss_plat_data *plat_priv); @@ -175,6 +177,20 @@ int cnss_wlfw_dynamic_feature_mask_send_sync(struct cnss_plat_data *plat_priv) return 0; } +static inline +int cnss_wlfw_get_info_send_sync(struct cnss_plat_data *plat_priv, int type, + void *cmd, int cmd_len) +{ + return 0; +} + +static inline +int cnss_wlfw_wfc_call_status_send_sync(struct cnss_plat_data *plat_priv, + u32 data_len, const void *data); +{ + return 0; +} + static inline int cnss_register_coex_service(struct cnss_plat_data *plat_priv) {