Skip to content

Commit

Permalink
ath10k: Keep track of which interrupts fired, don't poll them
Browse files Browse the repository at this point in the history
If we have a per CE (Copy Engine) IRQ then we have no summary
register.  Right now the code generates a summary register by
iterating over all copy engines and seeing if they have an interrupt
pending.

This has a problem.  Specifically if _none_ if the Copy Engines have
an interrupt pending then they might go into low power mode and
reading from their address space will cause a full system crash.  This
was seen to happen when two interrupts went off at nearly the same
time.  Both were handled by a single call of ath10k_snoc_napi_poll()
but, because there were two interrupts handled and thus two calls to
napi_schedule() there was still a second call to
ath10k_snoc_napi_poll() which ran with no interrupts pending.

Instead of iterating over all the copy engines, let's just keep track
of the IRQs that fire.  Then we can effectively generate our own
summary without ever needing to read the Copy Engines.

Tested-on: WCN3990 SNOC WLAN.HL.3.2.2-00490-QCAHLSWMTPL-1

Signed-off-by: Douglas Anderson <[email protected]>
Reviewed-by: Rakesh Pillai <[email protected]>
Reviewed-by: Brian Norris <[email protected]>
Signed-off-by: Kalle Valo <[email protected]>
Link: https://lore.kernel.org/r/20200709082024.v2.1.I4d2f85ffa06f38532631e864a3125691ef5ffe06@changeid
  • Loading branch information
dianders authored and Kalle Valo committed Sep 1, 2020
1 parent b92aba3 commit d66d24a
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 66 deletions.
84 changes: 31 additions & 53 deletions drivers/net/wireless/ath/ath10k/ce.c
Original file line number Diff line number Diff line change
Expand Up @@ -481,38 +481,6 @@ static inline void ath10k_ce_engine_int_status_clear(struct ath10k *ar,
ath10k_ce_write32(ar, ce_ctrl_addr + wm_regs->addr, mask);
}

static bool ath10k_ce_engine_int_status_check(struct ath10k *ar, u32 ce_ctrl_addr,
unsigned int mask)
{
struct ath10k_hw_ce_host_wm_regs *wm_regs = ar->hw_ce_regs->wm_regs;

return ath10k_ce_read32(ar, ce_ctrl_addr + wm_regs->addr) & mask;
}

u32 ath10k_ce_gen_interrupt_summary(struct ath10k *ar)
{
struct ath10k_hw_ce_host_wm_regs *wm_regs = ar->hw_ce_regs->wm_regs;
struct ath10k_ce_pipe *ce_state;
struct ath10k_ce *ce;
u32 irq_summary = 0;
u32 ctrl_addr;
u32 ce_id;

ce = ath10k_ce_priv(ar);

for (ce_id = 0; ce_id < CE_COUNT; ce_id++) {
ce_state = &ce->ce_states[ce_id];
ctrl_addr = ce_state->ctrl_addr;
if (ath10k_ce_engine_int_status_check(ar, ctrl_addr,
wm_regs->cc_mask)) {
irq_summary |= BIT(ce_id);
}
}

return irq_summary;
}
EXPORT_SYMBOL(ath10k_ce_gen_interrupt_summary);

/*
* Guts of ath10k_ce_send.
* The caller takes responsibility for any needed locking.
Expand Down Expand Up @@ -1399,45 +1367,55 @@ static void ath10k_ce_per_engine_handler_adjust(struct ath10k_ce_pipe *ce_state)
ath10k_ce_watermark_intr_disable(ar, ctrl_addr);
}

int ath10k_ce_disable_interrupts(struct ath10k *ar)
void ath10k_ce_disable_interrupt(struct ath10k *ar, int ce_id)
{
struct ath10k_ce *ce = ath10k_ce_priv(ar);
struct ath10k_ce_pipe *ce_state;
u32 ctrl_addr;
int ce_id;

for (ce_id = 0; ce_id < CE_COUNT; ce_id++) {
ce_state = &ce->ce_states[ce_id];
if (ce_state->attr_flags & CE_ATTR_POLL)
continue;
ce_state = &ce->ce_states[ce_id];
if (ce_state->attr_flags & CE_ATTR_POLL)
return;

ctrl_addr = ath10k_ce_base_address(ar, ce_id);
ctrl_addr = ath10k_ce_base_address(ar, ce_id);

ath10k_ce_copy_complete_intr_disable(ar, ctrl_addr);
ath10k_ce_error_intr_disable(ar, ctrl_addr);
ath10k_ce_watermark_intr_disable(ar, ctrl_addr);
}
ath10k_ce_copy_complete_intr_disable(ar, ctrl_addr);
ath10k_ce_error_intr_disable(ar, ctrl_addr);
ath10k_ce_watermark_intr_disable(ar, ctrl_addr);
}
EXPORT_SYMBOL(ath10k_ce_disable_interrupt);

return 0;
void ath10k_ce_disable_interrupts(struct ath10k *ar)
{
int ce_id;

for (ce_id = 0; ce_id < CE_COUNT; ce_id++)
ath10k_ce_disable_interrupt(ar, ce_id);
}
EXPORT_SYMBOL(ath10k_ce_disable_interrupts);

void ath10k_ce_enable_interrupts(struct ath10k *ar)
void ath10k_ce_enable_interrupt(struct ath10k *ar, int ce_id)
{
struct ath10k_ce *ce = ath10k_ce_priv(ar);
int ce_id;
struct ath10k_ce_pipe *ce_state;

ce_state = &ce->ce_states[ce_id];
if (ce_state->attr_flags & CE_ATTR_POLL)
return;

ath10k_ce_per_engine_handler_adjust(ce_state);
}
EXPORT_SYMBOL(ath10k_ce_enable_interrupt);

void ath10k_ce_enable_interrupts(struct ath10k *ar)
{
int ce_id;

/* Enable interrupts for copy engine that
* are not using polling mode.
*/
for (ce_id = 0; ce_id < CE_COUNT; ce_id++) {
ce_state = &ce->ce_states[ce_id];
if (ce_state->attr_flags & CE_ATTR_POLL)
continue;

ath10k_ce_per_engine_handler_adjust(ce_state);
}
for (ce_id = 0; ce_id < CE_COUNT; ce_id++)
ath10k_ce_enable_interrupt(ar, ce_id);
}
EXPORT_SYMBOL(ath10k_ce_enable_interrupts);

