qcacmn: Fix IRQ disable for unit-test suspend on PCIe

pld_disable_irq is a no-op for PCI devices, causing unit-test resume
to fail in some cases. Define new IRQ disable/enable API's in HIF for
unit-test suspend/resume to use.

Change-Id: Ib3e6007025e791c867463c7d9f64512628c4ca0b
CRs-Fixed: 2023978
This commit is contained in:
Dustin Brown 2017-03-20 15:02:48 -07:00 • committed by Sandeep Puligilla
commit 6834d321d8
12 changed files with 265 additions and 117 deletions

View file

@ -722,6 +722,50 @@ int hif_bus_suspend(struct hif_opaque_softc *);
int hif_bus_resume_noirq(struct hif_opaque_softc *);
int hif_bus_suspend_noirq(struct hif_opaque_softc *);
/**
* hif_apps_irqs_enable() - Enables all irqs from the APPS side
* @hif_ctx: an opaque HIF handle to use
*
* As opposed to the standard hif_irq_enable, this function always applies to
* the APPS side kernel interrupt handling.
*
* Return: errno
*/
int hif_apps_irqs_enable(struct hif_opaque_softc *hif_ctx);
/**
* hif_apps_irqs_disable() - Disables all irqs from the APPS side
* @hif_ctx: an opaque HIF handle to use
*
* As opposed to the standard hif_irq_disable, this function always applies to
* the APPS side kernel interrupt handling.
*
* Return: errno
*/
int hif_apps_irqs_disable(struct hif_opaque_softc *hif_ctx);
/**
* hif_apps_wake_irq_enable() - Enables the wake irq from the APPS side
* @hif_ctx: an opaque HIF handle to use
*
* As opposed to the standard hif_irq_enable, this function always applies to
* the APPS side kernel interrupt handling.
*
* Return: errno
*/
int hif_apps_wake_irq_enable(struct hif_opaque_softc *hif_ctx);
/**
* hif_apps_wake_irq_disable() - Disables the wake irq from the APPS side
* @hif_ctx: an opaque HIF handle to use
*
* As opposed to the standard hif_irq_disable, this function always applies to
* the APPS side kernel interrupt handling.
*
* Return: errno
*/
int hif_apps_wake_irq_disable(struct hif_opaque_softc *hif_ctx);
#ifdef FEATURE_RUNTIME_PM
int hif_pre_runtime_suspend(struct hif_opaque_softc *hif_ctx);
void hif_pre_runtime_resume(struct hif_opaque_softc *hif_ctx);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -495,4 +495,13 @@ static inline void ce_t2h_msg_ce_cleanup(struct CE_handle *ce_hdl)
/* Additional internal-only ce_send flags */
#define CE_SEND_FLAG_GATHER 0x00010000 /* Use Gather */
/**
* hif_get_wake_ce_id() - gets the copy engine id used for waking up
* @scn: The hif context to use
* @ce_id: a pointer where the copy engine Id should be populated
*
* Return: errno
*/
int hif_get_wake_ce_id(struct hif_softc *scn, uint8_t *ce_id);
#endif /* __COPY_ENGINE_INTERNAL_H__ */

View file

