[PATCH v2 1/3] ath10k: Add history for tracking certain events

From: Rakesh Pillai
Date: Fri Jul 31 2020 - 14:27:46 EST


Add history for tracking the below events
- register read
- register write
- IRQ trigger
- NAPI poll
- CE service
- WMI cmd
- WMI event
- WMI tx completion

This will help in debugging any crash or any
improper behaviour.

Tested-on: WCN3990 hw1.0 SNOC WLAN.HL.3.1-01040-QCAHLSWMTPLZ-1

Signed-off-by: Rakesh Pillai <pillair@xxxxxxxxxxxxxx>
---
drivers/net/wireless/ath/ath10k/ce.c | 1 +
drivers/net/wireless/ath/ath10k/core.h | 74 +++++++++++++++++
drivers/net/wireless/ath/ath10k/debug.c | 133 ++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath10k/debug.h | 74 +++++++++++++++++
drivers/net/wireless/ath/ath10k/snoc.c | 15 +++-
drivers/net/wireless/ath/ath10k/wmi-tlv.c | 1 +
drivers/net/wireless/ath/ath10k/wmi.c | 10 +++
7 files changed, 307 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath10k/ce.c b/drivers/net/wireless/ath/ath10k/ce.c
index 84ec80c..0f541de 100644
--- a/drivers/net/wireless/ath/ath10k/ce.c
+++ b/drivers/net/wireless/ath/ath10k/ce.c
@@ -1299,6 +1299,7 @@ void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id)
struct ath10k_hw_ce_host_wm_regs *wm_regs = ar->hw_ce_regs->wm_regs;
u32 ctrl_addr = ce_state->ctrl_addr;

