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