android_kernel_oneplus_msm8998/drivers/net/wireless/ath/ath6kl/debug.c
Kalle Valo bc07ddb29a ath6kl: read fwlog from firmware ring buffer
Firmare sends the logs only when it's internal ring buffer is full. But
if firmware crashes we need to retrieve the latest logs through diagnose
window. This is now done everytime the debugfs file is read.

Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
2011-09-02 10:32:05 +03:00

580 lines
17 KiB
C

/*
* Copyright (c) 2004-2011 Atheros Communications Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "core.h"
#include <linux/circ_buf.h>
#include <linux/fs.h>
#include "debug.h"
#include "target.h"
struct ath6kl_fwlog_slot {
__le32 timestamp;
__le32 length;
/* max ATH6KL_FWLOG_PAYLOAD_SIZE bytes */
u8 payload[0];
};
#define ATH6KL_FWLOG_SIZE 32768
#define ATH6KL_FWLOG_SLOT_SIZE (sizeof(struct ath6kl_fwlog_slot) + \
ATH6KL_FWLOG_PAYLOAD_SIZE)
#define ATH6KL_FWLOG_VALID_MASK 0x1ffff
int ath6kl_printk(const char *level, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
int rtn;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
rtn = printk("%sath6kl: %pV", level, &vaf);
va_end(args);
return rtn;
}
#ifdef CONFIG_ATH6KL_DEBUG
void ath6kl_dump_registers(struct ath6kl_device *dev,
struct ath6kl_irq_proc_registers *irq_proc_reg,
struct ath6kl_irq_enable_reg *irq_enable_reg)
{
ath6kl_dbg(ATH6KL_DBG_ANY, ("<------- Register Table -------->\n"));
if (irq_proc_reg != NULL) {
ath6kl_dbg(ATH6KL_DBG_ANY,
"Host Int status: 0x%x\n",
irq_proc_reg->host_int_status);
ath6kl_dbg(ATH6KL_DBG_ANY,
"CPU Int status: 0x%x\n",
irq_proc_reg->cpu_int_status);
ath6kl_dbg(ATH6KL_DBG_ANY,
"Error Int status: 0x%x\n",
irq_proc_reg->error_int_status);
ath6kl_dbg(ATH6KL_DBG_ANY,
"Counter Int status: 0x%x\n",
irq_proc_reg->counter_int_status);
ath6kl_dbg(ATH6KL_DBG_ANY,
"Mbox Frame: 0x%x\n",
irq_proc_reg->mbox_frame);
ath6kl_dbg(ATH6KL_DBG_ANY,
"Rx Lookahead Valid: 0x%x\n",
irq_proc_reg->rx_lkahd_valid);
ath6kl_dbg(ATH6KL_DBG_ANY,
"Rx Lookahead 0: 0x%x\n",
irq_proc_reg->rx_lkahd[0]);
ath6kl_dbg(ATH6KL_DBG_ANY,
"Rx Lookahead 1: 0x%x\n",
irq_proc_reg->rx_lkahd[1]);
if (dev->ar->mbox_info.gmbox_addr != 0) {
/*
* If the target supports GMBOX hardware, dump some
* additional state.
*/
ath6kl_dbg(ATH6KL_DBG_ANY,
"GMBOX Host Int status 2: 0x%x\n",
irq_proc_reg->host_int_status2);
ath6kl_dbg(ATH6KL_DBG_ANY,
"GMBOX RX Avail: 0x%x\n",
irq_proc_reg->gmbox_rx_avail);
ath6kl_dbg(ATH6KL_DBG_ANY,
"GMBOX lookahead alias 0: 0x%x\n",
irq_proc_reg->rx_gmbox_lkahd_alias[0]);
ath6kl_dbg(ATH6KL_DBG_ANY,
"GMBOX lookahead alias 1: 0x%x\n",
irq_proc_reg->rx_gmbox_lkahd_alias[1]);
}
}
if (irq_enable_reg != NULL) {
ath6kl_dbg(ATH6KL_DBG_ANY,
"Int status Enable: 0x%x\n",
irq_enable_reg->int_status_en);
ath6kl_dbg(ATH6KL_DBG_ANY, "Counter Int status Enable: 0x%x\n",
irq_enable_reg->cntr_int_status_en);
}
ath6kl_dbg(ATH6KL_DBG_ANY, "<------------------------------->\n");
}
static void dump_cred_dist(struct htc_endpoint_credit_dist *ep_dist)
{
ath6kl_dbg(ATH6KL_DBG_ANY,
"--- endpoint: %d svc_id: 0x%X ---\n",
ep_dist->endpoint, ep_dist->svc_id);
ath6kl_dbg(ATH6KL_DBG_ANY, " dist_flags : 0x%X\n",
ep_dist->dist_flags);
ath6kl_dbg(ATH6KL_DBG_ANY, " cred_norm : %d\n",
ep_dist->cred_norm);
ath6kl_dbg(ATH6KL_DBG_ANY, " cred_min : %d\n",
ep_dist->cred_min);
ath6kl_dbg(ATH6KL_DBG_ANY, " credits : %d\n",
ep_dist->credits);
ath6kl_dbg(ATH6KL_DBG_ANY, " cred_assngd : %d\n",
ep_dist->cred_assngd);
ath6kl_dbg(ATH6KL_DBG_ANY, " seek_cred : %d\n",
ep_dist->seek_cred);
ath6kl_dbg(ATH6KL_DBG_ANY, " cred_sz : %d\n",
ep_dist->cred_sz);
ath6kl_dbg(ATH6KL_DBG_ANY, " cred_per_msg : %d\n",
ep_dist->cred_per_msg);
ath6kl_dbg(ATH6KL_DBG_ANY, " cred_to_dist : %d\n",
ep_dist->cred_to_dist);
ath6kl_dbg(ATH6KL_DBG_ANY, " txq_depth : %d\n",
get_queue_depth(&((struct htc_endpoint *)
ep_dist->htc_rsvd)->txq));
ath6kl_dbg(ATH6KL_DBG_ANY,
"----------------------------------\n");
}
void dump_cred_dist_stats(struct htc_target *target)
{
struct htc_endpoint_credit_dist *ep_list;
if (!AR_DBG_LVL_CHECK(ATH6KL_DBG_TRC))
return;
list_for_each_entry(ep_list, &target->cred_dist_list, list)
dump_cred_dist(ep_list);
ath6kl_dbg(ATH6KL_DBG_HTC_SEND, "ctxt:%p dist:%p\n",
target->cred_dist_cntxt, NULL);
ath6kl_dbg(ATH6KL_DBG_TRC, "credit distribution, total : %d, free : %d\n",
target->cred_dist_cntxt->total_avail_credits,
target->cred_dist_cntxt->cur_free_credits);
}
static int ath6kl_debugfs_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static void ath6kl_debug_fwlog_add(struct ath6kl *ar, const void *buf,
size_t buf_len)
{
struct circ_buf *fwlog = &ar->debug.fwlog_buf;
size_t space;
int i;
/* entries must all be equal size */
if (WARN_ON(buf_len != ATH6KL_FWLOG_SLOT_SIZE))
return;
space = CIRC_SPACE(fwlog->head, fwlog->tail, ATH6KL_FWLOG_SIZE);
if (space < buf_len)
/* discard oldest slot */
fwlog->tail = (fwlog->tail + ATH6KL_FWLOG_SLOT_SIZE) &
(ATH6KL_FWLOG_SIZE - 1);
for (i = 0; i < buf_len; i += space) {
space = CIRC_SPACE_TO_END(fwlog->head, fwlog->tail,
ATH6KL_FWLOG_SIZE);
if ((size_t) space > buf_len - i)
space = buf_len - i;
memcpy(&fwlog->buf[fwlog->head], buf, space);
fwlog->head = (fwlog->head + space) & (ATH6KL_FWLOG_SIZE - 1);
}
}
void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len)
{
struct ath6kl_fwlog_slot *slot = ar->debug.fwlog_tmp;
size_t slot_len;
if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE))
return;
spin_lock_bh(&ar->debug.fwlog_lock);
slot->timestamp = cpu_to_le32(jiffies);
slot->length = cpu_to_le32(len);
memcpy(slot->payload, buf, len);
slot_len = sizeof(*slot) + len;
if (slot_len < ATH6KL_FWLOG_SLOT_SIZE)
memset(slot->payload + len, 0,
ATH6KL_FWLOG_SLOT_SIZE - slot_len);
ath6kl_debug_fwlog_add(ar, slot, ATH6KL_FWLOG_SLOT_SIZE);
spin_unlock_bh(&ar->debug.fwlog_lock);
}
static bool ath6kl_debug_fwlog_empty(struct ath6kl *ar)
{
return CIRC_CNT(ar->debug.fwlog_buf.head,
ar->debug.fwlog_buf.tail,
ATH6KL_FWLOG_SLOT_SIZE) == 0;
}
static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath6kl *ar = file->private_data;
struct circ_buf *fwlog = &ar->debug.fwlog_buf;
size_t len = 0, buf_len = count;
ssize_t ret_cnt;
char *buf;
int ccnt;
buf = vmalloc(buf_len);
if (!buf)
return -ENOMEM;
/* read undelivered logs from firmware */
ath6kl_read_fwlogs(ar);
spin_lock_bh(&ar->debug.fwlog_lock);
while (len < buf_len && !ath6kl_debug_fwlog_empty(ar)) {
ccnt = CIRC_CNT_TO_END(fwlog->head, fwlog->tail,
ATH6KL_FWLOG_SIZE);
if ((size_t) ccnt > buf_len - len)
ccnt = buf_len - len;
memcpy(buf + len, &fwlog->buf[fwlog->tail], ccnt);
len += ccnt;
fwlog->tail = (fwlog->tail + ccnt) &
(ATH6KL_FWLOG_SIZE - 1);
}
spin_unlock_bh(&ar->debug.fwlog_lock);
if (WARN_ON(len > buf_len))
len = buf_len;
ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
vfree(buf);
return ret_cnt;
}
static const struct file_operations fops_fwlog = {
.open = ath6kl_debugfs_open,
.read = ath6kl_fwlog_read,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath6kl_fwlog_mask_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath6kl *ar = file->private_data;
char buf[16];
int len;
len = snprintf(buf, sizeof(buf), "0x%x\n", ar->debug.fwlog_mask);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath6kl_fwlog_mask_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath6kl *ar = file->private_data;
int ret;
ret = kstrtou32_from_user(user_buf, count, 0, &ar->debug.fwlog_mask);
if (ret)
return ret;
ret = ath6kl_wmi_config_debug_module_cmd(ar->wmi,
ATH6KL_FWLOG_VALID_MASK,
ar->debug.fwlog_mask);
if (ret)
return ret;
return count;
}
static const struct file_operations fops_fwlog_mask = {
.open = ath6kl_debugfs_open,
.read = ath6kl_fwlog_mask_read,
.write = ath6kl_fwlog_mask_write,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath6kl *ar = file->private_data;
struct target_stats *tgt_stats = &ar->target_stats;
char *buf;
unsigned int len = 0, buf_len = 1500;
int i;
long left;
ssize_t ret_cnt;
buf = kzalloc(buf_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (down_interruptible(&ar->sem)) {
kfree(buf);
return -EBUSY;
}
set_bit(STATS_UPDATE_PEND, &ar->flag);
if (ath6kl_wmi_get_stats_cmd(ar->wmi)) {
up(&ar->sem);
kfree(buf);
return -EIO;
}
left = wait_event_interruptible_timeout(ar->event_wq,
!test_bit(STATS_UPDATE_PEND,
&ar->flag), WMI_TIMEOUT);
up(&ar->sem);
if (left <= 0) {
kfree(buf);
return -ETIMEDOUT;
}
len += scnprintf(buf + len, buf_len - len, "\n");
len += scnprintf(buf + len, buf_len - len, "%25s\n",
"Target Tx stats");
len += scnprintf(buf + len, buf_len - len, "%25s\n\n",
"=================");
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Ucast packets", tgt_stats->tx_ucast_pkt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Bcast packets", tgt_stats->tx_bcast_pkt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Ucast byte", tgt_stats->tx_ucast_byte);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Bcast byte", tgt_stats->tx_bcast_byte);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Rts success cnt", tgt_stats->tx_rts_success_cnt);
for (i = 0; i < 4; i++)
len += scnprintf(buf + len, buf_len - len,
"%18s %d %10llu\n", "PER on ac",
i, tgt_stats->tx_pkt_per_ac[i]);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Error", tgt_stats->tx_err);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Fail count", tgt_stats->tx_fail_cnt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Retry count", tgt_stats->tx_retry_cnt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Multi retry cnt", tgt_stats->tx_mult_retry_cnt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Rts fail cnt", tgt_stats->tx_rts_fail_cnt);
len += scnprintf(buf + len, buf_len - len, "%25s %10llu\n\n",
"TKIP counter measure used",
tgt_stats->tkip_cnter_measures_invoked);
len += scnprintf(buf + len, buf_len - len, "%25s\n",
"Target Rx stats");
len += scnprintf(buf + len, buf_len - len, "%25s\n",
"=================");
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Ucast packets", tgt_stats->rx_ucast_pkt);
len += scnprintf(buf + len, buf_len - len, "%20s %10d\n",
"Ucast Rate", tgt_stats->rx_ucast_rate);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Bcast packets", tgt_stats->rx_bcast_pkt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Ucast byte", tgt_stats->rx_ucast_byte);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Bcast byte", tgt_stats->rx_bcast_byte);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Fragmented pkt", tgt_stats->rx_frgment_pkt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Error", tgt_stats->rx_err);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"CRC Err", tgt_stats->rx_crc_err);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Key chache miss", tgt_stats->rx_key_cache_miss);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Decrypt Err", tgt_stats->rx_decrypt_err);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Duplicate frame", tgt_stats->rx_dupl_frame);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Tkip Mic failure", tgt_stats->tkip_local_mic_fail);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"TKIP format err", tgt_stats->tkip_fmt_err);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"CCMP format Err", tgt_stats->ccmp_fmt_err);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n\n",
"CCMP Replay Err", tgt_stats->ccmp_replays);
len += scnprintf(buf + len, buf_len - len, "%25s\n",
"Misc Target stats");
len += scnprintf(buf + len, buf_len - len, "%25s\n",
"=================");
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Beacon Miss count", tgt_stats->cs_bmiss_cnt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Num Connects", tgt_stats->cs_connect_cnt);
len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n",
"Num disconnects", tgt_stats->cs_discon_cnt);
len += scnprintf(buf + len, buf_len - len, "%20s %10d\n",
"Beacon avg rssi", tgt_stats->cs_ave_beacon_rssi);
if (len > buf_len)
len = buf_len;
ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret_cnt;
}
static const struct file_operations fops_tgt_stats = {
.read = read_file_tgt_stats,
.open = ath6kl_debugfs_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
#define print_credit_info(fmt_str, ep_list_field) \
(len += scnprintf(buf + len, buf_len - len, fmt_str, \
ep_list->ep_list_field))
#define CREDIT_INFO_DISPLAY_STRING_LEN 200
#define CREDIT_INFO_LEN 128
static ssize_t read_file_credit_dist_stats(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath6kl *ar = file->private_data;
struct htc_target *target = ar->htc_target;
struct htc_endpoint_credit_dist *ep_list;
char *buf;
unsigned int buf_len, len = 0;
ssize_t ret_cnt;
buf_len = CREDIT_INFO_DISPLAY_STRING_LEN +
get_queue_depth(&target->cred_dist_list) * CREDIT_INFO_LEN;
buf = kzalloc(buf_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, buf_len - len, "%25s%5d\n",
"Total Avail Credits: ",
target->cred_dist_cntxt->total_avail_credits);
len += scnprintf(buf + len, buf_len - len, "%25s%5d\n",
"Free credits :",
target->cred_dist_cntxt->cur_free_credits);
len += scnprintf(buf + len, buf_len - len,
" Epid Flags Cred_norm Cred_min Credits Cred_assngd"
" Seek_cred Cred_sz Cred_per_msg Cred_to_dist"
" qdepth\n");
list_for_each_entry(ep_list, &target->cred_dist_list, list) {
print_credit_info(" %2d", endpoint);
print_credit_info("%10x", dist_flags);
print_credit_info("%8d", cred_norm);
print_credit_info("%9d", cred_min);
print_credit_info("%9d", credits);
print_credit_info("%10d", cred_assngd);
print_credit_info("%13d", seek_cred);
print_credit_info("%12d", cred_sz);
print_credit_info("%9d", cred_per_msg);
print_credit_info("%14d", cred_to_dist);
len += scnprintf(buf + len, buf_len - len, "%12d\n",
get_queue_depth(&((struct htc_endpoint *)
ep_list->htc_rsvd)->txq));
}
if (len > buf_len)
len = buf_len;
ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret_cnt;
}
static const struct file_operations fops_credit_dist_stats = {
.read = read_file_credit_dist_stats,
.open = ath6kl_debugfs_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
int ath6kl_debug_init(struct ath6kl *ar)
{
ar->debug.fwlog_buf.buf = vmalloc(ATH6KL_FWLOG_SIZE);
if (ar->debug.fwlog_buf.buf == NULL)
return -ENOMEM;
ar->debug.fwlog_tmp = kmalloc(ATH6KL_FWLOG_SLOT_SIZE, GFP_KERNEL);
if (ar->debug.fwlog_tmp == NULL) {
vfree(ar->debug.fwlog_buf.buf);
return -ENOMEM;
}
spin_lock_init(&ar->debug.fwlog_lock);
/*
* Actually we are lying here but don't know how to read the mask
* value from the firmware.
*/
ar->debug.fwlog_mask = 0;
ar->debugfs_phy = debugfs_create_dir("ath6kl",
ar->wdev->wiphy->debugfsdir);
if (!ar->debugfs_phy) {
vfree(ar->debug.fwlog_buf.buf);
kfree(ar->debug.fwlog_tmp);
return -ENOMEM;
}
debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar,
&fops_tgt_stats);
debugfs_create_file("credit_dist_stats", S_IRUSR, ar->debugfs_phy, ar,
&fops_credit_dist_stats);
debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar,
&fops_fwlog);
debugfs_create_file("fwlog_mask", S_IRUSR | S_IWUSR, ar->debugfs_phy,
ar, &fops_fwlog_mask);
return 0;
}
void ath6kl_debug_cleanup(struct ath6kl *ar)
{
vfree(ar->debug.fwlog_buf.buf);
kfree(ar->debug.fwlog_tmp);
}
#endif