+ ath10k_record_ce_event(ar, ATH10K_CE_SERVICE, ce_id);
/*
* Clear before handling
*
diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
index 5c18f6c..46bd5aa 100644
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -970,6 +970,75 @@ struct ath10k_bus_params {
bool hl_msdu_ids;
};

+#define ATH10K_REG_ACCESS_HISTORY_MAX 512
+#define ATH10K_CE_EVENT_HISTORY_MAX 1024
+#define ATH10K_WMI_EVENT_HISTORY_MAX 512
+#define ATH10K_WMI_CMD_HISTORY_MAX 256
+
+#define ATH10K_WMI_DATA_LEN 16
+
+enum ath10k_ce_event {
+ ATH10K_IRQ_TRIGGER,
+ ATH10K_NAPI_POLL,
+ ATH10K_CE_SERVICE,
+ ATH10K_NAPI_COMPLETE,
+ ATH10K_NAPI_RESCHED,
+ ATH10K_IRQ_SUMMARY,
+};
+
+enum ath10k_wmi_type {
+ ATH10K_WMI_EVENT,
+ ATH10K_WMI_CMD,
+ ATH10K_WMI_TX_COMPL,
+};
+
+struct ath10k_reg_access_entry {
+ u32 cpu_id;
+ bool write;
+ u32 offset;
+ u32 val;
+ u64 timestamp;
+};
+
+struct ath10k_wmi_event_entry {
+ u32 cpu_id;
+ enum ath10k_wmi_type type;
+ u32 id;
+ u64 timestamp;
+ unsigned char data[ATH10K_WMI_DATA_LEN];
+};
+
+struct ath10k_ce_event_entry {
+ u32 cpu_id;
+ enum ath10k_ce_event event_type;
+ u32 ce_id;
+ u64 timestamp;
+};
+
+struct ath10k_wmi_event_history {
+ struct ath10k_wmi_event_entry *record;
+ u32 max_entries;
+ atomic_t index;
+ /* lock for accessing wmi event history */
+ spinlock_t hist_lock;
+};
+
+struct ath10k_ce_event_history {
+ struct ath10k_ce_event_entry *record;
+ u32 max_entries;
+ atomic_t index;
+ /* lock for accessing ce event history */
+ spinlock_t hist_lock;
+};
+
+struct ath10k_reg_access_history {
+ struct ath10k_reg_access_entry *record;
+ u32 max_entries;
+ atomic_t index;
+ /* lock for accessing register access history */
+ spinlock_t hist_lock;
+};
+
struct ath10k {
struct ath_common ath_common;
struct ieee80211_hw *hw;
@@ -1261,6 +1330,11 @@ struct ath10k {
bool coex_support;
int coex_gpio_pin;

+ struct ath10k_reg_access_history reg_access_history;
+ struct ath10k_ce_event_history ce_event_history;
+ struct ath10k_wmi_event_history wmi_event_history;
+ struct ath10k_wmi_event_history wmi_cmd_history;
+
/* must be last */
u8 drv_priv[] __aligned(sizeof(void *));
};
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index e8250a6..9105b0b 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -2722,4 +2722,137 @@ void ath10k_dbg_dump(struct ath10k *ar,
}
EXPORT_SYMBOL(ath10k_dbg_dump);

+int ath10k_core_reg_access_history_init(struct ath10k *ar, u32 max_entries)
+{
+ ar->reg_access_history.record = vzalloc(max_entries *
+ sizeof(struct ath10k_reg_access_entry));
+ if (!ar->reg_access_history.record)
+ return -ENOMEM;
+
+ ar->reg_access_history.max_entries = max_entries;
+ atomic_set(&ar->reg_access_history.index, 0);
+ spin_lock_init(&ar->reg_access_history.hist_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(ath10k_core_reg_access_history_init);
+
+int ath10k_core_wmi_cmd_history_init(struct ath10k *ar, u32 max_entries)
+{
+ ar->wmi_cmd_history.record = vzalloc(max_entries *
+ sizeof(struct ath10k_wmi_event_entry));
+ if (!ar->wmi_cmd_history.record)
+ return -ENOMEM;
+
+ ar->wmi_cmd_history.max_entries = max_entries;
+ atomic_set(&ar->wmi_cmd_history.index, 0);
+ spin_lock_init(&ar->wmi_cmd_history.hist_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(ath10k_core_wmi_cmd_history_init);
+
+int ath10k_core_wmi_event_history_init(struct ath10k *ar, u32 max_entries)
+{
+ ar->wmi_event_history.record = vzalloc(max_entries *
+ sizeof(struct ath10k_wmi_event_entry));
+ if (!ar->wmi_event_history.record)
+ return -ENOMEM;
+
+ ar->wmi_event_history.max_entries = max_entries;
+ atomic_set(&ar->wmi_event_history.index, 0);
+ spin_lock_init(&ar->wmi_event_history.hist_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(ath10k_core_wmi_event_history_init);
+
+int ath10k_core_ce_event_history_init(struct ath10k *ar, u32 max_entries)
+{
+ ar->ce_event_history.record = vzalloc(max_entries *
+ sizeof(struct ath10k_ce_event_entry));
+ if (!ar->ce_event_history.record)
+ return -ENOMEM;
+
+ ar->ce_event_history.max_entries = max_entries;
+ atomic_set(&ar->ce_event_history.index, 0);
+ spin_lock_init(&ar->ce_event_history.hist_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(ath10k_core_ce_event_history_init);
+
+void ath10k_record_reg_access(struct ath10k *ar, u32 offset, u32 val, bool write)
+{
+ struct ath10k_reg_access_entry *entry;
+ u32 idx;
+
+ if (!ar->reg_access_history.record)
+ return;
+
+ idx = ath10k_core_get_next_idx(&ar->reg_access_history.index,
+ ar->reg_access_history.max_entries);
+ entry = &ar->reg_access_history.record[idx];
+
+ entry->timestamp = ath10k_core_get_timestamp();
+ entry->write = write;
+ entry->offset = offset;
+ entry->val = val;
+}
+EXPORT_SYMBOL(ath10k_record_reg_access);
+
+void ath10k_record_wmi_event(struct ath10k *ar, enum ath10k_wmi_type type,
+ u32 id, unsigned char *data)
+{
+ struct ath10k_wmi_event_entry *entry;
+ u32 idx;
+
+ if (type == ATH10K_WMI_EVENT) {
+ if (!ar->wmi_event_history.record)
+ return;
+
+ spin_lock_bh(&ar->wmi_event_history.hist_lock);
+ idx = ath10k_core_get_next_idx(&ar->reg_access_history.index,
+ ar->wmi_event_history.max_entries);
+ spin_unlock_bh(&ar->wmi_event_history.hist_lock);
+ entry = &ar->wmi_event_history.record[idx];
+ } else {
+ if (!ar->wmi_cmd_history.record)
+ return;
+
+ spin_lock_bh(&ar->wmi_cmd_history.hist_lock);
+ idx = ath10k_core_get_next_idx(&ar->reg_access_history.index,
+ ar->wmi_cmd_history.max_entries);
+ spin_unlock_bh(&ar->wmi_cmd_history.hist_lock);
+ entry = &ar->wmi_cmd_history.record[idx];
+ }
+
+ entry->timestamp = ath10k_core_get_timestamp();
+ entry->cpu_id = smp_processor_id();
+ entry->type = type;
+ entry->id = id;
+ memcpy(&entry->data, data + 4, ATH10K_WMI_DATA_LEN);
+}
+EXPORT_SYMBOL(ath10k_record_wmi_event);
+
+void ath10k_record_ce_event(struct ath10k *ar, enum ath10k_ce_event event_type,
+ int ce_id)
+{
+ struct ath10k_ce_event_entry *entry;
+ u32 idx;
+
+ if (!ar->ce_event_history.record)
+ return;
+
+ idx = ath10k_core_get_next_idx(&ar->ce_event_history.index,
+ ar->ce_event_history.max_entries);
+ entry = &ar->ce_event_history.record[idx];
+
+ entry->timestamp = ath10k_core_get_timestamp();
+ entry->cpu_id = smp_processor_id();
+ entry->event_type = event_type;
+ entry->ce_id = ce_id;
+}
+EXPORT_SYMBOL(ath10k_record_ce_event);
+
#endif /* CONFIG_ATH10K_DEBUG */
diff --git a/drivers/net/wireless/ath/ath10k/debug.h b/drivers/net/wireless/ath/ath10k/debug.h
index 997c1c8..c28aeb1 100644
--- a/drivers/net/wireless/ath/ath10k/debug.h
+++ b/drivers/net/wireless/ath/ath10k/debug.h
@@ -258,6 +258,38 @@ void ath10k_dbg_dump(struct ath10k *ar,
enum ath10k_debug_mask mask,
const char *msg, const char *prefix,
const void *buf, size_t len);
+
+/* ========== History init APIs =========== */
+int ath10k_core_reg_access_history_init(struct ath10k *ar, u32 max_entries);
+int ath10k_core_wmi_cmd_history_init(struct ath10k *ar, u32 max_entries);
+int ath10k_core_wmi_event_history_init(struct ath10k *ar, u32 max_entries);
+int ath10k_core_ce_event_history_init(struct ath10k *ar, u32 max_entries);
+
+/* ========== History record APIs =========== */
+void ath10k_record_reg_access(struct ath10k *ar, u32 offset, u32 val,
+ bool write);
+void ath10k_record_wmi_event(struct ath10k *ar, enum ath10k_wmi_type type,
+ u32 id, unsigned char *data);
+void ath10k_record_ce_event(struct ath10k *ar,
+ enum ath10k_ce_event event_type,
+ int ce_id);
+
+static inline u64 ath10k_core_get_timestamp(void)
+{
+ struct timespec64 ts;
+
+ ktime_get_real_ts64(&ts);
+ return ((u64)ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
+}
+
+static inline int ath10k_core_get_next_idx(atomic_t *index, u32 max_entries)
+{
+ u32 curr_idx;
+
+ curr_idx = atomic_fetch_inc(index);
+ return (curr_idx & (max_entries - 1));
+}
+
#else /* CONFIG_ATH10K_DEBUG */

static inline int __ath10k_dbg(struct ath10k *ar,
@@ -273,6 +305,48 @@ static inline void ath10k_dbg_dump(struct ath10k *ar,
const void *buf, size_t len)
{
}
+
+static inline int ath10k_core_reg_access_history_init(struct ath10k *ar,
+ u32 max_entries)
+{
+ return 0;
+}
+
+static inline int ath10k_core_wmi_cmd_history_init(struct ath10k *ar,
+ u32 max_entries)
+{
+ return 0;
+}
+
+static inline int ath10k_core_wmi_event_history_init(struct ath10k *ar,
+ u32 max_entries)
+{
+ return 0;
+}
+
+static inline int ath10k_core_ce_event_history_init(struct ath10k *ar,
+ u32 max_entries)
+{
+ return 0;
+}
+
+static inline void ath10k_record_reg_access(struct ath10k *ar, u32 offset,
+ u32 val, bool write)
+{
+}
+
+static inline void ath10k_record_wmi_event(struct ath10k *ar,
+ enum ath10k_wmi_type type,
+ u32 id, unsigned char *data)
+{
+}
+
+static inline void ath10k_record_ce_event(struct ath10k *ar,
+ enum ath10k_ce_event event_type,
+ int ce_id)
+{
+}
+
#endif /* CONFIG_ATH10K_DEBUG */

/* Avoid calling __ath10k_dbg() if debug_mask is not set and tracing
diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c
index 1ef5fdb..aa7ee32 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.c
+++ b/drivers/net/wireless/ath/ath10k/snoc.c
@@ -473,6 +473,7 @@ static void ath10k_snoc_write32(struct ath10k *ar, u32 offset, u32 value)
{
struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);

+ ath10k_record_reg_access(ar, offset, value, true);
iowrite32(value, ar_snoc->mem + offset);
}

@@ -482,6 +483,7 @@ static u32 ath10k_snoc_read32(struct ath10k *ar, u32 offset)
u32 val;

val = ioread32(ar_snoc->mem + offset);
+ ath10k_record_reg_access(ar, offset, val, false);

return val;
}
@@ -1159,6 +1161,7 @@ static irqreturn_t ath10k_snoc_per_engine_handler(int irq, void *arg)
ce_id);
return IRQ_HANDLED;
}
+ ath10k_record_ce_event(ar, ATH10K_IRQ_TRIGGER, ce_id);

ath10k_ce_disable_interrupt(ar, ce_id);
set_bit(ce_id, ar_snoc->pending_ce_irqs);
@@ -1175,6 +1178,7 @@ static int ath10k_snoc_napi_poll(struct napi_struct *ctx, int budget)
int done = 0;
int ce_id;

+ ath10k_record_ce_event(ar, ATH10K_NAPI_POLL, 0);
if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) {
napi_complete(ctx);
return done;
@@ -1188,8 +1192,12 @@ static int ath10k_snoc_napi_poll(struct napi_struct *ctx, int budget)

done = ath10k_htt_txrx_compl_task(ar, budget);

- if (done < budget)
+ if (done < budget) {
napi_complete(ctx);
+ ath10k_record_ce_event(ar, ATH10K_NAPI_COMPLETE, 0);
+ } else {
+ ath10k_record_ce_event(ar, ATH10K_NAPI_RESCHED, 0);
+ }

return done;
}
@@ -1660,6 +1668,11 @@ static int ath10k_snoc_probe(struct platform_device *pdev)
ar->ce_priv = &ar_snoc->ce;
msa_size = drv_data->msa_size;

+ ath10k_core_reg_access_history_init(ar, ATH10K_REG_ACCESS_HISTORY_MAX);
+ ath10k_core_wmi_event_history_init(ar, ATH10K_WMI_EVENT_HISTORY_MAX);
+ ath10k_core_wmi_cmd_history_init(ar, ATH10K_WMI_CMD_HISTORY_MAX);
+ ath10k_core_ce_event_history_init(ar, ATH10K_CE_EVENT_HISTORY_MAX);
+
ath10k_snoc_quirks_init(ar);

ret = ath10k_snoc_resource_init(ar);
diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
index 932266d..9df5748 100644
--- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
+++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
@@ -627,6 +627,7 @@ static void ath10k_wmi_tlv_op_rx(struct ath10k *ar, struct sk_buff *skb)
if (skb_pull(skb, sizeof(struct wmi_cmd_hdr)) == NULL)
goto out;

+ ath10k_record_wmi_event(ar, ATH10K_WMI_EVENT, id, skb->data);
trace_ath10k_wmi_event(ar, id, skb->data, skb->len);

consumed = ath10k_tm_event_wmi(ar, id, skb);
diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
index a81a1ab..8ebd05c 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.c
+++ b/drivers/net/wireless/ath/ath10k/wmi.c
@@ -1802,6 +1802,15 @@ struct sk_buff *ath10k_wmi_alloc_skb(struct ath10k *ar, u32 len)

static void ath10k_wmi_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb)
{
+ struct wmi_cmd_hdr *cmd_hdr;
+ enum wmi_tlv_event_id id;
+
+ cmd_hdr = (struct wmi_cmd_hdr *)skb->data;
+ id = MS(__le32_to_cpu(cmd_hdr->cmd_id), WMI_CMD_HDR_CMD_ID);
+
+ ath10k_record_wmi_event(ar, ATH10K_WMI_TX_COMPL, id,
+ skb->data + sizeof(struct wmi_cmd_hdr));
+
dev_kfree_skb(skb);
}

@@ -1912,6 +1921,7 @@ int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id)

might_sleep();

+ ath10k_record_wmi_event(ar, ATH10K_WMI_CMD, cmd_id, skb->data);
if (cmd_id == WMI_CMD_UNSUPPORTED) {
ath10k_warn(ar, "wmi command %d is not supported by firmware\n",
cmd_id);
--
2.7.4