/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. */ #include #include #include #include "main.h" #include "debug.h" #include "pci.h" #define CNSS_IPC_LOG_PAGES 32 void *cnss_ipc_log_context; static int cnss_pin_connect_show(struct seq_file *s, void *data) { struct cnss_plat_data *cnss_priv = s->private; seq_puts(s, "Pin connect results\n"); seq_printf(s, "FW power pin result: %04x\n", cnss_priv->pin_result.fw_pwr_pin_result); seq_printf(s, "FW PHY IO pin result: %04x\n", cnss_priv->pin_result.fw_phy_io_pin_result); seq_printf(s, "FW RF pin result: %04x\n", cnss_priv->pin_result.fw_rf_pin_result); seq_printf(s, "Host pin result: %04x\n", cnss_priv->pin_result.host_pin_result); seq_puts(s, "\n"); return 0; } static int cnss_pin_connect_open(struct inode *inode, struct file *file) { return single_open(file, cnss_pin_connect_show, inode->i_private); } static const struct file_operations cnss_pin_connect_fops = { .read = seq_read, .release = single_release, .open = cnss_pin_connect_open, .owner = THIS_MODULE, .llseek = seq_lseek, }; static int cnss_stats_show_state(struct seq_file *s, struct cnss_plat_data *plat_priv) { enum cnss_driver_state i; int skip = 0; unsigned long state; seq_printf(s, "\nState: 0x%lx(", plat_priv->driver_state); for (i = 0, state = plat_priv->driver_state; state != 0; state >>= 1, i++) { if (!(state & 0x1)) continue; if (skip++) seq_puts(s, " | "); switch (i) { case CNSS_QMI_WLFW_CONNECTED: seq_puts(s, "QMI_WLFW_CONNECTED"); continue; case CNSS_FW_MEM_READY: seq_puts(s, "FW_MEM_READY"); continue; case CNSS_FW_READY: seq_puts(s, "FW_READY"); continue; case CNSS_COLD_BOOT_CAL: seq_puts(s, "COLD_BOOT_CAL"); continue; case CNSS_DRIVER_LOADING: seq_puts(s, "DRIVER_LOADING"); continue; case CNSS_DRIVER_UNLOADING: seq_puts(s, "DRIVER_UNLOADING"); continue; case CNSS_DRIVER_PROBED: seq_puts(s, "DRIVER_PROBED"); continue; case CNSS_DRIVER_RECOVERY: seq_puts(s, "DRIVER_RECOVERY"); continue; case CNSS_FW_BOOT_RECOVERY: seq_puts(s, "FW_BOOT_RECOVERY"); continue; case CNSS_DEV_ERR_NOTIFY: seq_puts(s, "DEV_ERR"); continue; case CNSS_DRIVER_DEBUG: seq_puts(s, "DRIVER_DEBUG"); continue; case CNSS_DEV_REMOVED: seq_puts(s, "DEV_REMOVED"); continue; } seq_printf(s, "UNKNOWN-%d", i); } seq_puts(s, ")\n"); return 0; } static int cnss_stats_show_capability(struct seq_file *s, struct cnss_plat_data *plat_priv) { if (test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { seq_puts(s, "\n<---------- FW Capability ----------->\n"); seq_printf(s, "Chip ID: 0x%x\n", plat_priv->chip_info.chip_id); seq_printf(s, "Chip family: 0x%x\n", plat_priv->chip_info.chip_family); seq_printf(s, "Board ID: 0x%x\n", plat_priv->board_info.board_id); seq_printf(s, "SOC Info: 0x%x\n", plat_priv->soc_info.soc_id); seq_printf(s, "Firmware Version: 0x%x\n", plat_priv->fw_version_info.fw_version); seq_printf(s, "Firmware Build Timestamp: %s\n", plat_priv->fw_version_info.fw_build_timestamp); } return 0; } static int cnss_stats_show(struct seq_file *s, void *data) { struct cnss_plat_data *plat_priv = s->private; cnss_stats_show_state(s, plat_priv); cnss_stats_show_capability(s, plat_priv); return 0; } static int cnss_stats_open(struct inode *inode, struct file *file) { return single_open(file, cnss_stats_show, inode->i_private); } static const struct file_operations cnss_stats_fops = { .read = seq_read, .release = single_release, .open = cnss_stats_open, .owner = THIS_MODULE, .llseek = seq_lseek, }; static ssize_t cnss_dev_boot_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[64]; char *cmd; unsigned int len = 0; int ret = 0; len = min(count, sizeof(buf) - 1); if (copy_from_user(buf, user_buf, len)) return -EFAULT; buf[len] = '\0'; cmd = buf; if (sysfs_streq(cmd, "on")) { ret = cnss_power_on_device(plat_priv); } else if (sysfs_streq(cmd, "off")) { cnss_power_off_device(plat_priv); } else if (sysfs_streq(cmd, "enumerate")) { ret = cnss_pci_init(plat_priv); } else if (sysfs_streq(cmd, "download")) { set_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state); ret = cnss_pci_start_mhi(plat_priv->bus_priv); } else if (sysfs_streq(cmd, "linkup")) { ret = cnss_resume_pci_link(plat_priv->bus_priv); } else if (sysfs_streq(cmd, "linkdown")) { ret = cnss_suspend_pci_link(plat_priv->bus_priv); } else if (sysfs_streq(cmd, "powerup")) { set_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state); ret = cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_POWER_UP, CNSS_EVENT_SYNC, NULL); } else if (sysfs_streq(cmd, "shutdown")) { ret = cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_POWER_DOWN, CNSS_EVENT_SYNC, NULL); clear_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state); } else { cnss_pr_err("Device boot debugfs command is invalid\n"); ret = -EINVAL; } if (ret) return ret; return count; } static int cnss_dev_boot_debug_show(struct seq_file *s, void *data) { seq_puts(s, "\nUsage: echo > /cnss/dev_boot\n"); seq_puts(s, " can be one of below:\n"); seq_puts(s, "on: turn on device power, assert WLAN_EN\n"); seq_puts(s, "off: de-assert WLAN_EN, turn off device power\n"); seq_puts(s, "enumerate: de-assert PERST, enumerate PCIe\n"); seq_puts(s, "download: download FW and do QMI handshake with FW\n"); seq_puts(s, "linkup: bring up PCIe link\n"); seq_puts(s, "linkdown: bring down PCIe link\n"); seq_puts(s, "powerup: full power on sequence to boot device, download FW and do QMI handshake with FW\n"); seq_puts(s, "shutdown: full power off sequence to shutdown device\n"); return 0; } static int cnss_dev_boot_debug_open(struct inode *inode, struct file *file) { return single_open(file, cnss_dev_boot_debug_show, inode->i_private); } static const struct file_operations cnss_dev_boot_debug_fops = { .read = seq_read, .write = cnss_dev_boot_debug_write, .release = single_release, .open = cnss_dev_boot_debug_open, .owner = THIS_MODULE, .llseek = seq_lseek, }; static int cnss_reg_read_debug_show(struct seq_file *s, void *data) { struct cnss_plat_data *plat_priv = s->private; mutex_lock(&plat_priv->dev_lock); if (!plat_priv->diag_reg_read_buf) { seq_puts(s, "\nUsage: echo > /cnss/reg_read\n"); mutex_unlock(&plat_priv->dev_lock); return 0; } seq_printf(s, "\nRegister read, address: 0x%x memory type: 0x%x length: 0x%x\n\n", plat_priv->diag_reg_read_addr, plat_priv->diag_reg_read_mem_type, plat_priv->diag_reg_read_len); seq_hex_dump(s, "", DUMP_PREFIX_OFFSET, 32, 4, plat_priv->diag_reg_read_buf, plat_priv->diag_reg_read_len, false); plat_priv->diag_reg_read_len = 0; kfree(plat_priv->diag_reg_read_buf); plat_priv->diag_reg_read_buf = NULL; mutex_unlock(&plat_priv->dev_lock); return 0; } static ssize_t cnss_reg_read_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[64]; char *sptr, *token; unsigned int len = 0; u32 reg_offset, mem_type; u32 data_len = 0; u8 *reg_buf = NULL; const char *delim = " "; int ret = 0; 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) return -EINVAL; if (!sptr) return -EINVAL; if (kstrtou32(token, 0, &mem_type)) return -EINVAL; token = strsep(&sptr, delim); if (!token) return -EINVAL; if (!sptr) return -EINVAL; if (kstrtou32(token, 0, ®_offset)) return -EINVAL; token = strsep(&sptr, delim); if (!token) return -EINVAL; if (kstrtou32(token, 0, &data_len)) return -EINVAL; if (data_len == 0 || data_len > QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01) return -EINVAL; mutex_lock(&plat_priv->dev_lock); kfree(plat_priv->diag_reg_read_buf); plat_priv->diag_reg_read_buf = NULL; reg_buf = kzalloc(data_len, GFP_KERNEL); if (!reg_buf) { mutex_unlock(&plat_priv->dev_lock); return -ENOMEM; } ret = cnss_wlfw_athdiag_read_send_sync(plat_priv, reg_offset, mem_type, data_len, reg_buf); if (ret) { kfree(reg_buf); mutex_unlock(&plat_priv->dev_lock); return ret; } plat_priv->diag_reg_read_addr = reg_offset; plat_priv->diag_reg_read_mem_type = mem_type; plat_priv->diag_reg_read_len = data_len; plat_priv->diag_reg_read_buf = reg_buf; mutex_unlock(&plat_priv->dev_lock); return count; } static int cnss_reg_read_debug_open(struct inode *inode, struct file *file) { return single_open(file, cnss_reg_read_debug_show, inode->i_private); } static const struct file_operations cnss_reg_read_debug_fops = { .read = seq_read, .write = cnss_reg_read_debug_write, .open = cnss_reg_read_debug_open, .owner = THIS_MODULE, .llseek = seq_lseek, }; static int cnss_reg_write_debug_show(struct seq_file *s, void *data) { seq_puts(s, "\nUsage: echo > /cnss/reg_write\n"); return 0; } static ssize_t cnss_reg_write_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[64]; char *sptr, *token; unsigned int len = 0; u32 reg_offset, mem_type, reg_val; const char *delim = " "; int ret = 0; 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) return -EINVAL; if (!sptr) return -EINVAL; if (kstrtou32(token, 0, &mem_type)) return -EINVAL; token = strsep(&sptr, delim); if (!token) return -EINVAL; if (!sptr) return -EINVAL; if (kstrtou32(token, 0, ®_offset)) return -EINVAL; token = strsep(&sptr, delim); if (!token) return -EINVAL; if (kstrtou32(token, 0, ®_val)) return -EINVAL; ret = cnss_wlfw_athdiag_write_send_sync(plat_priv, reg_offset, mem_type, sizeof(u32), (u8 *)®_val); if (ret) return ret; return count; } static int cnss_reg_write_debug_open(struct inode *inode, struct file *file) { return single_open(file, cnss_reg_write_debug_show, inode->i_private); } static const struct file_operations cnss_reg_write_debug_fops = { .read = seq_read, .write = cnss_reg_write_debug_write, .open = cnss_reg_write_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) { struct dentry *root_dentry = plat_priv->root_dentry; debugfs_create_file("dev_boot", 0600, root_dentry, plat_priv, &cnss_dev_boot_debug_fops); debugfs_create_file("reg_read", 0600, root_dentry, plat_priv, &cnss_reg_read_debug_fops); debugfs_create_file("reg_write", 0600, root_dentry, plat_priv, &cnss_reg_write_debug_fops); return 0; } #else static int cnss_create_debug_only_node(struct cnss_plat_data *plat_priv) { return 0; } #endif int cnss_debugfs_create(struct cnss_plat_data *plat_priv) { int ret = 0; struct dentry *root_dentry; root_dentry = debugfs_create_dir("cnss", 0); if (IS_ERR(root_dentry)) { ret = PTR_ERR(root_dentry); cnss_pr_err("Unable to create debugfs %d\n", ret); goto out; } plat_priv->root_dentry = root_dentry; debugfs_create_file("pin_connect_result", 0644, root_dentry, plat_priv, &cnss_pin_connect_fops); debugfs_create_file("stats", 0644, root_dentry, plat_priv, &cnss_stats_fops); cnss_create_debug_only_node(plat_priv); out: return ret; } void cnss_debugfs_destroy(struct cnss_plat_data *plat_priv) { debugfs_remove_recursive(plat_priv->root_dentry); } int cnss_debug_init(void) { cnss_ipc_log_context = ipc_log_context_create(CNSS_IPC_LOG_PAGES, "cnss", 0); if (!cnss_ipc_log_context) { cnss_pr_err("Unable to create IPC log context!\n"); return -EINVAL; } return 0; } void cnss_debug_deinit(void) { if (cnss_ipc_log_context) { ipc_log_context_destroy(cnss_ipc_log_context); cnss_ipc_log_context = NULL; } }