Expand Down
14 changes: 6 additions & 8 deletions drivers/net/wireless/ath/ath10k/ce.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,13 @@ int ath10k_ce_cancel_send_next(struct ath10k_ce_pipe *ce_state,
/*==================CE Interrupt Handlers====================*/
void ath10k_ce_per_engine_service_any(struct ath10k *ar);
void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id);
int ath10k_ce_disable_interrupts(struct ath10k *ar);
void ath10k_ce_disable_interrupt(struct ath10k *ar, int ce_id);
void ath10k_ce_disable_interrupts(struct ath10k *ar);
void ath10k_ce_enable_interrupt(struct ath10k *ar, int ce_id);
void ath10k_ce_enable_interrupts(struct ath10k *ar);
void ath10k_ce_dump_registers(struct ath10k *ar,
struct ath10k_fw_crash_data *crash_data);

u32 ath10k_ce_gen_interrupt_summary(struct ath10k *ar);
void ath10k_ce_alloc_rri(struct ath10k *ar);
void ath10k_ce_free_rri(struct ath10k *ar);

Expand Down Expand Up @@ -376,12 +377,9 @@ static inline u32 ath10k_ce_interrupt_summary(struct ath10k *ar)
{
struct ath10k_ce *ce = ath10k_ce_priv(ar);

if (!ar->hw_params.per_ce_irq)
return CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_GET(
ce->bus_ops->read32((ar), CE_WRAPPER_BASE_ADDRESS +
CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS));
else
return ath10k_ce_gen_interrupt_summary(ar);
return CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_GET(
ce->bus_ops->read32((ar), CE_WRAPPER_BASE_ADDRESS +
CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS));
}

/* Host software's Copy Engine configuration. */
Expand Down
19 changes: 14 additions & 5 deletions drivers/net/wireless/ath/ath10k/snoc.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*/

#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/module.h>
Expand Down Expand Up @@ -923,6 +924,7 @@ static int ath10k_snoc_hif_start(struct ath10k *ar)
{
struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);

bitmap_clear(ar_snoc->pending_ce_irqs, 0, CE_COUNT_MAX);
napi_enable(&ar->napi);
ath10k_snoc_irq_enable(ar);
ath10k_snoc_rx_post(ar);
Expand Down Expand Up @@ -1158,7 +1160,9 @@ static irqreturn_t ath10k_snoc_per_engine_handler(int irq, void *arg)
return IRQ_HANDLED;
}

ath10k_snoc_irq_disable(ar);
ath10k_ce_disable_interrupt(ar, ce_id);
set_bit(ce_id, ar_snoc->pending_ce_irqs);

napi_schedule(&ar->napi);

return IRQ_HANDLED;
Expand All @@ -1167,20 +1171,25 @@ static irqreturn_t ath10k_snoc_per_engine_handler(int irq, void *arg)
static int ath10k_snoc_napi_poll(struct napi_struct *ctx, int budget)
{
struct ath10k *ar = container_of(ctx, struct ath10k, napi);
struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
int done = 0;
int ce_id;

if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) {
napi_complete(ctx);
return done;
}

ath10k_ce_per_engine_service_any(ar);
for (ce_id = 0; ce_id < CE_COUNT; ce_id++)
if (test_and_clear_bit(ce_id, ar_snoc->pending_ce_irqs)) {
ath10k_ce_per_engine_service(ar, ce_id);
ath10k_ce_enable_interrupt(ar, ce_id);
}

done = ath10k_htt_txrx_compl_task(ar, budget);

if (done < budget) {
if (done < budget)
napi_complete(ctx);
ath10k_snoc_irq_enable(ar);
}

return done;
}
Expand Down
1 change: 1 addition & 0 deletions drivers/net/wireless/ath/ath10k/snoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ struct ath10k_snoc {
unsigned long flags;
bool xo_cal_supported;
u32 xo_cal_data;
DECLARE_BITMAP(pending_ce_irqs, CE_COUNT_MAX);
};

static inline struct ath10k_snoc *ath10k_snoc_priv(struct ath10k *ar)
Expand Down

0 comments on commit d66d24a

Please sign in to comment.