@ -3177,3 +3177,23 @@ void hif_wlan_disable(struct hif_softc *scn)
pld_wlan_disable(scn->qdf_dev->dev, mode);
}
int hif_get_wake_ce_id(struct hif_softc *scn, uint8_t *ce_id)
{
QDF_STATUS status;
uint8_t ul_pipe, dl_pipe;
int ul_is_polled, dl_is_polled;
/* DL pipe for HTC_CTRL_RSVD_SVC should map to the wake CE */
status = hif_map_service_to_pipe(GET_HIF_OPAQUE_HDL(scn),
HTC_CTRL_RSVD_SVC,
&ul_pipe, &dl_pipe,
&ul_is_polled, &dl_is_polled);
if (status) {
HIF_ERROR("%s: failed to map pipe: %d", __func__, status);
return qdf_status_to_os_return(status);
}
*ce_id = dl_pipe;
return 0;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2015-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -335,25 +335,20 @@ static bool hif_interrupt_is_fake_apps_resume(struct hif_opaque_softc *hif_ctx,
int ce_id)
{
struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);
uint8_t ul_pipe, dl_pipe;
int ul_is_polled, dl_is_polled;
QDF_STATUS status;
int errno;
uint8_t wake_ce_id;
if (!test_bit(HIF_FA_SUSPENDED_BIT, &scn->fake_apps_ctx.state))
return false;
/* ensure passed ce_id matches wake irq */
/* dl_pipe will be populated with the wake irq number */
status = hif_map_service_to_pipe(hif_ctx, HTC_CTRL_RSVD_SVC,
&ul_pipe, &dl_pipe,
&ul_is_polled, &dl_is_polled);
if (status) {
HIF_ERROR("%s: pipe_mapping failure", __func__);
/* ensure passed ce_id matches wake ce_id */
errno = hif_get_wake_ce_id(scn, &wake_ce_id);
if (errno) {
HIF_ERROR("%s: failed to get wake CE Id: %d", __func__, errno);
return false;
}
return ce_id == dl_pipe;
return ce_id == wake_ce_id;
}
/**

View file

@ -35,6 +35,8 @@
#include "dummy.h"
#if defined(HIF_PCI) || defined(HIF_SNOC) || defined(HIF_AHB)
#include "ce_main.h"
#include "ce_api.h"
#include "ce_internal.h"
#endif
#include "htc_services.h"
#include "a_types.h"
@ -412,3 +414,83 @@ int hif_bus_reset_resume(struct hif_opaque_softc *scn)
struct hif_softc *hif_sc = HIF_GET_SOFTC(scn);
return hif_sc->bus_ops.hif_bus_reset_resume(hif_sc);
}
int hif_apps_irqs_disable(struct hif_opaque_softc *hif_ctx)
{
struct hif_softc *scn;
int i;
scn = HIF_GET_SOFTC(hif_ctx);
if (!scn) {
QDF_BUG(0);
return -EINVAL;
}
for (i = 0; i < scn->ce_count; ++i)
disable_irq(scn->bus_ops.hif_map_ce_to_irq(scn, i));
return 0;
}
int hif_apps_irqs_enable(struct hif_opaque_softc *hif_ctx)
{
struct hif_softc *scn;
int i;
scn = HIF_GET_SOFTC(hif_ctx);
if (!scn) {
QDF_BUG(0);
return -EINVAL;
}
for (i = 0; i < scn->ce_count; ++i)
enable_irq(scn->bus_ops.hif_map_ce_to_irq(scn, i));
return 0;
}
int hif_apps_wake_irq_disable(struct hif_opaque_softc *hif_ctx)
{
int errno;
struct hif_softc *scn;
uint8_t wake_ce_id;
scn = HIF_GET_SOFTC(hif_ctx);
if (!scn) {
QDF_BUG(0);
return -EINVAL;
}
errno = hif_get_wake_ce_id(scn, &wake_ce_id);
if (errno) {
HIF_ERROR("%s: failed to get wake CE Id: %d", __func__, errno);
return errno;
}
disable_irq(scn->bus_ops.hif_map_ce_to_irq(scn, wake_ce_id));
return 0;
}
int hif_apps_wake_irq_enable(struct hif_opaque_softc *hif_ctx)
{
int errno;
struct hif_softc *scn;
uint8_t wake_ce_id;
scn = HIF_GET_SOFTC(hif_ctx);
if (!scn) {
QDF_BUG(0);
return -EINVAL;
}
errno = hif_get_wake_ce_id(scn, &wake_ce_id);
if (errno) {
HIF_ERROR("%s: failed to get wake CE Id: %d", __func__, errno);
return errno;
}
enable_irq(scn->bus_ops.hif_map_ce_to_irq(scn, wake_ce_id));
return 0;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -81,6 +81,7 @@ struct hif_bus_ops {
void (*hif_set_bundle_mode) (struct hif_softc *hif_ctx, bool enabled,
int rx_bundle_cnt);
int (*hif_bus_reset_resume)(struct hif_softc *hif_ctx);
int (*hif_map_ce_to_irq)(struct hif_softc *hif_sc, int ce_id);
};
#ifdef HIF_SNOC

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -90,6 +90,9 @@ QDF_STATUS hif_initialize_pci_ops(struct hif_softc *hif_sc)
bus_ops->hif_grp_irq_disable = &hif_dummy_grp_irq_disable;
bus_ops->hif_grp_irq_enable = &hif_dummy_grp_irq_enable;
/* default to legacy mapping handler; override as needed */
bus_ops->hif_map_ce_to_irq = &hif_pci_legacy_map_ce_to_irq;
return QDF_STATUS_SUCCESS;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -81,6 +81,7 @@ QDF_STATUS hif_initialize_snoc_ops(struct hif_bus_ops *bus_ops)
&hif_snoc_clear_stats;
bus_ops->hif_grp_irq_disable = &hif_dummy_grp_irq_disable;
bus_ops->hif_grp_irq_enable = &hif_dummy_grp_irq_enable;
bus_ops->hif_map_ce_to_irq = &hif_snoc_map_ce_to_irq;
return QDF_STATUS_SUCCESS;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -54,4 +54,5 @@ void hif_pci_enable_power_management(struct hif_softc *hif_ctx,
void hif_pci_disable_power_management(struct hif_softc *hif_ctx);
void hif_pci_display_stats(struct hif_softc *hif_ctx);
void hif_pci_clear_stats(struct hif_softc *hif_ctx);
int hif_pci_legacy_map_ce_to_irq(struct hif_softc *scn, int ce_id);
#endif /* _PCI_API_H_ */

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
@ -46,4 +46,5 @@ void hif_snoc_irq_enable(struct hif_softc *scn, int ce_id);
int hif_snoc_dump_registers(struct hif_softc *scn);
void hif_snoc_display_stats(struct hif_softc *hif_ctx);
void hif_snoc_clear_stats(struct hif_softc *hif_ctx);
int hif_snoc_map_ce_to_irq(struct hif_softc *scn, int ce_id);
#endif /* _SNOC_API_H_ */

View file

@ -2730,30 +2730,21 @@ void hif_pci_prevent_linkdown(struct hif_softc *scn, bool flag)
}
#endif
/**
* hif_pci_bus_enable_wake_irq() - enable pci bus wake irq
*
* Configures the pci irq line as a wakeup source.
*
* Return: 0 for success and non-zero for failure
*/
static int hif_pci_bus_enable_wake_irq(struct hif_softc *scn)
static int hif_mark_wake_irq_wakeable(struct hif_softc *scn)
{
struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn);
int errno;
uint8_t ce_id;
if (!sc) {
HIF_ERROR("%s: sc is null", __func__);
return -EFAULT;
errno = hif_get_wake_ce_id(scn, &ce_id);
if (errno) {
HIF_ERROR("%s: failed to get wake CE Id: %d", __func__, errno);
return errno;
}
if (!sc->pdev) {
HIF_ERROR("%s: pdev is null", __func__);
return -EFAULT;
}
if (unlikely(enable_irq_wake(sc->pdev->irq))) {
HIF_ERROR("%s: Failed to enable wake IRQ", __func__);
return -EINVAL;
errno = enable_irq_wake(scn->bus_ops.hif_map_ce_to_irq(scn, ce_id));
if (errno) {
HIF_ERROR("%s: Failed to mark wake IRQ: %d", __func__, errno);
return errno;
}
return 0;
@ -2768,11 +2759,19 @@ static int hif_pci_bus_enable_wake_irq(struct hif_softc *scn)
*/
int hif_pci_bus_suspend(struct hif_softc *scn)
{
/*
* This is a workaround to a spurious wake interrupt issue. The
* proper fix is being worked on, and this should be removed as
* soon as possible.
*/
HIF_ERROR("%s: Pausing bus suspend for 200ms", __func__);
msleep(200);
if (hif_can_suspend_link(GET_HIF_OPAQUE_HDL(scn)))
return 0;
/* pci link is staying up; enable wake irq */
return hif_pci_bus_enable_wake_irq(scn);
return hif_mark_wake_irq_wakeable(scn);
}
/**
@ -2814,30 +2813,21 @@ static int __hif_check_link_status(struct hif_softc *scn)
return -EACCES;
}
/**
* hif_pci_bus_disable_wake_irq() - disable pci bus wake irq
*
* Deconfigures the pci irq line as a wakeup source.
*
* Return: 0 for success and non-zero for failure
*/
static int hif_pci_bus_disable_wake_irq(struct hif_softc *scn)
static int hif_unmark_wake_irq_wakeable(struct hif_softc *scn)
{
struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn);
int errno;
uint8_t ce_id;
if (!sc) {
HIF_ERROR("%s: sc is null", __func__);
return -EFAULT;
errno = hif_get_wake_ce_id(scn, &ce_id);
if (errno) {
HIF_ERROR("%s: failed to get wake CE Id: %d", __func__, errno);
return errno;
}
if (!sc->pdev) {
HIF_ERROR("%s: pdev is null", __func__);
return -EFAULT;
}
if (unlikely(disable_irq_wake(sc->pdev->irq))) {
HIF_ERROR("%s: Failed to disable wake IRQ", __func__);
return -EFAULT;
errno = disable_irq_wake(scn->bus_ops.hif_map_ce_to_irq(scn, ce_id));
if (errno) {
HIF_ERROR("%s: Failed to unmark wake IRQ: %d", __func__, errno);
return errno;
}
return 0;
@ -2862,7 +2852,7 @@ int hif_pci_bus_resume(struct hif_softc *scn)
return 0;
/* pci link is up; disable wake irq */
return hif_pci_bus_disable_wake_irq(scn);
return hif_unmark_wake_irq_wakeable(scn);
}
/**
@ -3093,30 +3083,39 @@ int hif_runtime_suspend(struct hif_opaque_softc *hif_ctx)
{
struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);
struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn);
int err;
int errno;
err = hif_pci_bus_suspend(scn);
if (err)
goto exit_with_error;
errno = hif_bus_suspend(hif_ctx);
if (errno) {
HIF_ERROR("%s: failed bus suspend: %d", __func__, errno);
return errno;
}
/*
* normal 3-stage suspend from CNSS disables irqs before calling
* noirq stage
*/
disable_irq(sc->pdev->irq);
err = hif_pci_bus_suspend_noirq(scn);
if (err)
errno = hif_apps_irqs_disable(hif_ctx);
if (errno) {
HIF_ERROR("%s: failed disable irqs: %d", __func__, errno);
goto bus_resume;
}
errno = hif_bus_suspend_noirq(hif_ctx);
if (errno) {
HIF_ERROR("%s: failed bus suspend noirq: %d", __func__, errno);
goto irqs_enable;
}
/* link should always be down; skip enable wake irq */
return 0;
bus_resume:
enable_irq(sc->pdev->irq);
err = hif_pci_bus_resume(scn);
QDF_BUG(err == 0);
bus_resume_noirq:
QDF_BUG(!hif_bus_resume_noirq(hif_ctx));
irq_enable:
QDF_BUG(!hif_apps_irqs_enable(hif_ctx));
bus_resume:
QDF_BUG(!hif_bus_resume(hif_ctx));
exit_with_error:
return err;
}
@ -3154,7 +3153,6 @@ static void hif_fastpath_resume(struct hif_opaque_softc *hif_ctx)
static void hif_fastpath_resume(struct hif_opaque_softc *hif_ctx) {}
#endif
/**
* hif_runtime_resume() - do the bus resume part of a runtime resume
*
@ -3162,35 +3160,14 @@ static void hif_fastpath_resume(struct hif_opaque_softc *hif_ctx) {}
*/
int hif_runtime_resume(struct hif_opaque_softc *hif_ctx)
{
struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);
struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn);
int err;
err = hif_pci_bus_resume_noirq(scn);
if (err)
goto exit_with_error;
/*
* normal 3-stage resume from CNSS enables irqs after calling
* noirq stage
*/
enable_irq(sc->pdev->irq);
err = hif_pci_bus_resume(scn);
if (err)
goto bus_suspend_noirq;
/* link should always be down; skip disable wake irq */
QDF_BUG(!hif_bus_resume_noirq(hif_ctx));
QDF_BUG(!hif_apps_irqs_enable(hif_ctx));
QDF_BUG(!hif_bus_resume(hif_ctx));
hif_fastpath_resume(hif_ctx);
return 0;
bus_suspend_noirq:
disable_irq(sc->pdev->irq);
err = hif_pci_bus_suspend_noirq(scn);
QDF_BUG(err == 0);
exit_with_error:
return err;
}
#endif /* #ifdef FEATURE_RUNTIME_PM */
@ -3580,6 +3557,13 @@ static irqreturn_t hif_ce_interrupt_handler(int irq, void *context)
}
extern const char *ce_name[];
static int hif_ce_msi_map_ce_to_irq(struct hif_softc *scn, int ce_id)
{
struct hif_pci_softc *pci_scn = HIF_GET_PCI_SOFTC(scn);
return pci_scn->ce_msi_irq_num[ce_id];
}
/* hif_srng_msi_irq_disable() - disable the irq for msi
* @hif_sc: hif context
* @ce_id: which ce to disable copy complete interrupts for
@ -3590,16 +3574,12 @@ extern const char *ce_name[];
*/
static void hif_ce_srng_msi_irq_disable(struct hif_softc *hif_sc, int ce_id)
{
struct hif_pci_softc *pci_sc = HIF_GET_PCI_SOFTC(hif_sc);
disable_irq_nosync(pci_sc->ce_msi_irq_num[ce_id]);
disable_irq_nosync(hif_ce_msi_map_ce_to_irq(hif_sc, ce_id));
}
static void hif_ce_srng_msi_irq_enable(struct hif_softc *hif_sc, int ce_id)
{
struct hif_pci_softc *pci_sc = HIF_GET_PCI_SOFTC(hif_sc);
enable_irq(pci_sc->ce_msi_irq_num[ce_id]);
enable_irq(hif_ce_msi_map_ce_to_irq(hif_sc, ce_id));
}
static void hif_ce_legacy_msi_irq_disable(struct hif_softc *hif_sc, int ce_id)
@ -3625,17 +3605,15 @@ static int hif_ce_msi_configure_irq(struct hif_softc *scn)
return ret;
if (ce_srng_based(scn)) {
scn->bus_ops.hif_irq_disable =
&hif_ce_srng_msi_irq_disable;
scn->bus_ops.hif_irq_enable =
&hif_ce_srng_msi_irq_enable;
scn->bus_ops.hif_irq_disable = &hif_ce_srng_msi_irq_disable;
scn->bus_ops.hif_irq_enable = &hif_ce_srng_msi_irq_enable;
} else {
scn->bus_ops.hif_irq_disable =
&hif_ce_legacy_msi_irq_disable;
scn->bus_ops.hif_irq_enable =
&hif_ce_legacy_msi_irq_enable;
scn->bus_ops.hif_irq_disable = &hif_ce_legacy_msi_irq_disable;
scn->bus_ops.hif_irq_enable = &hif_ce_legacy_msi_irq_enable;
}
scn->bus_ops.hif_map_ce_to_irq = &hif_ce_msi_map_ce_to_irq;
/* needs to match the ce_id -> irq data mapping
* used in the srng parameter configuration
*/
@ -3955,6 +3933,7 @@ void hif_pci_irq_enable(struct hif_softc *scn, int ce_id)
/* check for missed firmware crash */
hif_fw_interrupt_handler(0, scn);
}
/**
* hif_pci_irq_disable() - ce_irq_disable
* @scn: hif_softc
@ -4436,3 +4415,11 @@ void hif_runtime_lock_deinit(struct hif_opaque_softc *hif_ctx,
qdf_mem_free(context);
}
#endif /* FEATURE_RUNTIME_PM */
int hif_pci_legacy_map_ce_to_irq(struct hif_softc *scn, int ce_id)
{
struct hif_pci_softc *pci_scn = HIF_GET_PCI_SOFTC(scn);
/* legacy case only has one irq */
return pci_scn->irq;
}

View file

@ -457,3 +457,7 @@ int hif_snoc_bus_suspend_noirq(struct hif_softc *scn)
return 0;
}
int hif_snoc_map_ce_to_irq(struct hif_softc *scn, int ce_id)
{
return icnss_get_irq(ce_id);
}