blob: 1496f08cdda6466ffc84d0a062e27c3c65014bd7 [file] [log] [blame]
/*
* Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, 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.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/*
* Implementation of the Host-side Host InterFace (HIF) API
* for a Host/Target interconnect using Copy Engines over PCIe.
*/
//#include <athdefs.h>
#include <osdep.h>
#include "a_types.h"
#include "athdefs.h"
#include "osapi_linux.h"
#include "targcfg.h"
#include "adf_os_lock.h"
#include <adf_os_atomic.h> /* adf_os_atomic_read */
#include <targaddrs.h>
#include <bmi_msg.h>
#include <hif.h>
#include <htc_services.h>
#include "hif_msg_based.h"
#include "if_pci.h"
#include "copy_engine_api.h"
#include "regtable.h"
#define ATH_MODULE_NAME hif
#include <a_debug.h>
#include "hif_pci.h"
#include "vos_trace.h"
#include "vos_api.h"
#include "vos_cnss.h"
#include <vos_getBin.h>
#include "epping_main.h"
#ifdef CONFIG_PCI_MSM
#include <linux/msm_pcie.h>
#endif
#include "adf_trace.h"
/* use credit flow control over HTC */
unsigned int htc_credit_flow = 1;
int hif_pci_war1 = 0;
static DEFINE_SPINLOCK(pciwar_lock);
OSDRV_CALLBACKS HIF_osDrvcallback;
#define HIF_PCI_DEBUG ATH_DEBUG_MAKE_MODULE_MASK(0)
#ifdef IPA_UC_OFFLOAD
#define HIF_PCI_IPA_UC_ASSIGNED_CE 5
#endif /* IPA_UC_OFFLOAD */
#if defined(WLAN_DEBUG)
static ATH_DEBUG_MASK_DESCRIPTION g_HIFDebugDescription[] = {
{HIF_PCI_DEBUG,"hif_pci"},
};
ATH_DEBUG_INSTANTIATE_MODULE_VAR(hif,
"hif",
"PCIe Host Interface",
ATH_DEBUG_MASK_DEFAULTS | ATH_DEBUG_INFO, /*or with HIF_PCI_DEBUG if verbose HIF debug is required*/
ATH_DEBUG_DESCRIPTION_COUNT(g_HIFDebugDescription),
g_HIFDebugDescription);
#endif
#ifdef CONFIG_ATH_PCIE_ACCESS_DEBUG
spinlock_t pcie_access_log_lock;
unsigned int pcie_access_log_seqnum = 0;
HIF_ACCESS_LOG pcie_access_log[PCIE_ACCESS_LOG_NUM];
static void HIFTargetDumpAccessLog(void);
#endif
/*
* Host software's Copy Engine configuration.
* This table is derived from the CE_PCI TABLE, above.
*/
#ifdef BIG_ENDIAN_HOST
#define CE_ATTR_FLAGS CE_ATTR_BYTE_SWAP_DATA
#else
#define CE_ATTR_FLAGS 0
#endif
#define AGC_DUMP 1
#define CHANINFO_DUMP 2
#define BB_WATCHDOG_DUMP 3
#ifdef CONFIG_ATH_PCIE_ACCESS_DEBUG
#define PCIE_ACCESS_DUMP 4
#endif
/*
* Fix EV118783, poll to check whether a BMI response comes
* other than waiting for the interruption which may be lost.
*/
//#define BMI_RSP_POLLING
#define BMI_RSP_TO_MILLISEC 1000
/**
* enum ce_host_index: index into the host copy engine attribute
* table
* @CE_HOST_H2T_HTC_CTRL: host->target HTC control and raw streams
* @CE_HOST_T2H_HTT_HTC_CTRL: target->host HTT + HTC control
* @CE_HOST_T2H_WMI: target->host WMI
* @CE_HOST_H2T_WMI: host->target WMI
* @CE_HOST_H2T_HTT: host->target HTT
* @CE_HOST_IPA2T_HTC_CTRL: ipa_uc->target HTC control
* @CE_HOST_TARGET_HIF: Target autonomous HIF_memcpy
* @CE_HOST_DIAG: ce_diag, the Diagnostic Window
* Note: This enum is closely tied to the host_CE_config_wlan
* table below. Please update the enum if the table is updated
*/
enum ce_host_index {
CE_HOST_H2T_HTC_CTRL = 0,
CE_HOST_T2H_HTT_HTC_CTRL = 1,
CE_HOST_T2H_WMI = 2,
CE_HOST_H2T_WMI = 3,
CE_HOST_H2T_HTT = 4,
#ifndef IPA_UC_OFFLOAD
CE_HOST_UNUSED = 5,
#else
CE_HOST_IPA2T_HTC_CTRL = 5,
#endif
CE_HOST_TARGET_HIF = 6,
CE_HOST_DIAG = 7,
};
/**
* Note: This structure is closely tied to the enum above.
* Please update the enum if the table is updated
*/
static struct CE_attr host_CE_config_wlan[] =
{
{ /* CE0 */ CE_ATTR_FLAGS, 0, 16, 256, 0, NULL, }, /* host->target HTC control and raw streams */
/* could be moved to share CE3 */
{ /* CE1 */ CE_ATTR_FLAGS, 0, 0, 2048, 512, NULL, },/* target->host HTT + HTC control */
{ /* CE2 */ CE_ATTR_FLAGS, 0, 0, 2048, 32, NULL, },/* target->host WMI */
{ /* CE3 */ CE_ATTR_FLAGS, 0, 32, 2048, 0, NULL, },/* host->target WMI */
{ /* CE4 */ CE_ATTR_FLAGS | CE_ATTR_DISABLE_INTR, 0, CE_HTT_H2T_MSG_SRC_NENTRIES , 256, 0, NULL, }, /* host->target HTT */
#ifndef IPA_UC_OFFLOAD
{ /* CE5 */ CE_ATTR_FLAGS, 0, 0, 0, 0, NULL, }, /* unused */
#else
{ /* CE5 */ CE_ATTR_FLAGS | CE_ATTR_DISABLE_INTR, 0, 1024, 512, 0, NULL, }, /* ipa_uc->target HTC control */
#endif /* IPA_UC_OFFLOAD */
{ /* CE6 */ CE_ATTR_FLAGS, 0, 0, 0, 0, NULL, }, /* Target autonomous HIF_memcpy */
{ /* CE7 */ CE_ATTR_FLAGS, 0, 2, DIAG_TRANSFER_LIMIT, 2, NULL, }, /* ce_diag, the Diagnostic Window */
};
static struct CE_attr *host_CE_config = host_CE_config_wlan;
/*
* Target firmware's Copy Engine configuration.
* This table is derived from the CE_PCI TABLE, above.
* It is passed to the Target at startup for use by firmware.
*/
static struct CE_pipe_config target_CE_config_wlan[] = {
{ /* CE0 */ 0, PIPEDIR_OUT, 32, 256, CE_ATTR_FLAGS, 0, }, /* host->target HTC control and raw streams */
{ /* CE1 */ 1, PIPEDIR_IN, 32, 2048, CE_ATTR_FLAGS, 0, }, /* target->host HTT + HTC control */
{ /* CE2 */ 2, PIPEDIR_IN, 32, 2048, CE_ATTR_FLAGS, 0, }, /* target->host WMI */
{ /* CE3 */ 3, PIPEDIR_OUT, 32, 2048, CE_ATTR_FLAGS, 0, }, /* host->target WMI */
{ /* CE4 */ 4, PIPEDIR_OUT, 256, 256, CE_ATTR_FLAGS, 0, }, /* host->target HTT */
/* NB: 50% of src nentries, since tx has 2 frags */
#ifndef IPA_UC_OFFLOAD
{ /* CE5 */ 5, PIPEDIR_OUT, 32, 2048, CE_ATTR_FLAGS, 0, }, /* unused */
#else
{ /* CE5 */ 5, PIPEDIR_OUT, 1024, 64, CE_ATTR_FLAGS, 0, }, /* ipa_uc->target HTC control */
#endif /* IPA_UC_OFFLOAD */
{ /* CE6 */ 6, PIPEDIR_INOUT, 32, 4096, CE_ATTR_FLAGS, 0, },/* Reserved for target autonomous HIF_memcpy */
/* CE7 used only by Host */
};
static struct CE_pipe_config *target_CE_config = target_CE_config_wlan;
static int target_CE_config_sz = sizeof(target_CE_config_wlan);
/*
* CE config for endpoint-ping test
* EP-ping is used to verify HTC/HIF basic functionality and could be used to
* measure interface performance. Here comes some notes.
* 1. In theory, each CE could be used to test. However, due to the limitation
* of target memory EP-ping only focus on CE 1/2/3/4 which are used for
* WMI/HTT services
* 2. The EP-ping CE config does not share the same CE config with WLAN
* application since the max_size and entries requirement for EP-ping
* is different.
*/
#define EPPING_CE_FLAGS_POLL CE_ATTR_DISABLE_INTR|CE_ATTR_ENABLE_POLL|CE_ATTR_FLAGS
static struct CE_attr host_CE_config_wlan_epping_poll[] =
{
{ /* CE0 */ CE_ATTR_FLAGS, 0, 16, 256, 0, NULL, }, /* host->target HTC control and raw streams */
{ /* CE1 */ EPPING_CE_FLAGS_POLL, 0, 0, 2048, 128, NULL, }, /* target->host EP-ping */
{ /* CE2 */ EPPING_CE_FLAGS_POLL, 0, 0, 2048, 128, NULL, }, /* target->host EP-ping */
{ /* CE3 */ CE_ATTR_FLAGS, 0, 128, 2048, 0, NULL, }, /* host->target EP-ping */
{ /* CE4 */ CE_ATTR_FLAGS, 0, 128, 2048, 0, NULL, }, /* host->target EP-ping */
{ /* CE5 */ CE_ATTR_FLAGS, 0, 0, 2048, 128, NULL, }, /* EP-ping heartbeat */
{ /* CE6 */ CE_ATTR_FLAGS, 0, 0, 0, 0, NULL, }, /* unused */
{ /* CE7 */ CE_ATTR_FLAGS, 0, 2, DIAG_TRANSFER_LIMIT, 2, NULL, }, /* ce_diag, the Diagnostic Window */
};
static struct CE_attr host_CE_config_wlan_epping_irq[] =
{
{ /* CE0 */ CE_ATTR_FLAGS, 0, 16, 256, 0, NULL, }, /* host->target HTC control and raw streams */
{ /* CE1 */ CE_ATTR_FLAGS, 0, 0, 2048, 128, NULL, }, /* target->host EP-ping */
{ /* CE2 */ CE_ATTR_FLAGS, 0, 0, 2048, 128, NULL, }, /* target->host EP-ping */
{ /* CE3 */ CE_ATTR_FLAGS, 0, 128, 2048, 0, NULL, }, /* host->target EP-ping */
{ /* CE4 */ CE_ATTR_FLAGS, 0, 128, 2048, 0, NULL, }, /* host->target EP-ping */
{ /* CE5 */ CE_ATTR_FLAGS, 0, 0, 2048, 128, NULL, }, /* EP-ping heartbeat */
{ /* CE6 */ CE_ATTR_FLAGS, 0, 0, 0, 0, NULL, }, /* unused */
{ /* CE7 */ CE_ATTR_FLAGS, 0, 2, DIAG_TRANSFER_LIMIT, 2, NULL, }, /* ce_diag, the Diagnostic Window */
};
/*
* EP-ping firmware's CE configuration
*/
static struct CE_pipe_config target_CE_config_wlan_epping[] = {
{ /* CE0 */ 0, PIPEDIR_OUT, 16, 256, CE_ATTR_FLAGS, 0, }, /* host->target HTC control and raw streams */
{ /* CE1 */ 1, PIPEDIR_IN, 128, 2048, CE_ATTR_FLAGS, 0, }, /* target->host EP-ping */
{ /* CE2 */ 2, PIPEDIR_IN, 128, 2048, CE_ATTR_FLAGS, 0, }, /* target->host EP-ping */
{ /* CE3 */ 3, PIPEDIR_OUT, 128, 2048, CE_ATTR_FLAGS, 0, }, /* host->target EP-ping */
{ /* CE4 */ 4, PIPEDIR_OUT, 128, 2048, CE_ATTR_FLAGS, 0, }, /* host->target EP-ping */
{ /* CE5 */ 5, PIPEDIR_IN, 128, 2048, CE_ATTR_FLAGS, 0, }, /* EP-ping heartbeat */
{ /* CE6 */ 6, PIPEDIR_INOUT, 0, 0, CE_ATTR_FLAGS, 0, }, /* unused */
/* CE7 used only by Host */
};
int hif_completion_thread(struct HIF_CE_state *hif_state);
static int hif_post_recv_buffers(HIF_DEVICE *hif_device);
void
WAR_PCI_WRITE32(char *addr, u32 offset, u32 value)
{
if (hif_pci_war1) {
unsigned long irq_flags;
spin_lock_irqsave(&pciwar_lock, irq_flags);
(void)ioread32((void __iomem *)(addr+offset+4)); /* 3rd read prior to write */
(void)ioread32((void __iomem *)(addr+offset+4)); /* 2nd read prior to write */
(void)ioread32((void __iomem *)(addr+offset+4)); /* 1st read prior to write */
iowrite32((u32)(value), (void __iomem *)(addr+offset));
spin_unlock_irqrestore(&pciwar_lock, irq_flags);
} else {
iowrite32((u32)(value), (void __iomem *)(addr+offset));
}
}
int HIFInit(OSDRV_CALLBACKS *callbacks)
{
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
A_MEMZERO(&HIF_osDrvcallback,sizeof(HIF_osDrvcallback));
A_REGISTER_MODULE_DEBUG_INFO(hif);
HIF_osDrvcallback.deviceInsertedHandler = callbacks->deviceInsertedHandler;
HIF_osDrvcallback.deviceRemovedHandler = callbacks->deviceRemovedHandler;
HIF_osDrvcallback.deviceSuspendHandler = callbacks->deviceSuspendHandler;
HIF_osDrvcallback.deviceResumeHandler = callbacks->deviceResumeHandler;
HIF_osDrvcallback.deviceWakeupHandler = callbacks->deviceWakeupHandler;
HIF_osDrvcallback.context = callbacks->context;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return EOK;
}
int
HIFAttachHTC(HIF_DEVICE *hif_device, HTC_CALLBACKS *callbacks)
{
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
ASSERT(0);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return EOK;
}
void
HIFDetachHTC(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
A_MEMZERO(&hif_state->msg_callbacks_pending, sizeof(hif_state->msg_callbacks_pending));
A_MEMZERO(&hif_state->msg_callbacks_current, sizeof(hif_state->msg_callbacks_current));
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
}
/* Send the first nbytes bytes of the buffer */
A_STATUS
HIFSend_head(HIF_DEVICE *hif_device,
a_uint8_t pipe, unsigned int transfer_id, unsigned int nbytes, adf_nbuf_t nbuf)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct HIF_CE_pipe_info *pipe_info = &(hif_state->pipe_info[pipe]);
struct CE_handle *ce_hdl = pipe_info->ce_hdl;
int bytes = nbytes, nfrags = 0;
struct CE_sendlist sendlist;
int status;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
A_ASSERT(nbytes <= adf_nbuf_len(nbuf));
/*
* The common case involves sending multiple fragments within a
* single download (the tx descriptor and the tx frame header).
* So, optimize for the case of multiple fragments by not even
* checking whether it's necessary to use a sendlist.
* The overhead of using a sendlist for a single buffer download
* is not a big deal, since it happens rarely (for WMI messages).
*/
CE_sendlist_init(&sendlist);
do {
a_uint32_t frag_paddr;
int frag_bytes;
frag_paddr = adf_nbuf_get_frag_paddr_lo(nbuf, nfrags);
frag_bytes = adf_nbuf_get_frag_len(nbuf, nfrags);
status = CE_sendlist_buf_add(
&sendlist, frag_paddr,
frag_bytes > bytes ? bytes : frag_bytes,
adf_nbuf_get_frag_is_wordstream(nbuf, nfrags) ?
0 : CE_SEND_FLAG_SWAP_DISABLE);
if (status != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("%s: error, frag_num %d larger than the given limit\n",
__func__, nfrags));
return status;
}
bytes -= frag_bytes;
nfrags++;
} while (bytes > 0);
/* Make sure we have resources to handle this request */
adf_os_spin_lock_bh(&pipe_info->completion_freeq_lock);
if (pipe_info->num_sends_allowed < nfrags) {
adf_os_spin_unlock_bh(&pipe_info->completion_freeq_lock);
OL_ATH_HIF_PKT_ERROR_COUNT_INCR(hif_state, HIF_PIPE_NO_RESOURCE);
return A_NO_RESOURCE;
}
pipe_info->num_sends_allowed -= nfrags;
adf_os_spin_unlock_bh(&pipe_info->completion_freeq_lock);
if(adf_os_unlikely(ce_hdl == NULL)) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("%s: error CE handle is null\n", __func__));
return A_ERROR;
}
NBUF_UPDATE_TX_PKT_COUNT(nbuf, NBUF_TX_PKT_HIF);
DPTRACE(adf_dp_trace(nbuf, ADF_DP_TRACE_HIF_PACKET_PTR_RECORD,
adf_nbuf_data_addr(nbuf),
sizeof(adf_nbuf_data(nbuf)), ADF_TX));
status = CE_sendlist_send(ce_hdl, nbuf, &sendlist, transfer_id);
A_ASSERT(status == A_OK);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return status;
}
/* Send the entire buffer */
A_STATUS
HIFSend(HIF_DEVICE *hif_device, a_uint8_t pipe, adf_nbuf_t hdr_buf, adf_nbuf_t netbuf)
{
return HIFSend_head(hif_device, pipe, 0, adf_nbuf_len(netbuf), netbuf);
}
void
HIFSendCompleteCheck(HIF_DEVICE *hif_device, a_uint8_t pipe, int force)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
if (! force) {
int resources;
/*
* Decide whether to actually poll for completions, or just
* wait for a later chance.
* If there seem to be plenty of resources left, then just wait,
* since checking involves reading a CE register, which is a
* relatively expensive operation.
*/
resources = HIFGetFreeQueueNumber(hif_device, pipe);
/*
* If at least 50% of the total resources are still available,
* don't bother checking again yet.
*/
if (resources > (host_CE_config[pipe].src_nentries >> 1)) {
return;
}
}
#ifdef ATH_11AC_TXCOMPACT
CE_per_engine_servicereap(hif_state->sc, pipe);
#else
CE_per_engine_service(hif_state->sc, pipe);
#endif
}
a_uint16_t
HIFGetFreeQueueNumber(HIF_DEVICE *hif_device, a_uint8_t pipe)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct HIF_CE_pipe_info *pipe_info = &(hif_state->pipe_info[pipe]);
a_uint16_t rv;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
adf_os_spin_lock_bh(&pipe_info->completion_freeq_lock);
rv = pipe_info->num_sends_allowed;
adf_os_spin_unlock_bh(&pipe_info->completion_freeq_lock);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return rv;
}
/* Called by lower (CE) layer when a send to Target completes. */
void
HIF_PCI_CE_send_done(struct CE_handle *copyeng, void *ce_context, void *transfer_context,
CE_addr_t CE_data, unsigned int nbytes, unsigned int transfer_id,
unsigned int sw_index, unsigned int hw_index)
{
struct HIF_CE_pipe_info *pipe_info = (struct HIF_CE_pipe_info *)ce_context;
struct HIF_CE_state *hif_state = pipe_info->HIF_CE_state;
struct HIF_CE_completion_state *compl_state;
struct HIF_CE_completion_state *compl_queue_head, *compl_queue_tail; /* local queue */
unsigned int sw_idx = sw_index, hw_idx = hw_index;
compl_queue_head = compl_queue_tail = NULL;
do {
/*
* For the send completion of an item in sendlist, just increment
* num_sends_allowed. The upper layer callback will be triggered
* when last fragment is done with send.
*/
if (transfer_context == CE_SENDLIST_ITEM_CTXT) {
adf_os_spin_lock(&pipe_info->completion_freeq_lock);
pipe_info->num_sends_allowed++; /* NB: meaningful only for Sends */
adf_os_spin_unlock(&pipe_info->completion_freeq_lock);
continue;
}
adf_os_spin_lock(&pipe_info->completion_freeq_lock);
compl_state = pipe_info->completion_freeq_head;
if (!compl_state) {
adf_os_spin_unlock(&pipe_info->completion_freeq_lock);
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("Out of free buf in hif send completion list, potential hw_index corruption"
"pipe_num:%d num_send_allowed:%d pipe_info:0x%pK sw_index:%d hw_index:%d nbytes:%d\n",
pipe_info->pipe_num, pipe_info->num_sends_allowed,
pipe_info, sw_idx, hw_idx, nbytes));
ce_target_reset(hif_state->sc);
break;
}
pipe_info->completion_freeq_head = compl_state->next;
adf_os_spin_unlock(&pipe_info->completion_freeq_lock);
compl_state->next = NULL;
compl_state->send_or_recv = HIF_CE_COMPLETE_SEND;
compl_state->copyeng = copyeng;
compl_state->ce_context = ce_context;
compl_state->transfer_context = transfer_context;
compl_state->data = CE_data;
compl_state->nbytes = nbytes;
compl_state->transfer_id = transfer_id;
compl_state->flags = 0;
/* Enqueue at end of local queue */
if (compl_queue_tail) {
compl_queue_tail->next = compl_state;
} else {
compl_queue_head = compl_state;
}
compl_queue_tail = compl_state;
} while (CE_completed_send_next(copyeng, &ce_context, &transfer_context,
&CE_data, &nbytes, &transfer_id,
&sw_idx, &hw_idx) == EOK);
if (compl_queue_head == NULL) {
/*
* If only some of the items within a sendlist have completed,
* don't invoke completion processing until the entire sendlist
* has been sent.
*/
return;
}
adf_os_spin_lock(&hif_state->completion_pendingq_lock);
/* Enqueue the local completion queue on the per-device completion queue */
if (hif_state->completion_pendingq_head) {
hif_state->completion_pendingq_tail->next = compl_queue_head;
hif_state->completion_pendingq_tail = compl_queue_tail;
adf_os_spin_unlock(&hif_state->completion_pendingq_lock);
} else {
hif_state->completion_pendingq_head = compl_queue_head;
hif_state->completion_pendingq_tail = compl_queue_tail;
adf_os_spin_unlock(&hif_state->completion_pendingq_lock);
/* Alert the send completion service thread */
hif_completion_thread(hif_state);
}
}
/* Called by lower (CE) layer when data is received from the Target. */
void
HIF_PCI_CE_recv_data(struct CE_handle *copyeng, void *ce_context, void *transfer_context,
CE_addr_t CE_data, unsigned int nbytes, unsigned int transfer_id, unsigned int flags)
{
struct HIF_CE_pipe_info *pipe_info = (struct HIF_CE_pipe_info *)ce_context;
struct HIF_CE_state *hif_state = pipe_info->HIF_CE_state;
struct hif_pci_softc *sc = hif_state->sc;
struct ol_softc *scn = sc->ol_sc;
struct HIF_CE_completion_state *compl_state;
struct HIF_CE_completion_state *compl_queue_head, *compl_queue_tail; /* local queue */
compl_queue_head = compl_queue_tail = NULL;
do {
hif_pm_runtime_mark_last_busy(sc->dev);
adf_os_spin_lock(&pipe_info->completion_freeq_lock);
compl_state = pipe_info->completion_freeq_head;
if (!compl_state) {
adf_os_spin_unlock(&pipe_info->completion_freeq_lock);
ce_target_reset(sc);
break;
}
pipe_info->completion_freeq_head = compl_state->next;
adf_os_spin_unlock(&pipe_info->completion_freeq_lock);
compl_state->next = NULL;
compl_state->send_or_recv = HIF_CE_COMPLETE_RECV;
compl_state->copyeng = copyeng;
compl_state->ce_context = ce_context;
compl_state->transfer_context = transfer_context;
compl_state->data = CE_data;
compl_state->nbytes = nbytes;
compl_state->transfer_id = transfer_id;
compl_state->flags = flags;
/* Enqueue at end of local queue */
if (compl_queue_tail) {
compl_queue_tail->next = compl_state;
} else {
compl_queue_head = compl_state;
}
compl_queue_tail = compl_state;
#ifdef HTC_CRP_DEBUG
if (CE_HOST_T2H_WMI == pipe_info->pipe_num)
adf_nbuf_unmap_single(scn->adf_dev, (adf_nbuf_t)transfer_context,
ADF_OS_DMA_BIDIRECTIONAL);
else
#endif
adf_nbuf_unmap_single(scn->adf_dev, (adf_nbuf_t)transfer_context,
ADF_OS_DMA_FROM_DEVICE);
/*
* EV #112693 - [Peregrine][ES1][WB342][Win8x86][Performance] BSoD_0x133 occurred in VHT80 UDP_DL
* Break out DPC by force if number of loops in HIF_PCI_CE_recv_data reaches MAX_NUM_OF_RECEIVES to avoid spending too long time in DPC for each interrupt handling.
* Schedule another DPC to avoid data loss if we had taken force-break action before
* Apply to Windows OS only currently, Linux/MAC os can expand to their platform if necessary
*/
/* Set up force_break flag if num of receices reaches MAX_NUM_OF_RECEIVES */
sc->receive_count++;
if (adf_os_unlikely(hif_max_num_receives_reached(sc->receive_count)))
{
sc->force_break = 1;
break;
}
} while (CE_completed_recv_next(copyeng, &ce_context, &transfer_context,
&CE_data, &nbytes, &transfer_id, &flags) == EOK);
adf_os_spin_lock(&hif_state->completion_pendingq_lock);
/* Enqueue the local completion queue on the per-device completion queue */
if (hif_state->completion_pendingq_head) {
hif_state->completion_pendingq_tail->next = compl_queue_head;
hif_state->completion_pendingq_tail = compl_queue_tail;
adf_os_spin_unlock(&hif_state->completion_pendingq_lock);
} else {
hif_state->completion_pendingq_head = compl_queue_head;
hif_state->completion_pendingq_tail = compl_queue_tail;
adf_os_spin_unlock(&hif_state->completion_pendingq_lock);
/* Alert the recv completion service thread */
hif_completion_thread(hif_state);
}
}
/* TBDXXX: Set CE High Watermark; invoke txResourceAvailHandler in response */
void
HIFPostInit(HIF_DEVICE *hif_device, void *unused, MSG_BASED_HIF_CALLBACKS *callbacks)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
#ifdef CONFIG_ATH_PCIE_ACCESS_DEBUG
spin_lock_init(&pcie_access_log_lock);
#endif
/* Save callbacks for later installation */
A_MEMCPY(&hif_state->msg_callbacks_pending, callbacks, sizeof(hif_state->msg_callbacks_pending));
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
}
static void hif_pci_free_complete_state(struct HIF_CE_pipe_info *pipe_info)
{
struct HIF_CE_completion_state_list *tmp_list;
while (pipe_info->completion_space_list) {
tmp_list = pipe_info->completion_space_list;
pipe_info->completion_space_list = tmp_list->next;
vos_mem_free(tmp_list);
}
}
int
hif_completion_thread_startup(struct HIF_CE_state *hif_state)
{
struct CE_handle *ce_diag = hif_state->ce_diag;
struct hif_pci_softc *sc = hif_state->sc;
A_target_id_t targid = hif_state->targid;
int pipe_num;
//daemonize("hif_compl_thread");
adf_os_spinlock_init(&hif_state->completion_pendingq_lock);
hif_state->completion_pendingq_head = hif_state->completion_pendingq_tail = NULL;
A_TARGET_ACCESS_LIKELY(targid);
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
struct CE_attr attr;
struct HIF_CE_pipe_info *pipe_info;
int completions_needed;
pipe_info = &hif_state->pipe_info[pipe_num];
if (pipe_info->ce_hdl == ce_diag) {
continue; /* Handle Diagnostic CE specially */
}
attr = host_CE_config[pipe_num];
completions_needed = 0;
if (attr.src_nentries) { /* pipe used to send to target */
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("pipe_num:%d pipe_info:0x%pK\n",
pipe_num, pipe_info));
CE_send_cb_register(pipe_info->ce_hdl, HIF_PCI_CE_send_done, pipe_info, attr.flags & CE_ATTR_DISABLE_INTR);
completions_needed += attr.src_nentries;
pipe_info->num_sends_allowed = attr.src_nentries-1;
}
if (attr.dest_nentries) { /* pipe used to receive from target */
CE_recv_cb_register(pipe_info->ce_hdl, HIF_PCI_CE_recv_data, pipe_info, attr.flags & CE_ATTR_DISABLE_INTR);
completions_needed += attr.dest_nentries;
}
pipe_info->completion_freeq_head = pipe_info->completion_freeq_tail = NULL;
if (completions_needed > 0) {
struct HIF_CE_completion_state *compl_state;
struct HIF_CE_completion_state_list *tmp_list;
int i;
int idx;
int num_list;
int allocated_node;
int num_in_batch;
size_t len;
allocated_node = 0;
num_list = (completions_needed + HIF_CE_COMPLETE_STATE_NUM -1);
num_list /= HIF_CE_COMPLETE_STATE_NUM;
for (idx = 0; idx < num_list; idx++) {
if (completions_needed - allocated_node >=
HIF_CE_COMPLETE_STATE_NUM)
num_in_batch = HIF_CE_COMPLETE_STATE_NUM;
else
num_in_batch = completions_needed - allocated_node;
if (num_in_batch <= 0)
break;
len = num_in_batch *
sizeof(struct HIF_CE_completion_state) +
sizeof(struct HIF_CE_completion_state_list);
/* Allocate structures to track pending send/recv completions */
tmp_list =
(struct HIF_CE_completion_state_list *)vos_mem_malloc(len);
if (!tmp_list) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("ath ERROR: compl_state has no mem\n"));
hif_pci_free_complete_state(pipe_info);
return -1;
}
compl_state = (struct HIF_CE_completion_state *)
((uint8_t *)tmp_list +
sizeof(struct HIF_CE_completion_state_list));
for (i = 0; i < num_in_batch; i++) {
compl_state->send_or_recv = HIF_CE_COMPLETE_FREE;
compl_state->next = NULL;
if (pipe_info->completion_freeq_head)
pipe_info->completion_freeq_tail->next = compl_state;
else
pipe_info->completion_freeq_head = compl_state;
pipe_info->completion_freeq_tail = compl_state;
compl_state++;
allocated_node++;
}
if (pipe_info->completion_space_list == NULL) {
pipe_info->completion_space_list = tmp_list;
tmp_list->next = NULL;
} else {
tmp_list->next = pipe_info->completion_space_list;
pipe_info->completion_space_list = tmp_list;
}
}
adf_os_spinlock_init(&pipe_info->completion_freeq_lock);
}
}
A_TARGET_ACCESS_UNLIKELY(targid);
return 0;
}
void
hif_completion_thread_shutdown(struct HIF_CE_state *hif_state)
{
struct HIF_CE_completion_state *compl_state;
struct HIF_CE_pipe_info *pipe_info;
struct hif_pci_softc *sc = hif_state->sc;
int pipe_num;
/*
* Drop pending completions. These have already been
* reported by the CE layer to us but we have not yet
* passed them upstack.
*/
while ((compl_state = hif_state->completion_pendingq_head) != NULL) {
adf_nbuf_t netbuf;
netbuf = (adf_nbuf_t)compl_state->transfer_context;
adf_nbuf_free(netbuf);
hif_state->completion_pendingq_head = compl_state->next;
/*
* NB: Don't bother to place compl_state on pipe's free queue,
* because we'll free underlying memory for the free queues
* in a moment anyway.
*/
}
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
pipe_info = &hif_state->pipe_info[pipe_num];
hif_pci_free_complete_state(pipe_info);
adf_os_spinlock_destroy(&pipe_info->completion_freeq_lock);
}
//hif_state->compl_thread = NULL;
//complete_and_exit(&hif_state->compl_thread_done, 0);
}
/*
* This thread provides a context in which send/recv completions
* are handled.
*
* Note: HIF installs callback functions with the CE layer.
* Those functions are called directly (e.g. in interrupt context).
* Upper layers (e.g. HTC) have installed callbacks with HIF which
* expect to be called in a thread context. This is where that
* conversion occurs.
*
* TBDXXX: Currently we use just one thread for all pipes.
* This might be sufficient or we might need multiple threads.
*/
int
//hif_completion_thread(void *hif_dev)
hif_completion_thread(struct HIF_CE_state *hif_state)
{
MSG_BASED_HIF_CALLBACKS *msg_callbacks = &hif_state->msg_callbacks_current;
struct HIF_CE_completion_state *compl_state;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
/* Allow only one instance of the thread to execute at a time to
* prevent out of order processing of messages - this is bad for higher
* layer code
*/
if (!adf_os_atomic_dec_and_test(&hif_state->hif_thread_idle)) {
/* We were not the lucky one */
adf_os_atomic_inc(&hif_state->hif_thread_idle);
return 0;
}
/* Make sure that HTC registered call backs with the HIF are valid */
if (!msg_callbacks->fwEventHandler
|| !msg_callbacks->txCompletionHandler
|| !msg_callbacks->rxCompletionHandler) {
return 0;
}
while (atomic_read(&hif_state->fw_event_pending) > 0) {
/*
* Clear pending state before handling, in case there's
* another while we process the first.
*/
atomic_set(&hif_state->fw_event_pending, 0);
msg_callbacks->fwEventHandler(msg_callbacks->Context, A_ERROR);
}
if (hif_state->sc->ol_sc->target_status == OL_TRGET_STATUS_RESET)
return 0;
for (;;) {
struct HIF_CE_pipe_info *pipe_info;
int send_done = 0;
adf_os_spin_lock(&hif_state->completion_pendingq_lock);
if (!hif_state->completion_pendingq_head) {
/* We are atomically sure that there is no pending work */
adf_os_atomic_inc(&hif_state->hif_thread_idle);
adf_os_spin_unlock(&hif_state->completion_pendingq_lock);
break; /* All pending completions are handled */
}
/* Dequeue the first unprocessed but completed transfer */
compl_state = hif_state->completion_pendingq_head;
hif_state->completion_pendingq_head = compl_state->next;
adf_os_spin_unlock(&hif_state->completion_pendingq_lock);
pipe_info = (struct HIF_CE_pipe_info *)compl_state->ce_context;
if (compl_state->send_or_recv == HIF_CE_COMPLETE_SEND) {
msg_callbacks->txCompletionHandler(msg_callbacks->Context, compl_state->transfer_context, compl_state->transfer_id);
send_done = 1;
} else { /* compl_state->send_or_recv == HIF_CE_COMPLETE_RECV */
adf_nbuf_t netbuf;
unsigned int nbytes;
atomic_inc(&pipe_info->recv_bufs_needed);
hif_post_recv_buffers((HIF_DEVICE *)hif_state);
netbuf = (adf_nbuf_t)compl_state->transfer_context;
nbytes = compl_state->nbytes;
/*
To see the following debug output, enable the HIF_PCI_DEBUG flag in
the debug module declaration in this source file
*/
AR_DEBUG_PRINTF(HIF_PCI_DEBUG,("HIF_PCI_CE_recv_data netbuf=%pK nbytes=%d\n", netbuf, nbytes));
if (nbytes <= pipe_info->buf_sz) {
adf_nbuf_set_pktlen(netbuf, nbytes);
msg_callbacks->rxCompletionHandler(msg_callbacks->Context,
netbuf, pipe_info->pipe_num);
} else {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Invalid Rx message netbuf:%pK nbytes:%d\n",
netbuf, nbytes));
adf_nbuf_free(netbuf);
}
}
/* Recycle completion state back to the pipe it came from. */
compl_state->next = NULL;
compl_state->send_or_recv = HIF_CE_COMPLETE_FREE;
adf_os_spin_lock(&pipe_info->completion_freeq_lock);
if (pipe_info->completion_freeq_head) {
pipe_info->completion_freeq_tail->next = compl_state;
} else {
pipe_info->completion_freeq_head = compl_state;
}
pipe_info->completion_freeq_tail = compl_state;
pipe_info->num_sends_allowed += send_done;
adf_os_spin_unlock(&pipe_info->completion_freeq_lock);
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return 0;
}
/*
* Install pending msg callbacks.
*
* TBDXXX: This hack is needed because upper layers install msg callbacks
* for use with HTC before BMI is done; yet this HIF implementation
* needs to continue to use BMI msg callbacks. Really, upper layers
* should not register HTC callbacks until AFTER BMI phase.
*/
static void
hif_msg_callbacks_install(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
A_MEMCPY(&hif_state->msg_callbacks_current,
&hif_state->msg_callbacks_pending, sizeof(hif_state->msg_callbacks_pending));
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
}
int
HIFConfigureDevice(HIF_DEVICE *hif_device, HIF_DEVICE_CONFIG_OPCODE opcode,
void *config, u_int32_t configLen)
{
int status = EOK;
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
switch (opcode) {
case HIF_DEVICE_GET_OS_DEVICE:
{
HIF_DEVICE_OS_DEVICE_INFO *info = (HIF_DEVICE_OS_DEVICE_INFO *)config;
info->pOSDevice = (void *)sc->dev;
}
break;
case HIF_DEVICE_GET_MBOX_BLOCK_SIZE:
/* provide fake block sizes for mailboxes to satisfy upper layer software */
((u_int32_t *)config)[0] = 16;
((u_int32_t *)config)[1] = 16;
((u_int32_t *)config)[2] = 16;
((u_int32_t *)config)[3] = 16;
break;
case HIF_BMI_DONE:
{
printk("%s: BMI_DONE\n", __FUNCTION__); /* TBDXXX */
break;
}
default:
status = !EOK;
break;
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return status;
}
void
HIFClaimDevice(HIF_DEVICE *hif_device, void *claimedContext)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
hif_state->claimedContext = claimedContext;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
}
void
HIFReleaseDevice(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
hif_state->claimedContext = NULL;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
}
void
HIFGetDefaultPipe(HIF_DEVICE *hif_device, a_uint8_t *ULPipe, a_uint8_t *DLPipe)
{
int ul_is_polled, dl_is_polled;
(void)HIFMapServiceToPipe(
hif_device, HTC_CTRL_RSVD_SVC,
ULPipe, DLPipe,
&ul_is_polled, &dl_is_polled);
}
/* TBDXXX - temporary mapping while we have too few CE's */
int
HIFMapServiceToPipe(HIF_DEVICE *hif_device, a_uint16_t ServiceId, a_uint8_t *ULPipe, a_uint8_t *DLPipe, int *ul_is_polled, int *dl_is_polled)
{
int status = EOK;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
*dl_is_polled = 0; /* polling for received messages not supported */
switch (ServiceId) {
case HTT_DATA_MSG_SVC:
/*
* Host->target HTT gets its own pipe, so it can be polled
* while other pipes are interrupt driven.
*/
*ULPipe = 4;
/*
* Use the same target->host pipe for HTC ctrl, HTC raw streams,
* and HTT.
*/
*DLPipe = 1;
break;
case HTC_CTRL_RSVD_SVC:
case HTC_RAW_STREAMS_SVC:
/*
* Note: HTC_RAW_STREAMS_SVC is currently unused, and
* HTC_CTRL_RSVD_SVC could share the same pipe as the
* WMI services. So, if another CE is needed, change
* this to *ULPipe = 3, which frees up CE 0.
*/
//*ULPipe = 3;
*ULPipe = 0;
*DLPipe = 1;
break;
case WMI_DATA_BK_SVC:
/*
* To avoid some confusions, better to introduce new EP-ping
* service instead of using existed services. Until the main
* framework support this, keep this design.
*/
if (WLAN_IS_EPPING_ENABLED(vos_get_conparam())) {
*ULPipe = 4;
*DLPipe = 1;
break;
}
case WMI_DATA_BE_SVC:
case WMI_DATA_VI_SVC:
case WMI_DATA_VO_SVC:
case WMI_CONTROL_SVC:
*ULPipe = 3;
*DLPipe = 2;
break;
#ifdef IPA_UC_OFFLOAD
case WDI_IPA_TX_SVC:
*ULPipe = 5;
break;
#endif /* IPA_UC_OFFLOAD */
/* pipe 5 unused */
/* pipe 6 reserved */
/* pipe 7 reserved */
default:
status = !EOK;
break;
}
*ul_is_polled = (host_CE_config[*ULPipe].flags & CE_ATTR_DISABLE_INTR) != 0;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return status;
}
void HIFDumpTargetMemory(HIF_DEVICE *hif_device, void *ramdump_base,
u_int32_t address, u_int32_t size)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
A_target_id_t targid;
u_int32_t loc = address;
u_int32_t val = 0;
u_int32_t j = 0;
u8 *temp = ramdump_base;
hif_state = (struct HIF_CE_state *)hif_device;
sc = hif_state->sc;
targid = hif_state->targid;
A_TARGET_ACCESS_BEGIN(targid);
while (j < size) {
val = A_PCI_READ32(sc->mem + loc + j);
OS_MEMCPY(temp, &val, 4);
j += 4;
temp += 4;
}
A_TARGET_ACCESS_END(targid);
}
/*
* TBDXXX: Should be a function call specific to each Target-type.
* This convoluted macro converts from Target CPU Virtual Address Space to CE Address Space.
* As part of this process, we conservatively fetch the current PCIE_BAR. MOST of the time,
* this should match the upper bits of PCI space for this device; but that's not guaranteed.
*/
#define TARG_CPU_SPACE_TO_CE_SPACE(pci_addr, addr) \
(((A_PCI_READ32((pci_addr)+(SOC_CORE_BASE_ADDRESS|CORE_CTRL_ADDRESS)) & 0x7ff) << 21) \
| 0x100000 | ((addr) & 0xfffff))
/* Wait up to this many Ms for a Diagnostic Access CE operation to complete */
#define DIAG_ACCESS_CE_TIMEOUT_MS 10
/*
* Diagnostic read/write access is provided for startup/config/debug usage.
* Caller must guarantee proper alignment, when applicable, and single user
* at any moment.
*/
A_STATUS
HIFDiagReadMem(HIF_DEVICE *hif_device, A_UINT32 address, A_UINT8 *data, int nbytes)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
struct ol_softc *scn;
A_target_id_t targid;
A_STATUS status = EOK;
CE_addr_t buf;
unsigned int completed_nbytes, orig_nbytes, remaining_bytes;
unsigned int id;
unsigned int flags;
struct CE_handle *ce_diag;
CE_addr_t CE_data; /* Host buffer address in CE space */
adf_os_dma_addr_t CE_data_base = 0;
void *data_buf = NULL;
int i;
hif_state = (struct HIF_CE_state *)hif_device;
sc = hif_state->sc;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" %s\n",__FUNCTION__));
/* This code cannot handle reads to non-memory space. Redirect to the
* register read fn but preserve the multi word read capability of this fn
*/
if (address < DRAM_BASE_ADDRESS) {
if ((address & 0x3) || ((uintptr_t)data & 0x3)) {
return (-EIO);
}
while ((nbytes >= 4) &&
(A_OK == (status = HIFDiagReadAccess(hif_device, address,
(A_UINT32*)data)))) {
nbytes -= sizeof(A_UINT32);
address+= sizeof(A_UINT32);
data += sizeof(A_UINT32);
}
return status;
}
scn = sc->ol_sc;
targid = hif_state->targid;
ce_diag = hif_state->ce_diag;
A_TARGET_ACCESS_LIKELY(targid);
/*
* Allocate a temporary bounce buffer to hold caller's data
* to be DMA'ed from Target. This guarantees
* 1) 4-byte alignment
* 2) Buffer in DMA-able space
*/
orig_nbytes = nbytes;
data_buf = (A_UCHAR *)pci_alloc_consistent(scn->sc_osdev->bdev,
orig_nbytes,
&CE_data_base);
if (!data_buf) {
status = A_NO_MEMORY;
goto done;
}
adf_os_mem_set(data_buf, 0, orig_nbytes);
pci_dma_sync_single_for_device(scn->sc_osdev->bdev, CE_data_base, orig_nbytes, PCI_DMA_FROMDEVICE);
remaining_bytes = orig_nbytes;
CE_data = CE_data_base;
while (remaining_bytes) {
nbytes = min(remaining_bytes, DIAG_TRANSFER_LIMIT);
{
status = CE_recv_buf_enqueue(ce_diag, NULL, CE_data);
if (status != A_OK) {
goto done;
}
}
{ /* Request CE to send from Target(!) address to Host buffer */
/*
* The address supplied by the caller is in the
* Target CPU virtual address space.
*
* In order to use this address with the diagnostic CE,
* convert it from
* Target CPU virtual address space
* to
* CE address space
*/
A_TARGET_ACCESS_BEGIN_RET(targid);
address = TARG_CPU_SPACE_TO_CE_SPACE(sc->mem, address);
A_TARGET_ACCESS_END_RET(targid);
status = CE_send(ce_diag, NULL, (CE_addr_t)address, nbytes, 0, 0);
if (status != EOK) {
goto done;
}
}
i=0;
while (CE_completed_send_next(ce_diag, NULL, NULL, &buf,
&completed_nbytes, &id,
NULL, NULL) != A_OK) {
A_MDELAY(1);
if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
status = A_EBUSY;
goto done;
}
}
if (nbytes != completed_nbytes) {
status = A_ERROR;
goto done;
}
if (buf != (CE_addr_t)address) {
status = A_ERROR;
goto done;
}
i=0;
while (CE_completed_recv_next(ce_diag, NULL, NULL, &buf, &completed_nbytes, &id, &flags) != A_OK) {
A_MDELAY(1);
if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
status = A_EBUSY;
goto done;
}
}
if (nbytes != completed_nbytes) {
status = A_ERROR;
goto done;
}
if (buf != CE_data) {
status = A_ERROR;
goto done;
}
remaining_bytes -= nbytes;
address += nbytes;
CE_data += nbytes;
}
done:
A_TARGET_ACCESS_UNLIKELY(targid);
if (status == A_OK) {
/* Copy data from allocated DMA buf to caller's buf */
A_MEMCPY(data, data_buf, orig_nbytes);
} else {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s failure (0x%x)\n", __FUNCTION__, address));
}
if (data_buf) {
pci_free_consistent(scn->sc_osdev->bdev, orig_nbytes,
data_buf, CE_data_base);
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return status;
}
/* Read 4-byte aligned data from Target memory or register */
A_STATUS
HIFDiagReadAccess(HIF_DEVICE *hif_device, A_UINT32 address, A_UINT32 *data)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
hif_state = (struct HIF_CE_state *)hif_device;
sc = hif_state->sc;
if (address >= DRAM_BASE_ADDRESS) { /* Assume range doesn't cross this boundary */
return HIFDiagReadMem(hif_device, address, (A_UINT8 *)data, sizeof(A_UINT32));
} else {
A_target_id_t targid;
targid = hif_state->targid;
A_TARGET_ACCESS_BEGIN_RET(targid);
*data = A_TARGET_READ(targid, address);
A_TARGET_ACCESS_END_RET(targid);
return A_OK;
}
}
A_STATUS
HIFDiagWriteMem(HIF_DEVICE *hif_device, A_UINT32 address, A_UINT8 *data, int nbytes)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
struct ol_softc *scn;
A_target_id_t targid;
A_STATUS status = A_OK;
CE_addr_t buf;
unsigned int completed_nbytes, orig_nbytes, remaining_bytes;
unsigned int id;
unsigned int flags;
struct CE_handle *ce_diag;
void *data_buf = NULL;
CE_addr_t CE_data; /* Host buffer address in CE space */
adf_os_dma_addr_t CE_data_base = 0;
int i;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" %s\n",__FUNCTION__));
hif_state = (struct HIF_CE_state *)hif_device;
sc = hif_state->sc;
scn = sc->ol_sc;
targid = hif_state->targid;
ce_diag = hif_state->ce_diag;
A_TARGET_ACCESS_LIKELY(targid);
/*
* Allocate a temporary bounce buffer to hold caller's data
* to be DMA'ed to Target. This guarantees
* 1) 4-byte alignment
* 2) Buffer in DMA-able space
*/
orig_nbytes = nbytes;
data_buf = (A_UCHAR *)pci_alloc_consistent(scn->sc_osdev->bdev,
orig_nbytes,
&CE_data_base);
if (!data_buf) {
status = A_NO_MEMORY;
goto done;
}
/* Copy caller's data to allocated DMA buf */
A_MEMCPY(data_buf, data, orig_nbytes);
pci_dma_sync_single_for_device(scn->sc_osdev->bdev, CE_data_base, orig_nbytes, PCI_DMA_TODEVICE);
/*
* The address supplied by the caller is in the
* Target CPU virtual address space.
*
* In order to use this address with the diagnostic CE,
* convert it from
* Target CPU virtual address space
* to
* CE address space
*/
A_TARGET_ACCESS_BEGIN_RET(targid);
address = TARG_CPU_SPACE_TO_CE_SPACE(sc->mem, address);
A_TARGET_ACCESS_END_RET(targid);
remaining_bytes = orig_nbytes;
CE_data = CE_data_base;
while (remaining_bytes) {
nbytes = min(remaining_bytes, DIAG_TRANSFER_LIMIT);
{ /* Set up to receive directly into Target(!) address */
status = CE_recv_buf_enqueue(ce_diag, NULL, address);
if (status != A_OK) {
goto done;
}
}
{
/*
* Request CE to send caller-supplied data that
* was copied to bounce buffer to Target(!) address.
*/
status = CE_send(ce_diag, NULL, (CE_addr_t)CE_data, nbytes, 0, 0);
if (status != A_OK) {
goto done;
}
}
i=0;
while (CE_completed_send_next(ce_diag, NULL, NULL, &buf,
&completed_nbytes, &id,
NULL, NULL) != A_OK) {
A_MDELAY(1);
if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
status = A_EBUSY;
goto done;
}
}
if (nbytes != completed_nbytes) {
status = A_ERROR;
goto done;
}
if (buf != CE_data) {
status = A_ERROR;
goto done;
}
i=0;
while (CE_completed_recv_next(ce_diag, NULL, NULL, &buf, &completed_nbytes, &id, &flags) != A_OK) {
A_MDELAY(1);
if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
status = A_EBUSY;
goto done;
}
}
if (nbytes != completed_nbytes) {
status = A_ERROR;
goto done;
}
if (buf != address) {
status = A_ERROR;
goto done;
}
remaining_bytes -= nbytes;
address += nbytes;
CE_data += nbytes;
}
done:
A_TARGET_ACCESS_UNLIKELY(targid);
if (data_buf) {
pci_free_consistent(scn->sc_osdev->bdev, orig_nbytes,
data_buf, CE_data_base);
}
if (status != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s failure (0x%x)\n", __FUNCTION__, address));
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return status;
}
/* Write 4B data to Target memory or register */
A_STATUS
HIFDiagWriteAccess(HIF_DEVICE *hif_device, A_UINT32 address, A_UINT32 data)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
hif_state = (struct HIF_CE_state *)hif_device;
sc = hif_state->sc;
if (address >= DRAM_BASE_ADDRESS) { /* Assume range doesn't cross this boundary */
A_UINT32 data_buf = data;
return HIFDiagWriteMem(hif_device, address, (A_UINT8 *)&data_buf, sizeof(A_UINT32));
} else {
struct HIF_CE_state *hif_state;
A_target_id_t targid;
hif_state = (struct HIF_CE_state *)hif_device;
targid = hif_state->targid;
A_TARGET_ACCESS_BEGIN_RET(targid);
A_TARGET_WRITE(targid, address, data);
A_TARGET_ACCESS_END_RET(targid);
return A_OK;
}
}
/**
* hif_dump_pipe_debug_count() - Log error count
* @hif_device: HIF device pointer.
*
* Output the pipe error counts of each pipe to log file
*
* Return: N/A
*/
void hif_dump_pipe_debug_count(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
int pipe_num;
if (hif_device == NULL) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
"%s hif_device is NULL", __func__));
return;
}
hif_state = (struct HIF_CE_state *)hif_device;
if (hif_state == NULL) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
"%s hif_state is NULL", __func__));
return;
}
sc = hif_state->sc;
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
struct HIF_CE_pipe_info *pipe_info;
pipe_info = &hif_state->pipe_info[pipe_num];
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
"%s pipe_id = %d, recv_bufs_needed = %d, nbuf_alloc_err_count = %u, nbuf_dma_err_count = %u, nbuf_ce_enqueue_err_count = %u",
__func__, pipe_info->pipe_num,
atomic_read(&pipe_info->recv_bufs_needed),
pipe_info->nbuf_alloc_err_count,
pipe_info->nbuf_dma_err_count,
pipe_info->nbuf_ce_enqueue_err_count));
}
}
static int
hif_post_recv_buffers_for_pipe(struct HIF_CE_pipe_info *pipe_info)
{
struct CE_handle *ce_hdl;
adf_os_size_t buf_sz;
struct HIF_CE_state *hif_state = pipe_info->HIF_CE_state;
struct hif_pci_softc *sc = hif_state->sc;
struct ol_softc *scn = sc->ol_sc;
a_status_t ret;
uint32_t bufs_posted = 0;
buf_sz = pipe_info->buf_sz;
if (buf_sz == 0) {
/* Unused Copy Engine */
return 0;
}
ce_hdl = pipe_info->ce_hdl;
adf_os_spin_lock_bh(&pipe_info->recv_bufs_needed_lock);
while (atomic_read(&pipe_info->recv_bufs_needed) > 0) {
CE_addr_t CE_data; /* CE space buffer address*/
adf_nbuf_t nbuf;
int status;
atomic_dec(&pipe_info->recv_bufs_needed);
adf_os_spin_unlock_bh(&pipe_info->recv_bufs_needed_lock);
nbuf = adf_nbuf_alloc(scn->adf_dev, buf_sz, 0, 4, FALSE);
if (!nbuf) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("%s buf alloc error [%d] needed %d\n",
__func__, pipe_info->pipe_num,
atomic_read(&pipe_info->recv_bufs_needed)));
atomic_inc(&pipe_info->recv_bufs_needed);
adf_os_spin_lock_bh(&pipe_info->recv_bufs_needed_lock);
pipe_info->nbuf_alloc_err_count++;
adf_os_spin_unlock_bh(&pipe_info->recv_bufs_needed_lock);
return 1;
}
#ifdef HTC_CRP_DEBUG
#define HTC_DEBUG_PATTERN 0xF005BA11
if (CE_HOST_T2H_WMI == pipe_info->pipe_num) {
uint32_t * data;
data = (uint32_t *)adf_nbuf_data(nbuf);
*data = HTC_DEBUG_PATTERN;
*(data + 1) = HTC_DEBUG_PATTERN;
*(data + 2) = HTC_DEBUG_PATTERN;
*(data + 3) = HTC_DEBUG_PATTERN;
}
#endif
/*
* adf_nbuf_peek_header(nbuf, &data, &unused);
* CE_data = dma_map_single(dev, data, buf_sz, DMA_FROM_DEVICE);
*/
#ifdef HTC_CRP_DEBUG
if (CE_HOST_T2H_WMI == pipe_info->pipe_num)
ret = adf_nbuf_map_single(scn->adf_dev, nbuf,
ADF_OS_DMA_BIDIRECTIONAL);
else
#endif
ret = adf_nbuf_map_single(scn->adf_dev, nbuf, ADF_OS_DMA_FROM_DEVICE);
if (unlikely(ret != A_STATUS_OK)) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s mapping error\n", __func__));
adf_nbuf_free(nbuf);
atomic_inc(&pipe_info->recv_bufs_needed);
adf_os_spin_lock_bh(&pipe_info->recv_bufs_needed_lock);
pipe_info->nbuf_dma_err_count++;
adf_os_spin_unlock_bh(&pipe_info->recv_bufs_needed_lock);
return 1;
}
CE_data = adf_nbuf_get_frag_paddr_lo(nbuf, 0);
pci_dma_sync_single_for_device(scn->sc_osdev->bdev, CE_data,
buf_sz, PCI_DMA_FROMDEVICE);
status = CE_recv_buf_enqueue(ce_hdl, (void *)nbuf, CE_data);
A_ASSERT(status == EOK);
if (status != EOK) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("%s CE_recv_buf_enqueue error [%d] needed %d\n",
__func__, pipe_info->pipe_num,
atomic_read(&pipe_info->recv_bufs_needed)));
adf_nbuf_unmap_single(scn->adf_dev, nbuf, ADF_OS_DMA_FROM_DEVICE);
atomic_inc(&pipe_info->recv_bufs_needed);
adf_nbuf_free(nbuf);
adf_os_spin_lock_bh(&pipe_info->recv_bufs_needed_lock);
pipe_info->nbuf_ce_enqueue_err_count++;
adf_os_spin_unlock_bh(&pipe_info->recv_bufs_needed_lock);
return 1;
}
adf_os_spin_lock_bh(&pipe_info->recv_bufs_needed_lock);
bufs_posted++;
}
pipe_info->nbuf_alloc_err_count =
(pipe_info->nbuf_alloc_err_count > bufs_posted)?
pipe_info->nbuf_alloc_err_count - bufs_posted : 0;
pipe_info->nbuf_dma_err_count =
(pipe_info->nbuf_dma_err_count > bufs_posted)?
pipe_info->nbuf_dma_err_count - bufs_posted : 0;
pipe_info->nbuf_ce_enqueue_err_count =
(pipe_info->nbuf_ce_enqueue_err_count > bufs_posted)?
pipe_info->nbuf_ce_enqueue_err_count - bufs_posted : 0;
adf_os_spin_unlock_bh(&pipe_info->recv_bufs_needed_lock);
return 0;
}
/*
* Try to post all desired receive buffers for all pipes.
* Returns 0 if all desired buffers are posted,
* non-zero if were were unable to completely
* replenish receive buffers.
*/
static int
hif_post_recv_buffers(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
A_target_id_t targid = hif_state->targid;
int pipe_num, rv=0;
A_TARGET_ACCESS_LIKELY(targid);
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
struct HIF_CE_pipe_info *pipe_info;
pipe_info = &hif_state->pipe_info[pipe_num];
if (hif_post_recv_buffers_for_pipe(pipe_info)) {
rv = 1;
goto done;
}
}
done:
A_TARGET_ACCESS_UNLIKELY(targid);
return rv;
}
void HIFDump(HIF_DEVICE *hif_device, u_int8_t cmd_id, bool start)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
switch (cmd_id) {
case AGC_DUMP:
if (start)
priv_start_agc(sc);
else
priv_dump_agc(sc);
break;
case CHANINFO_DUMP:
if (start)
priv_start_cap_chaninfo(sc);
else
priv_dump_chaninfo(sc);
break;
case BB_WATCHDOG_DUMP:
priv_dump_bbwatchdog(sc);
break;
#ifdef CONFIG_ATH_PCIE_ACCESS_DEBUG
case PCIE_ACCESS_DUMP:
HIFTargetDumpAccessLog();
break;
#endif
default:
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("Invalid htc dump command\n"));
break;
}
}
A_STATUS
HIFStart(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
if (hif_completion_thread_startup(hif_state))
return A_ERROR;
hif_msg_callbacks_install(hif_device);
/* Post buffers once to start things off. */
(void)hif_post_recv_buffers(hif_device);
hif_state->started = TRUE;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return A_OK;
}
void
HIFGrowBuffers(hif_handle_t hif_hdl)
{
struct hif_pci_softc *sc = hif_hdl;
struct HIF_CE_state *hif_state = (struct HIF_CE_state *) sc->hif_device;
struct HIF_CE_pipe_info *pipe_info;
struct CE_attr *attr;
int pipe_num;
for (pipe_num = 0; pipe_num < sc->ce_count; pipe_num++) {
pipe_info = &hif_state->pipe_info[pipe_num];
attr = &host_CE_config[pipe_num];
if (attr->dest_nentries > 0) {
adf_os_spin_lock_bh(&pipe_info->recv_bufs_needed_lock);
atomic_set(&pipe_info->recv_bufs_needed, attr->dest_nentries-1 - initBufferCount(attr->dest_nentries -1));
adf_os_spin_unlock_bh(&pipe_info->recv_bufs_needed_lock);
if (hif_post_recv_buffers_for_pipe(pipe_info)) {
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s failed to grow\n",__FUNCTION__));
break;
}
}
}
}
void
hif_recv_buffer_cleanup_on_pipe(struct HIF_CE_pipe_info *pipe_info)
{
struct ol_softc *scn;
struct CE_handle *ce_hdl;
u_int32_t buf_sz;
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
adf_nbuf_t netbuf;
CE_addr_t CE_data;
void *per_CE_context;
buf_sz = pipe_info->buf_sz;
if (buf_sz == 0) {
/* Unused Copy Engine */
return;
}
hif_state = pipe_info->HIF_CE_state;
if (!hif_state->started) {
return;
}
sc = hif_state->sc;
scn = sc->ol_sc;
ce_hdl = pipe_info->ce_hdl;
if (scn->adf_dev == NULL) {
return;
}
while (CE_revoke_recv_next(ce_hdl, &per_CE_context, (void **)&netbuf, &CE_data) == A_OK)
{
#ifdef HTC_CRP_DEBUG
if (CE_HOST_T2H_WMI == pipe_info->pipe_num)
adf_nbuf_unmap_single(scn->adf_dev, netbuf,
ADF_OS_DMA_BIDIRECTIONAL);
else
#endif
adf_nbuf_unmap_single(scn->adf_dev, netbuf, ADF_OS_DMA_FROM_DEVICE);
adf_nbuf_free(netbuf);
}
}
void
hif_send_buffer_cleanup_on_pipe(struct HIF_CE_pipe_info *pipe_info)
{
struct CE_handle *ce_hdl;
struct HIF_CE_state *hif_state;
adf_nbuf_t netbuf;
void *per_CE_context;
CE_addr_t CE_data;
unsigned int nbytes;
unsigned int id;
u_int32_t buf_sz;
buf_sz = pipe_info->buf_sz;
if (buf_sz == 0) {
/* Unused Copy Engine */
return;
}
hif_state = pipe_info->HIF_CE_state;
if (!hif_state->started) {
return;
}
ce_hdl = pipe_info->ce_hdl;
while (CE_cancel_send_next(ce_hdl, &per_CE_context, (void **)&netbuf, &CE_data, &nbytes, &id) == A_OK)
{
if (netbuf != CE_SENDLIST_ITEM_CTXT)
{
/*
* Packets enqueued by htt_h2t_ver_req_msg() and
* htt_h2t_rx_ring_cfg_msg_ll() have already been freed in
* htt_htc_misc_pkt_pool_free() in WLANTL_Close(), so do not
* free them here again by checking whether it's the EndPoint
* which they are queued in.
*/
if (id == hif_state->sc->htc_endpoint) {
return;
}
/* Indicate the completion to higer layer to free the buffer */
if (hif_state->msg_callbacks_current.txCompletionHandler)
hif_state->msg_callbacks_current.txCompletionHandler(
hif_state->msg_callbacks_current.Context, netbuf, id);
}
}
}
/*
* Cleanup residual buffers for device shutdown:
* buffers that were enqueued for receive
* buffers that were to be sent
* Note: Buffers that had completed but which were
* not yet processed are on a completion queue. They
* are handled when the completion thread shuts down.
*/
void
hif_buffer_cleanup(struct HIF_CE_state *hif_state)
{
struct hif_pci_softc *sc = hif_state->sc;
int pipe_num;
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
struct HIF_CE_pipe_info *pipe_info;
pipe_info = &hif_state->pipe_info[pipe_num];
hif_recv_buffer_cleanup_on_pipe(pipe_info);
hif_send_buffer_cleanup_on_pipe(pipe_info);
}
}
void
HIFFlushSurpriseRemove(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
hif_buffer_cleanup(hif_state);
}
void
HIFStop(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
int pipe_num;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
if (!hif_state->started && !sc->hif_init_done) {
return; /* already stopped or stopping */
}
if (sc->hdd_startup_reinit_flag == TRUE)
return; /* If still in wlan_hdd_startup or wlan_hdd_reinit nop. */
sc->hif_init_done = FALSE;
if (hif_state->started) {
/* sync shutdown */
hif_completion_thread_shutdown(hif_state);
hif_completion_thread(hif_state);
} else {
hif_completion_thread_shutdown(hif_state);
}
/*
* At this point, asynchronous threads are stopped,
* The Target should not DMA nor interrupt, Host code may
* not initiate anything more. So we just need to clean
* up Host-side state.
*/
#if defined(CONFIG_ATH_PROCFS_DIAG_SUPPORT)
athdiag_procfs_remove();
#endif
hif_buffer_cleanup(hif_state);
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
struct HIF_CE_pipe_info *pipe_info;
pipe_info = &hif_state->pipe_info[pipe_num];
if (pipe_info->ce_hdl) {
CE_fini(pipe_info->ce_hdl);
pipe_info->ce_hdl = NULL;
pipe_info->buf_sz = 0;
}
}
adf_os_timer_cancel(&hif_state->sleep_timer);
adf_os_timer_free(&hif_state->sleep_timer);
hif_state->started = FALSE;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
}
int
hifWaitForPendingRecv(HIF_DEVICE *device)
{
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" %s\n",__FUNCTION__));
/* Nothing needed -- CE layer will notify via recv completion */
return EOK;
}
void
HIFShutDownDevice(HIF_DEVICE *hif_device)
{
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("+%s\n",__FUNCTION__));
if (hif_device) {
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
HIFStop(hif_device);
A_FREE(hif_state);
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("-%s\n",__FUNCTION__));
}
/* Track a BMI transaction that is in progress */
#ifndef BIT
#define BIT(n) (1 << (n))
#endif
typedef enum {
BMI_REQ_SEND_DONE = BIT(0), /* the bmi request is done(tx completion) */
BMI_RESP_RECV_DONE = BIT(1), /* the bmi respond is received */
} BMI_TRANSACTION_FLAGS;
struct BMI_transaction {
struct HIF_CE_state *hif_state;
adf_os_mutex_t bmi_transaction_sem;
A_UINT8 *bmi_request_host; /* Request BMI message in Host address space */
CE_addr_t bmi_request_CE; /* Request BMI message in CE address space */
u_int32_t bmi_request_length; /* Length of BMI request */
A_UINT8 *bmi_response_host; /* Response BMI message in Host address space */
CE_addr_t bmi_response_CE; /* Response BMI message in CE address space */
unsigned int bmi_response_length;/* Length of received response */
unsigned int bmi_timeout_ms;
A_UINT32 bmi_transaction_flags; /* flags for the transcation in bmi stage */
};
/*
* send/recv completion functions for BMI.
* NB: The "net_buf" parameter is actually just a straight buffer, not an sk_buff.
*/
static void
HIF_BMI_send_done(struct CE_handle *copyeng, void *ce_context, void *transfer_context,
CE_addr_t data, unsigned int nbytes, unsigned int transfer_id,
unsigned int sw_index, unsigned int hw_index)
{
struct BMI_transaction *transaction = (struct BMI_transaction *)transfer_context;
struct hif_pci_softc *sc = transaction->hif_state->sc;
#ifdef BMI_RSP_POLLING
/*
* Fix EV118783, Release a semaphore after sending
* no matter whether a response is been expecting now.
*/
adf_os_mutex_release(sc->ol_sc->adf_dev, &transaction->bmi_transaction_sem);
#else
/*
* If a response is anticipated, we'll complete the
* transaction if the response has been received.
* If no response is anticipated, complete the
* transaction now.
*/
transaction->bmi_transaction_flags |= BMI_REQ_SEND_DONE;
/* resp is't needed or has already been received, never assume resp comes later then this */
if (!transaction->bmi_response_CE ||
(transaction->bmi_transaction_flags & BMI_RESP_RECV_DONE)) {
adf_os_mutex_release(sc->ol_sc->adf_dev, &transaction->bmi_transaction_sem);
}
#endif
}
#ifndef BMI_RSP_POLLING
static void
HIF_BMI_recv_data(struct CE_handle *copyeng, void *ce_context, void *transfer_context,
CE_addr_t data, unsigned int nbytes, unsigned int transfer_id, unsigned int flags)
{
struct BMI_transaction *transaction = (struct BMI_transaction *)transfer_context;
struct hif_pci_softc *sc = transaction->hif_state->sc;
transaction->bmi_response_length = nbytes;
transaction->bmi_transaction_flags |= BMI_RESP_RECV_DONE;
/* when both send/recv are done, the sem can be released */
if (transaction->bmi_transaction_flags & BMI_REQ_SEND_DONE) {
adf_os_mutex_release(sc->ol_sc->adf_dev, &transaction->bmi_transaction_sem);
}
}
#endif
int
HIFExchangeBMIMsg(HIF_DEVICE *hif_device,
A_UINT8 *bmi_request,
u_int32_t request_length,
A_UINT8 *bmi_response,
u_int32_t *bmi_response_lengthp,
u_int32_t TimeoutMS)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
struct ol_softc *scn = sc->ol_sc;
struct HIF_CE_pipe_info *send_pipe_info = &(hif_state->pipe_info[BMI_CE_NUM_TO_TARG]);
struct CE_handle *ce_send = send_pipe_info->ce_hdl;
CE_addr_t CE_request, CE_response = 0;
A_target_id_t targid = hif_state->targid;
struct BMI_transaction *transaction = NULL;
int status = EOK;
struct HIF_CE_pipe_info *recv_pipe_info = &(hif_state->pipe_info[BMI_CE_NUM_TO_HOST]);
struct CE_handle *ce_recv = recv_pipe_info->ce_hdl;
#ifdef BMI_RSP_POLLING
int i;
CE_addr_t buf;
unsigned int completed_nbytes, id, flags;
#endif
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" %s\n",__FUNCTION__));
transaction = (struct BMI_transaction *)A_MALLOC(sizeof(*transaction));
if (unlikely(!transaction)) {
return -ENOMEM;
}
A_TARGET_ACCESS_LIKELY(targid);
/* Initialize bmi_transaction_sem to block */
adf_os_init_mutex(&transaction->bmi_transaction_sem);
adf_os_mutex_acquire(scn->adf_dev, &transaction->bmi_transaction_sem);
transaction->hif_state = hif_state;
transaction->bmi_request_host = bmi_request;
transaction->bmi_request_length = request_length;
transaction->bmi_response_length = 0;
transaction->bmi_timeout_ms = TimeoutMS;
transaction->bmi_transaction_flags = 0;
/*
* CE_request = dma_map_single(dev, (void *)bmi_request, request_length, DMA_TO_DEVICE);
*/
CE_request = scn->BMICmd_pa;
transaction->bmi_request_CE = CE_request;
if (bmi_response) {
/*
* CE_response = dma_map_single(dev, bmi_response, BMI_DATASZ_MAX, DMA_FROM_DEVICE);
*/
CE_response = scn->BMIRsp_pa;
transaction->bmi_response_host = bmi_response;
transaction->bmi_response_CE = CE_response;
/* dma_cache_sync(dev, bmi_response, BMI_DATASZ_MAX, DMA_FROM_DEVICE); */
pci_dma_sync_single_for_device(scn->sc_osdev->bdev, CE_response, BMI_DATASZ_MAX, PCI_DMA_FROMDEVICE);
CE_recv_buf_enqueue(ce_recv, transaction, transaction->bmi_response_CE);
/* NB: see HIF_BMI_recv_done */
} else {
transaction->bmi_response_host = NULL;
transaction->bmi_response_CE = 0;
}
/* dma_cache_sync(dev, bmi_request, request_length, DMA_TO_DEVICE); */
pci_dma_sync_single_for_device(scn->sc_osdev->bdev, CE_request, request_length, PCI_DMA_TODEVICE);
status = CE_send(ce_send, transaction, CE_request, request_length, -1, 0);
ASSERT(status == EOK);
/* NB: see HIF_BMI_send_done */
/* TBDXXX: handle timeout */
/* Wait for BMI request/response transaction to complete */
/* Always just wait for BMI request here if BMI_RSP_POLLING is defined */
if (adf_os_mutex_acquire_timeout(scn->adf_dev, &transaction->bmi_transaction_sem, HZ)) {
printk("%s:error, can't get bmi response\n", __func__);
status = A_EBUSY;
}
if (bmi_response) {
#ifdef BMI_RSP_POLLING
/* Fix EV118783, do not wait a semaphore for the BMI response
* since the relative interruption may be lost.
* poll the BMI response instead.
*/
i = 0;
while (CE_completed_recv_next(ce_recv, NULL, NULL, &buf, &completed_nbytes, &id, &flags) != A_OK) {
if (i++ > BMI_RSP_TO_MILLISEC) {
printk("%s:error, can't get bmi response\n", __func__);
status = A_EBUSY;
break;
}
OS_DELAY(1000);
}
if ((status == EOK) && bmi_response_lengthp) {
*bmi_response_lengthp = completed_nbytes;
}
#else
if ((status == EOK) && bmi_response_lengthp) {
*bmi_response_lengthp = transaction->bmi_response_length;
}
#endif
}
/* dma_unmap_single(dev, transaction->bmi_request_CE, request_length, DMA_TO_DEVICE); */
//bus_unmap_single(scn->sc_osdev, transaction->bmi_request_CE, request_length, BUS_DMA_TODEVICE);
if (status != EOK) {
CE_addr_t unused_buffer;
unsigned int unused_nbytes;
unsigned int unused_id;
CE_cancel_send_next(ce_send, NULL, NULL, &unused_buffer, &unused_nbytes, &unused_id);
}
A_TARGET_ACCESS_UNLIKELY(targid);
A_FREE(transaction);
return status;
}
/* CE_PCI TABLE */
/*
* NOTE: the table below is out of date, though still a useful reference.
* Refer to target_service_to_CE_map and HIFMapServiceToPipe for the actual
* mapping of HTC services to HIF pipes.
*/
/*
* This authoritative table defines Copy Engine configuration and the mapping
* of services/endpoints to CEs. A subset of this information is passed to
* the Target during startup as a prerequisite to entering BMI phase.
* See:
* target_service_to_CE_map - Target-side mapping
* HIFMapServiceToPipe - Host-side mapping
* target_CE_config - Target-side configuration
* host_CE_config - Host-side configuration
=============================================================================
Purpose | Service / Endpoint | CE | Dire | Xfer | Xfer
| | | ctio | Size | Frequency
| | | n | |
=============================================================================
tx | HTT_DATA (downlink) | CE 0 | h->t | medium - | very frequent
descriptor | | | | O(100B) | and regular
download | | | | |
-----------------------------------------------------------------------------
rx | HTT_DATA (uplink) | CE 1 | t->h | small - | frequent and
indication | | | | O(10B) | regular
upload | | | | |
-----------------------------------------------------------------------------
MSDU | DATA_BK (uplink) | CE 2 | t->h | large - | rare
upload | | | | O(1000B) | (frequent
e.g. noise | | | | | during IP1.0
packets | | | | | testing)
-----------------------------------------------------------------------------
MSDU | DATA_BK (downlink) | CE 3 | h->t | large - | very rare
download | | | | O(1000B) | (frequent
e.g. | | | | | during IP1.0
misdirecte | | | | | testing)
d EAPOL | | | | |
packets | | | | |
-----------------------------------------------------------------------------
n/a | DATA_BE, DATA_VI | CE 2 | t->h | | never(?)
| DATA_VO (uplink) | | | |
-----------------------------------------------------------------------------
n/a | DATA_BE, DATA_VI | CE 3 | h->t | | never(?)
| DATA_VO (downlink) | | | |
-----------------------------------------------------------------------------
WMI events | WMI_CONTROL (uplink) | CE 4 | t->h | medium - | infrequent
| | | | O(100B) |
-----------------------------------------------------------------------------
WMI | WMI_CONTROL | CE 5 | h->t | medium - | infrequent
messages | (downlink) | | | O(100B) |
| | | | |
-----------------------------------------------------------------------------
n/a | HTC_CTRL_RSVD, | CE 1 | t->h | | never(?)
| HTC_RAW_STREAMS | | | |
| (uplink) | | | |
-----------------------------------------------------------------------------
n/a | HTC_CTRL_RSVD, | CE 0 | h->t | | never(?)
| HTC_RAW_STREAMS | | | |
| (downlink) | | | |
-----------------------------------------------------------------------------
diag | none (raw CE) | CE 7 | t<>h | 4 | Diag Window
| | | | | infrequent
=============================================================================
*/
/*
* Map from service/endpoint to Copy Engine.
* This table is derived from the CE_PCI TABLE, above.
* It is passed to the Target at startup for use by firmware.
*/
static struct service_to_pipe target_service_to_CE_map_wlan[] = {
{
WMI_DATA_VO_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
3,
},
{
WMI_DATA_VO_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
2,
},
{
WMI_DATA_BK_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
3,
},
{
WMI_DATA_BK_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
2,
},
{
WMI_DATA_BE_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
3,
},
{
WMI_DATA_BE_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
2,
},
{
WMI_DATA_VI_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
3,
},
{
WMI_DATA_VI_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
2,
},
{
WMI_CONTROL_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
3,
},
{
WMI_CONTROL_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
2,
},
{
HTC_CTRL_RSVD_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
0, /* could be moved to 3 (share with WMI) */
},
{
HTC_CTRL_RSVD_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
1,
},
{
HTC_RAW_STREAMS_SVC, /* not currently used */
PIPEDIR_OUT, /* out = UL = host -> target */
0,
},
{
HTC_RAW_STREAMS_SVC, /* not currently used */
PIPEDIR_IN, /* in = DL = target -> host */
1,
},
{
HTT_DATA_MSG_SVC,
PIPEDIR_OUT, /* out = UL = host -> target */
4,
},
{
HTT_DATA_MSG_SVC,
PIPEDIR_IN, /* in = DL = target -> host */
1,
},
#ifdef IPA_UC_OFFLOAD
{
WDI_IPA_TX_SVC,
PIPEDIR_OUT, /* in = DL = target -> host */
5,
},
#endif /* IPA_UC_OFFLOAD */
/* (Additions here) */
{ /* Must be last */
0,
0,
0,
},
};
static struct service_to_pipe *target_service_to_CE_map = target_service_to_CE_map_wlan;
static int target_service_to_CE_map_sz = sizeof(target_service_to_CE_map_wlan);
static struct service_to_pipe target_service_to_CE_map_wlan_epping[] = {
{ WMI_DATA_VO_SVC, PIPEDIR_OUT, 3, }, /* out = UL = host -> target */
{ WMI_DATA_VO_SVC, PIPEDIR_IN, 2, }, /* in = DL = target -> host */
{ WMI_DATA_BK_SVC, PIPEDIR_OUT, 4, }, /* out = UL = host -> target */
{ WMI_DATA_BK_SVC, PIPEDIR_IN, 1, }, /* in = DL = target -> host */
{ WMI_DATA_BE_SVC, PIPEDIR_OUT, 3, }, /* out = UL = host -> target */
{ WMI_DATA_BE_SVC, PIPEDIR_IN, 2, }, /* in = DL = target -> host */
{ WMI_DATA_VI_SVC, PIPEDIR_OUT, 3, }, /* out = UL = host -> target */
{ WMI_DATA_VI_SVC, PIPEDIR_IN, 2, }, /* in = DL = target -> host */
{ WMI_CONTROL_SVC, PIPEDIR_OUT, 3, }, /* out = UL = host -> target */
{ WMI_CONTROL_SVC, PIPEDIR_IN, 2, }, /* in = DL = target -> host */
{ HTC_CTRL_RSVD_SVC, PIPEDIR_OUT, 0, }, /* out = UL = host -> target */
{ HTC_CTRL_RSVD_SVC, PIPEDIR_IN, 1, }, /* in = DL = target -> host */
{ HTC_RAW_STREAMS_SVC, PIPEDIR_OUT, 0, }, /* out = UL = host -> target */
{ HTC_RAW_STREAMS_SVC, PIPEDIR_IN, 1, }, /* in = DL = target -> host */
{ HTT_DATA_MSG_SVC, PIPEDIR_OUT, 4, }, /* out = UL = host -> target */
{ HTT_DATA_MSG_SVC, PIPEDIR_IN, 1, }, /* in = DL = target -> host */
{ 0, 0, 0, }, /* Must be last */
};
/*
* Send an interrupt to the device to wake up the Target CPU
* so it has an opportunity to notice any changed state.
*/
void
HIF_wake_target_cpu(struct hif_pci_softc *sc)
{
A_STATUS rv;
A_UINT32 core_ctrl;
rv = HIFDiagReadAccess(sc->hif_device, SOC_CORE_BASE_ADDRESS|CORE_CTRL_ADDRESS, &core_ctrl);
ASSERT(rv == A_OK);
core_ctrl |= CORE_CTRL_CPU_INTR_MASK; /* A_INUM_FIRMWARE interrupt to Target CPU */
rv = HIFDiagWriteAccess(sc->hif_device, SOC_CORE_BASE_ADDRESS|CORE_CTRL_ADDRESS, core_ctrl);
ASSERT(rv == A_OK);
}
#define HIF_MIN_SLEEP_INACTIVITY_TIME_MS 50
#define HIF_SLEEP_INACTIVITY_TIMER_PERIOD_MS 60
static void
HIF_sleep_entry(void *arg)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)arg;
A_target_id_t pci_addr = TARGID_TO_PCI_ADDR(hif_state->targid);
struct hif_pci_softc *sc = hif_state->sc;
u_int32_t idle_ms;
if (vos_is_unload_in_progress())
return;
if (sc->recovery)
return;
adf_os_spin_lock_irqsave(&hif_state->keep_awake_lock);
if (hif_state->verified_awake == FALSE) {
idle_ms = adf_os_ticks_to_msecs(adf_os_ticks()
- hif_state->sleep_ticks);
if (idle_ms >= HIF_MIN_SLEEP_INACTIVITY_TIME_MS) {
if (!adf_os_atomic_read(&sc->pci_link_suspended)) {
A_PCI_WRITE32(pci_addr + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
hif_state->fake_sleep = FALSE;
}
} else {
adf_os_timer_cancel(&hif_state->sleep_timer);
adf_os_timer_start(&hif_state->sleep_timer,
HIF_SLEEP_INACTIVITY_TIMER_PERIOD_MS);
}
} else {
adf_os_timer_cancel(&hif_state->sleep_timer);
adf_os_timer_start(&hif_state->sleep_timer,
HIF_SLEEP_INACTIVITY_TIMER_PERIOD_MS);
}
adf_os_spin_unlock_irqrestore(&hif_state->keep_awake_lock);
}
void
HIFCancelDeferredTargetSleep(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
A_target_id_t pci_addr = TARGID_TO_PCI_ADDR(hif_state->targid);
struct hif_pci_softc *sc = hif_state->sc;
adf_os_spin_lock_irqsave(&hif_state->keep_awake_lock);
/*
* If the deferred sleep timer is running cancel it
* and put the soc into sleep.
*/
if (hif_state->fake_sleep == TRUE) {
adf_os_timer_cancel(&hif_state->sleep_timer);
if (hif_state->verified_awake == FALSE) {
A_PCI_WRITE32(pci_addr + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
}
hif_state->fake_sleep = FALSE;
}
adf_os_spin_unlock_irqrestore(&hif_state->keep_awake_lock);
}
/*
* Called from PCI layer whenever a new PCI device is probed.
* Initializes per-device HIF state and notifies the main
* driver that a new HIF device is present.
*/
int
HIF_PCIDeviceProbed(hif_handle_t hif_hdl)
{
struct HIF_CE_state *hif_state;
struct HIF_CE_pipe_info *pipe_info;
int pipe_num;
A_STATUS rv;
struct hif_pci_softc *sc = hif_hdl;
struct ol_softc *scn = sc->ol_sc;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n",__FUNCTION__));
/* if epping is enabled we need to use the epping configuration. */
if (WLAN_IS_EPPING_ENABLED(vos_get_conparam())) {
if (WLAN_IS_EPPING_IRQ(vos_get_conparam()))
host_CE_config = host_CE_config_wlan_epping_irq;
else
host_CE_config = host_CE_config_wlan_epping_poll;
target_CE_config = target_CE_config_wlan_epping;
target_CE_config_sz = sizeof(target_CE_config_wlan_epping);
target_service_to_CE_map = target_service_to_CE_map_wlan_epping;
target_service_to_CE_map_sz = sizeof(target_service_to_CE_map_wlan_epping);
}
hif_state = (struct HIF_CE_state *)A_MALLOC(sizeof(*hif_state));
if (!hif_state) {
return -ENOMEM;
}
A_MEMZERO(hif_state, sizeof(*hif_state));
sc->hif_device = (HIF_DEVICE *)hif_state;
hif_state->sc = sc;
adf_os_spinlock_init(&hif_state->keep_awake_lock);
adf_os_spinlock_init(&hif_state->suspend_lock);
adf_os_atomic_init(&hif_state->hif_thread_idle);
adf_os_atomic_inc(&hif_state->hif_thread_idle);
hif_state->keep_awake_count = 0;
hif_state->fake_sleep = FALSE;
hif_state->sleep_ticks = 0;
adf_os_timer_init(NULL, &hif_state->sleep_timer,
HIF_sleep_entry, (void *)hif_state,
ADF_NON_DEFERRABLE_TIMER);
hif_state->fw_indicator_address = FW_INDICATOR_ADDRESS;
hif_state->targid = A_TARGET_ID(sc->hif_device);
#if CONFIG_ATH_PCIE_MAX_PERF || CONFIG_ATH_PCIE_AWAKE_WHILE_DRIVER_LOAD
/* Force AWAKE forever/till the driver is loaded */
if (HIFTargetSleepStateAdjust(hif_state->targid, FALSE, TRUE) < 0)
return -EACCES;
#endif
A_TARGET_ACCESS_LIKELY(hif_state->targid); /* During CE initializtion */
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
struct CE_attr *attr;
pipe_info = &hif_state->pipe_info[pipe_num];
pipe_info->pipe_num = pipe_num;
pipe_info->HIF_CE_state = hif_state;
attr = &host_CE_config[pipe_num];
pipe_info->ce_hdl = CE_init(sc, pipe_num, attr);
ASSERT(pipe_info->ce_hdl != NULL);
if (pipe_num == sc->ce_count-1) {
/* Reserve the ultimate CE for Diagnostic Window support */
hif_state->ce_diag = hif_state->pipe_info[sc->ce_count-1].ce_hdl;
continue;
}
pipe_info->buf_sz = (adf_os_size_t)(attr->src_sz_max);
adf_os_spinlock_init(&pipe_info->recv_bufs_needed_lock);
if (attr->dest_nentries > 0) {
atomic_set(&pipe_info->recv_bufs_needed, initBufferCount(attr->dest_nentries-1));
} else {
atomic_set(&pipe_info->recv_bufs_needed, 0);
}
}
#if defined(CONFIG_ATH_PROCFS_DIAG_SUPPORT)
if (athdiag_procfs_init(sc) != 0) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("athdiag_procfs_init failed\n"));
return A_ERROR;
}
#endif
/*
* Initially, establish CE completion handlers for use with BMI.
* These are overwritten with generic handlers after we exit BMI phase.
*/
pipe_info = &hif_state->pipe_info[BMI_CE_NUM_TO_TARG];
CE_send_cb_register(pipe_info->ce_hdl, HIF_BMI_send_done, pipe_info, 0);
#ifndef BMI_RSP_POLLING
pipe_info = &hif_state->pipe_info[BMI_CE_NUM_TO_HOST];
CE_recv_cb_register(pipe_info->ce_hdl, HIF_BMI_recv_data, pipe_info, 0);
#endif
{ /* Download to Target the CE Configuration and the service-to-CE map */
A_UINT32 interconnect_targ_addr = host_interest_item_address(scn->target_type, offsetof(struct host_interest_s, hi_interconnect_state));
A_UINT32 pcie_state_targ_addr = 0;
A_UINT32 pipe_cfg_targ_addr = 0;
A_UINT32 svc_to_pipe_map = 0;
A_UINT32 pcie_config_flags = 0;
/* Supply Target-side CE configuration */
rv = HIFDiagReadAccess(sc->hif_device, interconnect_targ_addr, &pcie_state_targ_addr);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get pcie state addr (%d)\n", rv));
goto done;
}
if (pcie_state_targ_addr == 0) {
rv = A_ERROR;
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed pcie state addr is 0\n"));
goto done;
}
rv = HIFDiagReadAccess(sc->hif_device,
pcie_state_targ_addr+offsetof(struct pcie_state_s, pipe_cfg_addr),
&pipe_cfg_targ_addr);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get pipe cfg addr (%d)\n", rv));
goto done;
}
if (pipe_cfg_targ_addr == 0) {
rv = A_ERROR;
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed pipe cfg addr is 0\n"));
goto done;
}
rv = HIFDiagWriteMem(sc->hif_device, pipe_cfg_targ_addr, (A_UINT8 *)target_CE_config, target_CE_config_sz);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed write pipe cfg (%d)\n", rv));
goto done;
}
rv = HIFDiagReadAccess(sc->hif_device,
pcie_state_targ_addr+offsetof(struct pcie_state_s, svc_to_pipe_map),
&svc_to_pipe_map);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get svc/pipe map (%d)\n", rv));
goto done;
}
if (svc_to_pipe_map == 0) {
rv = A_ERROR;
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed svc_to_pipe map is 0\n"));
goto done;
}
rv = HIFDiagWriteMem(sc->hif_device,
svc_to_pipe_map,
(A_UINT8 *)target_service_to_CE_map,
target_service_to_CE_map_sz);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed write svc/pipe map (%d)\n", rv));
goto done;
}
rv = HIFDiagReadAccess(sc->hif_device,
pcie_state_targ_addr+offsetof(struct pcie_state_s, config_flags),
&pcie_config_flags);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get pcie config_flags (%d)\n", rv));
goto done;
}
#if (CONFIG_PCIE_ENABLE_L1_CLOCK_GATE)
pcie_config_flags |= PCIE_CONFIG_FLAG_ENABLE_L1;
#else
pcie_config_flags &= ~PCIE_CONFIG_FLAG_ENABLE_L1;
#endif /* CONFIG_PCIE_ENABLE_L1_CLOCK_GATE */
pcie_config_flags |= PCIE_CONFIG_FLAG_CLK_SWITCH_WAIT;
#if (CONFIG_PCIE_ENABLE_AXI_CLK_GATE)
pcie_config_flags |= PCIE_CONFIG_FLAG_AXI_CLK_GATE;
#endif
rv = HIFDiagWriteMem(sc->hif_device,
pcie_state_targ_addr+offsetof(struct pcie_state_s, config_flags),
(A_UINT8 *)&pcie_config_flags,
sizeof(pcie_config_flags));
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed write pcie config_flags (%d)\n", rv));
goto done;
}
}
{ /* configure early allocation */
A_UINT32 ealloc_value;
A_UINT32 ealloc_targ_addr = host_interest_item_address(scn->target_type, offsetof(struct host_interest_s, hi_early_alloc));
rv = HIFDiagReadAccess(sc->hif_device, ealloc_targ_addr, &ealloc_value);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get early alloc val (%d)\n", rv));
goto done;
}
/* 1 bank is switched to IRAM, except ROME 1.0 */
ealloc_value |= ((HI_EARLY_ALLOC_MAGIC << HI_EARLY_ALLOC_MAGIC_SHIFT) & HI_EARLY_ALLOC_MAGIC_MASK);
{
A_UINT8 banks_switched = 1;
A_UINT32 chip_id;
rv = HIFDiagReadAccess(sc->hif_device, CHIP_ID_ADDRESS | RTC_SOC_BASE_ADDRESS, &chip_id);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get chip id val (%d)\n", rv));
goto done;
}
if (CHIP_ID_VERSION_GET(chip_id) == 0xD ||
CHIP_ID_VERSION_GET(chip_id) == 0xF) {
scn->target_revision = CHIP_ID_REVISION_GET(chip_id);
switch(CHIP_ID_REVISION_GET(chip_id)) {
case 0x2: /* ROME 1.3 */
/* 2 banks are switched to IRAM */
banks_switched = 2;
break;
case 0x4: /* ROME 2.1 */
case 0x5: /* ROME 2.2 */
banks_switched = 6;
break;
case 0x8: /* ROME 3.0 */
case 0x9: /* ROME 3.1 */
case 0xA: /* ROME 3.2 */
case 0xD: /* Naples */
banks_switched = 9;
break;
case 0x0: /* ROME 1.0 */
case 0x1: /* ROME 1.1 */
default:
/* 3 banks are switched to IRAM */
banks_switched = 3;
break;
}
}
ealloc_value |= ((banks_switched << HI_EARLY_ALLOC_IRAM_BANKS_SHIFT) & HI_EARLY_ALLOC_IRAM_BANKS_MASK);
}
rv = HIFDiagWriteAccess(sc->hif_device, ealloc_targ_addr, ealloc_value);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed set early alloc val (%d)\n", rv));
goto done;
}
}
{ /* Tell Target to proceed with initialization */
A_UINT32 flag2_value;
A_UINT32 flag2_targ_addr = host_interest_item_address(scn->target_type, offsetof(struct host_interest_s, hi_option_flag2));
rv = HIFDiagReadAccess(sc->hif_device, flag2_targ_addr, &flag2_value);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed get option val (%d)\n", rv));
goto done;
}
flag2_value |= HI_OPTION_EARLY_CFG_DONE;
rv = HIFDiagWriteAccess(sc->hif_device, flag2_targ_addr, flag2_value);
if (rv != A_OK) {
AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("ath: HIF_PCIDeviceProbed set option val (%d)\n", rv));
goto done;
}
HIF_wake_target_cpu(sc);
}
done:
A_TARGET_ACCESS_UNLIKELY(hif_state->targid);
if (rv == A_OK) {
} else {
/* Failure, so clean up */
for (pipe_num=0; pipe_num < sc->ce_count; pipe_num++) {
pipe_info = &hif_state->pipe_info[pipe_num];
if (pipe_info->ce_hdl) {
CE_fini(pipe_info->ce_hdl);
pipe_info->ce_hdl = NULL;
pipe_info->buf_sz = 0;
}
}
adf_os_timer_cancel(&hif_state->sleep_timer);
adf_os_timer_free(&hif_state->sleep_timer);
A_FREE(hif_state);
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n",__FUNCTION__));
return (rv != A_OK);
}
/*
* The "ID" returned here is an opaque cookie used for
* A_TARGET_READ and A_TARGET_WRITE -- low-overhead APIs
* appropriate for PCIe.
*/
A_target_id_t
HIFGetTargetId(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
return(TARGID(sc));
}
extern void HIFdebug(void);
#ifdef CONFIG_PCI_MSM
static inline void hif_msm_pcie_debug_info(struct hif_pci_softc *sc)
{
msm_pcie_debug_info(sc->pdev, 13, 1, 0, 0, 0);
msm_pcie_debug_info(sc->pdev, 13, 2, 0, 0, 0);
}
#else
static inline void hif_msm_pcie_debug_info(struct hif_pci_softc *sc) {};
#endif
/*
* For now, we use simple on-demand sleep/wake.
* Some possible improvements:
* -Use the Host-destined A_INUM_PCIE_AWAKE interrupt rather than spin/delay
* (or perhaps spin/delay for a short while, then convert to sleep/interrupt)
* Careful, though, these functions may be used by interrupt handlers ("atomic")
* -Don't use host_reg_table for this code; instead use values directly
* -Use a separate timer to track activity and allow Target to sleep only
* if it hasn't done anything for a while; may even want to delay some
* processing for a short while in order to "batch" (e.g.) transmit
* requests with completion processing into "windows of up time". Costs
* some performance, but improves power utilization.
* -On some platforms, it might be possible to eliminate explicit
* sleep/wakeup. Instead, take a chance that each access works OK. If not,
* recover from the failure by forcing the Target awake.
* -Change keep_awake_count to an atomic_t in order to avoid spin lock
* overhead in some cases. Perhaps this makes more sense when
* CONFIG_ATH_PCIE_ACCESS_LIKELY is used and less sense when LIKELY is
* disabled.
* -It is possible to compile this code out and simply force the Target
* to remain awake. That would yield optimal performance at the cost of
* increased power. See CONFIG_ATH_PCIE_MAX_PERF.
*
* Note: parameter wait_for_it has meaning only when waking (when sleep_ok==0).
*/
int
HIFTargetSleepStateAdjust(A_target_id_t targid,
A_BOOL sleep_ok,
A_BOOL wait_for_it)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)TARGID_TO_HIF(targid);
A_target_id_t pci_addr = TARGID_TO_PCI_ADDR(targid);
static int max_delay;
static int debug = 0;
struct hif_pci_softc *sc = hif_state->sc;
if (sc->recovery)
return -EACCES;
if (adf_os_atomic_read(&sc->pci_link_suspended)) {
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"invalid access, PCIe link is suspended");
debug = 1;
VOS_ASSERT(0);
return -EACCES;
}
if(debug) {
wait_for_it = TRUE;
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"doing debug for invalid access, PCIe link is suspended");
VOS_ASSERT(0);
}
if (sleep_ok) {
adf_os_spin_lock_irqsave(&hif_state->keep_awake_lock);
hif_state->keep_awake_count--;
if (hif_state->keep_awake_count == 0) {
/* Allow sleep */
hif_state->verified_awake = FALSE;
hif_state->sleep_ticks = adf_os_ticks();
}
if (hif_state->fake_sleep == FALSE) {
/* Set the Fake Sleep */
hif_state->fake_sleep = TRUE;
/* Start the Sleep Timer */
adf_os_timer_cancel(&hif_state->sleep_timer);
adf_os_timer_start(&hif_state->sleep_timer,
HIF_SLEEP_INACTIVITY_TIMER_PERIOD_MS);
}
adf_os_spin_unlock_irqrestore(&hif_state->keep_awake_lock);
} else {
adf_os_spin_lock_irqsave(&hif_state->keep_awake_lock);
if (hif_state->fake_sleep) {
hif_state->verified_awake = TRUE;
} else {
if (hif_state->keep_awake_count == 0) {
/* Force AWAKE */
A_PCI_WRITE32(pci_addr + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
}
}
hif_state->keep_awake_count++;
adf_os_spin_unlock_irqrestore(&hif_state->keep_awake_lock);
if (wait_for_it && !hif_state->verified_awake) {
#define PCIE_WAKE_TIMEOUT 8000 /* 8Ms */
int tot_delay = 0;
int curr_delay = 5;
for (;;) {
if (hif_pci_targ_is_awake(sc, pci_addr)) {
hif_state->verified_awake = TRUE;
break;
} else if (!hif_pci_targ_is_present(targid, pci_addr)) {
break;
}
//ASSERT(tot_delay <= PCIE_WAKE_TIMEOUT);
if (tot_delay > PCIE_WAKE_TIMEOUT)
{
u_int16_t val;
u_int32_t bar;
printk("%s: keep_awake_count = %d\n", __func__,
hif_state->keep_awake_count);
pci_read_config_word(sc->pdev, PCI_VENDOR_ID, &val);
printk("%s: PCI Vendor ID = 0x%04x\n", __func__, val);
pci_read_config_word(sc->pdev, PCI_DEVICE_ID, &val);
printk("%s: PCI Device ID = 0x%04x\n", __func__, val);
pci_read_config_word(sc->pdev, PCI_COMMAND, &val);
printk("%s: PCI Command = 0x%04x\n", __func__, val);
pci_read_config_word(sc->pdev, PCI_STATUS, &val);
printk("%s: PCI Status = 0x%04x\n", __func__, val);
pci_read_config_dword(sc->pdev, PCI_BASE_ADDRESS_0, &bar);
printk("%s: PCI BAR 0 = 0x%08x\n", __func__, bar);
printk("%s: PCIE_SOC_WAKE_ADDRESS = 0x%08x, RTC_STATE_ADDRESS = 0x%08x\n",
__func__,
A_PCI_READ32(pci_addr + PCIE_LOCAL_BASE_ADDRESS
+ PCIE_SOC_WAKE_ADDRESS),
A_PCI_READ32(pci_addr + PCIE_LOCAL_BASE_ADDRESS
+ RTC_STATE_ADDRESS));
printk("%s:error, can't wakeup target\n", __func__);
hif_msm_pcie_debug_info(sc);
if (!vos_is_logp_in_progress(VOS_MODULE_ID_VOSS, NULL)) {
sc->recovery = true;
vos_set_logp_in_progress(VOS_MODULE_ID_VOSS, TRUE);
if (!sc->ol_sc->enable_self_recovery)
vos_device_crashed(sc->dev);
else
vos_wlan_pci_link_down();
} else {
adf_os_print("%s- %d: SSR is in progress!!!!\n",
__func__, __LINE__);
}
return -EACCES;
}
OS_DELAY(curr_delay);
tot_delay += curr_delay;
if (curr_delay < 50) {
curr_delay += 5;
}
}
/*
* NB: If Target has to come out of Deep Sleep,
* this may take a few Msecs. Typically, though
* this delay should be <30us.
*/
if (tot_delay > max_delay) {
max_delay = tot_delay;
}
}
}
if(debug && hif_state->verified_awake) {
debug = 0;
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"%s: INTR_ENABLE_REG = 0x%08x, INTR_CAUSE_REG = 0x%08x, "
"CPU_INTR_REG = 0x%08x, INTR_CLR_REG = 0x%08x, "
"CE_INTERRUPT_SUMMARY_REG = 0x%08x", __func__,
A_PCI_READ32(sc->mem + SOC_CORE_BASE_ADDRESS +
PCIE_INTR_ENABLE_ADDRESS),
A_PCI_READ32(sc->mem + SOC_CORE_BASE_ADDRESS +
PCIE_INTR_CAUSE_ADDRESS),
A_PCI_READ32(sc->mem + SOC_CORE_BASE_ADDRESS +
CPU_INTR_ADDRESS),
A_PCI_READ32(sc->mem + SOC_CORE_BASE_ADDRESS +
PCIE_INTR_CLR_ADDRESS),
A_PCI_READ32(sc->mem + CE_WRAPPER_BASE_ADDRESS +
CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS));
}
return EOK;
}
void
HIFSetTargetSleep(HIF_DEVICE *hif_device, A_BOOL sleep_ok, A_BOOL wait_for_it)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
HIFTargetSleepStateAdjust(hif_state->targid, sleep_ok, wait_for_it);
}
A_BOOL
HIFTargetForcedAwake(A_target_id_t targid)
{
A_target_id_t pci_addr = TARGID_TO_PCI_ADDR(targid);
A_BOOL awake;
A_BOOL pcie_forced_awake;
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)TARGID_TO_HIF(targid);
struct hif_pci_softc *sc = hif_state->sc;
awake = hif_pci_targ_is_awake(sc, pci_addr);
pcie_forced_awake =
!!(A_PCI_READ32(pci_addr + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS) & PCIE_SOC_WAKE_V_MASK);
return (awake && pcie_forced_awake);
}
#ifdef CONFIG_ATH_PCIE_ACCESS_DEBUG
A_UINT32
HIFTargetReadChecked(A_target_id_t targid, A_UINT32 offset)
{
A_UINT32 value;
void *addr;
if (!A_TARGET_ACCESS_OK(targid)) {
HIFdebug();
}
addr = TARGID_TO_PCI_ADDR(targid)+offset;
value = A_PCI_READ32(addr);
{
unsigned long irq_flags;
int idx = pcie_access_log_seqnum % PCIE_ACCESS_LOG_NUM;
spin_lock_irqsave(&pcie_access_log_lock, irq_flags);
pcie_access_log[idx].seqnum = pcie_access_log_seqnum;
pcie_access_log[idx].is_write = FALSE;
pcie_access_log[idx].addr = addr;
pcie_access_log[idx].value = value;
pcie_access_log_seqnum++;
spin_unlock_irqrestore(&pcie_access_log_lock, irq_flags);
}
return value;
}
void
HIFTargetWriteChecked(A_target_id_t targid, A_UINT32 offset, A_UINT32 value)
{
void *addr;
if (!A_TARGET_ACCESS_OK(targid)) {
HIFdebug();
}
addr = TARGID_TO_PCI_ADDR(targid)+(offset);
A_PCI_WRITE32(addr, value);
{
unsigned long irq_flags;
int idx = pcie_access_log_seqnum % PCIE_ACCESS_LOG_NUM;
spin_lock_irqsave(&pcie_access_log_lock, irq_flags);
pcie_access_log[idx].seqnum = pcie_access_log_seqnum;
pcie_access_log[idx].is_write = TRUE;
pcie_access_log[idx].addr = addr;
pcie_access_log[idx].value = value;
pcie_access_log_seqnum++;
spin_unlock_irqrestore(&pcie_access_log_lock, irq_flags);
}
}
void
HIFdebug(void)
{
/* BUG_ON(1); */
// BREAK();
}
void
HIFTargetDumpAccessLog(void)
{
int idx, len, start_idx, cur_idx;
unsigned long irq_flags;
spin_lock_irqsave(&pcie_access_log_lock, irq_flags);
if (pcie_access_log_seqnum > PCIE_ACCESS_LOG_NUM)
{
len = PCIE_ACCESS_LOG_NUM;
start_idx = pcie_access_log_seqnum % PCIE_ACCESS_LOG_NUM;
}
else
{
len = pcie_access_log_seqnum;
start_idx = 0;
}
for(idx = 0; idx < len; idx++)
{
cur_idx = (start_idx + idx) % PCIE_ACCESS_LOG_NUM;
printk("idx:%d\t sn:%u wr:%d addr:%pK val:%u.\n",
idx,
pcie_access_log[cur_idx].seqnum,
pcie_access_log[cur_idx].is_write,
pcie_access_log[cur_idx].addr,
pcie_access_log[cur_idx].value);
}
pcie_access_log_seqnum = 0;
spin_unlock_irqrestore(&pcie_access_log_lock, irq_flags);
}
#endif
/*
* Convert an opaque HIF device handle into the corresponding
* opaque (void *) operating system device handle.
*/
#if ! defined(A_SIMOS_DEVHOST)
void *
HIFDeviceToOsDevice(HIF_DEVICE *hif_device)
{
return ((struct HIF_CE_state *) hif_device)->sc->dev;
}
#endif
/*
* Typically called from either the PCI infrastructure when
* a firmware interrupt is pending OR from the the shared PCI
* interrupt handler when a firmware-generated interrupt
* to the Host might be pending.
*/
irqreturn_t
HIF_fw_interrupt_handler(int irq, void *arg)
{
struct hif_pci_softc *sc = arg;
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)sc->hif_device;
A_target_id_t targid = hif_state->targid;
A_UINT32 fw_indicator_address, fw_indicator;
A_TARGET_ACCESS_BEGIN_RET(targid);
fw_indicator_address = hif_state->fw_indicator_address;
/* For sudden unplug this will return ~0 */
fw_indicator = A_TARGET_READ(targid, fw_indicator_address);
if ((fw_indicator != ~0) && (fw_indicator & FW_IND_EVENT_PENDING)) {
/* ACK: clear Target-side pending event */
A_TARGET_WRITE(targid, fw_indicator_address, fw_indicator & ~FW_IND_EVENT_PENDING);
A_TARGET_ACCESS_END_RET(targid);
if (hif_state->started) {
/* Alert the Host-side service thread */
atomic_set(&hif_state->fw_event_pending, 1);
hif_completion_thread(hif_state);
} else {
/*
* Probable Target failure before we're prepared
* to handle it. Generally unexpected.
*/
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("ath ERROR: Early firmware event indicated\n"));
}
} else {
A_TARGET_ACCESS_END_RET(targid);
}
return ATH_ISR_SCHED;
}
void *hif_get_targetdef(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
return sc->targetdef;
}
void HIFsuspendwow(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
adf_os_atomic_set(&sc->wow_done, 1);
}
#ifdef IPA_UC_OFFLOAD
void HIFIpaGetCEResource(HIF_DEVICE *hif_device,
A_UINT32 *ce_sr_base_paddr,
A_UINT32 *ce_sr_ring_size,
A_UINT32 *ce_reg_paddr)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct HIF_CE_pipe_info *pipe_info =
&(hif_state->pipe_info[HIF_PCI_IPA_UC_ASSIGNED_CE]);
struct CE_handle *ce_hdl = pipe_info->ce_hdl;
CE_ipaGetResource(ce_hdl, ce_sr_base_paddr, ce_sr_ring_size, ce_reg_paddr);
return;
}
#endif /* IPA_UC_OFFLOAD */
#ifdef FEATURE_RUNTIME_PM
/**
* hif_pci_runtime_pm_warn() - Runtime PM Debugging API
* @sc: hif_pci_softc context
* @msg: log message
*
* Return: void
*/
void hif_pci_runtime_pm_warn(struct hif_pci_softc *sc, const char *msg)
{
struct hif_pm_runtime_context *ctx;
static const char *rpm_status[] = {"RPM_ACTIVE", "RPM_RESUMING",
"RPM_SUSPENDED", "RPM_SUSPENDING"};
pr_warn("%s: usage_count: %d, pm_state: %d, prevent_suspend_cnt: %d\n",
msg, atomic_read(&sc->dev->power.usage_count),
atomic_read(&sc->pm_state),
sc->prevent_suspend_cnt);
pr_warn("runtime_status: %s, runtime_error: %d, disable_depth : %d "
"autosuspend_delay: %d\n",
rpm_status[sc->dev->power.runtime_status],
sc->dev->power.runtime_error,
sc->dev->power.disable_depth,
sc->dev->power.autosuspend_delay);
pr_warn("runtime_get: %u, runtime_put: %u, request_resume: %u\n",
sc->pm_stats.runtime_get, sc->pm_stats.runtime_put,
sc->pm_stats.request_resume);
pr_warn("allow_suspend: %u, prevent_suspend: %u\n",
sc->pm_stats.allow_suspend,
sc->pm_stats.prevent_suspend);
pr_warn("prevent_suspend_timeout: %u, allow_suspend_timeout: %u\n",
sc->pm_stats.prevent_suspend_timeout,
sc->pm_stats.allow_suspend_timeout);
pr_warn("Suspended: %u, resumed: %u count\n",
sc->pm_stats.suspended,
sc->pm_stats.resumed);
pr_warn("suspend_err: %u, runtime_get_err: %u\n",
sc->pm_stats.suspend_err,
sc->pm_stats.runtime_get_err);
pr_warn("Active Wakeup Sources preventing Runtime Suspend: ");
list_for_each_entry(ctx, &sc->prevent_suspend_list, list) {
pr_warn("%s", ctx->name);
if (ctx->timeout)
pr_warn("(%d ms)", ctx->timeout);
pr_warn(" ");
}
pr_warn("\n");
WARN_ON(1);
}
int hif_pm_runtime_get(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
int ret = 0;
int pm_state = adf_os_atomic_read(&sc->pm_state);
if (pm_state == HIF_PM_RUNTIME_STATE_ON ||
pm_state == HIF_PM_RUNTIME_STATE_NONE) {
sc->pm_stats.runtime_get++;
ret = __hif_pm_runtime_get(sc->dev);
/* Get can return 1 if the device is already active, just return
* success in that case
*/
if (ret > 0)
ret = 0;
if (ret)
hif_pm_runtime_put(hif_device);
if (ret && ret != -EINPROGRESS) {
sc->pm_stats.runtime_get_err++;
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"%s: Runtime Get PM Error in pm_state:%d"
" ret: %d\n", __func__,
adf_os_atomic_read(&sc->pm_state), ret);
}
return ret;
}
sc->pm_stats.request_resume++;
sc->pm_stats.last_resume_caller = (void *)_RET_IP_;
ret = hif_pm_request_resume(sc->dev);
return -EAGAIN;
}
int hif_pm_runtime_put(HIF_DEVICE *hif_device)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)hif_device;
struct hif_pci_softc *sc = hif_state->sc;
int ret = 0;
int pm_state, usage_count;
pm_state = adf_os_atomic_read(&sc->pm_state);
usage_count = atomic_read(&sc->dev->power.usage_count);
/*
* During Driver unload, platform driver increments the usage
* count to prevent any runtime suspend getting called.
* So during driver load in HIF_PM_RUNTIME_STATE_NONE state the
* usage_count should be one. In all other states, whithout
* get calling put is FATAL, so handling that case here.
*/
if ((pm_state == HIF_PM_RUNTIME_STATE_NONE && usage_count == 1) ||
usage_count == 0) {
hif_pci_runtime_pm_warn(sc, "PUT Without a Get Operation");
return -EINVAL;
}
sc->pm_stats.runtime_put++;
hif_pm_runtime_mark_last_busy(sc->dev);
ret = hif_pm_runtime_put_auto(sc->dev);
return 0;
}
static int __hif_pm_runtime_prevent_suspend(struct hif_pci_softc
*hif_sc, struct hif_pm_runtime_context *context)
{
int ret = 0;
/*
* We shouldn't be setting context->timeout to zero here when
* context is active as we will have a case where Timeout API's
* for the same context called back to back.
* eg: echo "1=T:10:T:20" > /d/cnss_runtime_pm
* Set context->timeout to zero in hif_pm_runtime_prevent_suspend
* API to ensure the timeout version is no more active and
* list entry of this context will be deleted during allow suspend.
*/
if (context->active)
return 0;
ret = __hif_pm_runtime_get(hif_sc->dev);
/**
* The ret can be -EINPROGRESS, if Runtime status is RPM_RESUMING or
* RPM_SUSPENDING. Any other negative value is an error.
* We shouldn't be do runtime_put here as in later point allow
* suspend gets called with the the context and there the usage count
* is decremented, so suspend will be prevented.
*/
if (ret < 0 && ret != -EINPROGRESS) {
hif_sc->pm_stats.runtime_get_err++;
hif_pci_runtime_pm_warn(hif_sc,
"Prevent Suspend Runtime PM Error");
}
hif_sc->prevent_suspend_cnt++;
context->active = true;
list_add_tail(&context->list, &hif_sc->prevent_suspend_list);
hif_sc->pm_stats.prevent_suspend++;
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_INFO,
"%s: in pm_state:%d ret: %d\n", __func__,
adf_os_atomic_read(&hif_sc->pm_state), ret);
return ret;
}
static int __hif_pm_runtime_allow_suspend(struct hif_pci_softc *hif_sc,
struct hif_pm_runtime_context *context)
{
int ret = 0;
int usage_count;
if (hif_sc->prevent_suspend_cnt == 0)
return ret;
if (!context->active)
return ret;
usage_count = atomic_read(&hif_sc->dev->power.usage_count);
/*
* During Driver unload, platform driver increments the usage
* count to prevent any runtime suspend getting called.
* So during driver load in HIF_PM_RUNTIME_STATE_NONE state the
* usage_count should be one. Ideally this shouldn't happen as
* context->active should be active for allow suspend to happen
* Handling this case here to prevent any failures.
*/
if ((adf_os_atomic_read(&hif_sc->pm_state) == HIF_PM_RUNTIME_STATE_NONE
&& usage_count == 1) || usage_count == 0) {
hif_pci_runtime_pm_warn(hif_sc,
"Allow without a prevent suspend");
return -EINVAL;
}
list_del(&context->list);
hif_sc->prevent_suspend_cnt--;
context->active = false;
context->timeout = 0;
hif_pm_runtime_mark_last_busy(hif_sc->dev);
ret = hif_pm_runtime_put_auto(hif_sc->dev);
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_INFO,
"%s: in pm_state:%d ret: %d\n", __func__,
adf_os_atomic_read(&hif_sc->pm_state), ret);
hif_sc->pm_stats.allow_suspend++;
return ret;
}
void hif_pci_runtime_pm_timeout_fn(unsigned long data)
{
struct hif_pci_softc *hif_sc = (struct hif_pci_softc *)data;
unsigned long timer_expires;
struct hif_pm_runtime_context *context, *temp;
spin_lock_bh(&hif_sc->runtime_lock);
timer_expires = hif_sc->runtime_timer_expires;
/* Make sure we are not called too early, this should take care of
* following case
*
* CPU0 CPU1 (timeout function)
* ---- ----------------------
* spin_lock_irq
* timeout function called
*
* mod_timer()
*
* spin_unlock_irq
* spin_lock_irq
*/
if (timer_expires > 0 && !time_after(timer_expires, jiffies)) {
hif_sc->runtime_timer_expires = 0;
list_for_each_entry_safe(context, temp,
&hif_sc->prevent_suspend_list, list) {
if (context->timeout) {
__hif_pm_runtime_allow_suspend(hif_sc, context);
hif_sc->pm_stats.allow_suspend_timeout++;
}
}
}
spin_unlock_bh(&hif_sc->runtime_lock);
}
int hif_pm_runtime_prevent_suspend(void *ol_sc, void *data)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
struct hif_pm_runtime_context *context = data;
if (!sc->enable_runtime_pm)
return 0;
if (!context)
return -EINVAL;
if (in_irq())
WARN_ON(1);
spin_lock_bh(&hif_sc->runtime_lock);
context->timeout = 0;
__hif_pm_runtime_prevent_suspend(hif_sc, context);
spin_unlock_bh(&hif_sc->runtime_lock);
return 0;
}
int hif_pm_runtime_allow_suspend(void *ol_sc, void *data)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
struct hif_pm_runtime_context *context = data;
if (!sc->enable_runtime_pm)
return 0;
if (!context)
return -EINVAL;
if (in_irq())
WARN_ON(1);
spin_lock_bh(&hif_sc->runtime_lock);
__hif_pm_runtime_allow_suspend(hif_sc, context);
/* The list can be empty as well in cases where
* we have one context in the list and the allow
* suspend came before the timer expires and we delete
* context above from the list.
* When list is empty prevent_suspend count will be zero.
*/
if (hif_sc->prevent_suspend_cnt == 0 &&
hif_sc->runtime_timer_expires > 0) {
del_timer(&hif_sc->runtime_timer);
hif_sc->runtime_timer_expires = 0;
}
spin_unlock_bh(&hif_sc->runtime_lock);
return 0;
}
/**
* hif_pm_runtime_prevent_suspend_timeout() - Prevent runtime suspend timeout
* @ol_sc: HIF context
* delay: Timeout in milliseconds
*
* Prevent runtime suspend with a timeout after which runtime suspend would be
* allowed. This API uses a single timer to allow the suspend and timer is
* modified if the timeout is changed before timer fires.
* If the timeout is less than autosuspend_delay then use mark_last_busy instead
* of starting the timer.
*
* It is wise to try not to use this API and correct the design if possible.
*
* Return: 0 on success and negative error code on failure
*/
int hif_pm_runtime_prevent_suspend_timeout(void *ol_sc, void *data,
unsigned int delay)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
int ret = 0;
unsigned long expires;
struct hif_pm_runtime_context *context = data;
if (vos_is_load_unload_in_progress(VOS_MODULE_ID_HIF, NULL)) {
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"%s: Load/unload in progress, ignore!",
__func__);
return -EINVAL;
}
if (vos_is_logp_in_progress(VOS_MODULE_ID_HIF, NULL)) {
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"%s: LOGP in progress, ignore!", __func__);
return -EINVAL;
}
if (!sc->enable_runtime_pm)
return 0;
if (!context)
return -EINVAL;
if (in_irq())
WARN_ON(1);
/*
* Don't use internal timer if the timeout is less than auto suspend
* delay.
*/
if (delay <= hif_sc->dev->power.autosuspend_delay) {
hif_pm_request_resume(hif_sc->dev);
hif_pm_runtime_mark_last_busy(hif_sc->dev);
return ret;
}
expires = jiffies + msecs_to_jiffies(delay);
expires += !expires;
spin_lock_bh(&hif_sc->runtime_lock);
context->timeout = delay;
ret = __hif_pm_runtime_prevent_suspend(hif_sc, context);
hif_sc->pm_stats.prevent_suspend_timeout++;
/* Modify the timer only if new timeout is after already configured
* timeout
*/
if (time_after(expires, hif_sc->runtime_timer_expires)) {
mod_timer(&hif_sc->runtime_timer, expires);
hif_sc->runtime_timer_expires = expires;
}
spin_unlock_bh(&hif_sc->runtime_lock);
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_INFO,
"%s: pm_state: %d delay: %dms ret: %d\n", __func__,
adf_os_atomic_read(&hif_sc->pm_state), delay, ret);
return ret;
}
/**
* hif_runtime_pm_prevent_suspend_init() - API to initialize Runtime PM context
* @name: Context name
*
* This API initalizes the Runtime PM context of the caller and
* return the pointer.
*
* Return: void *
*/
void *hif_runtime_pm_prevent_suspend_init(const char *name)
{
struct hif_pm_runtime_context *context;
context = adf_os_mem_alloc(NULL, sizeof(*context));
if (!context) {
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"%s: No memory for Runtime PM wakelock context\n",
__func__);
return NULL;
}
context->name = name ? name : "Default";
return context;
}
/**
* hif_runtime_pm_prevent_suspend_deinit() - This API frees the runtime pm ctx
* @data: Runtime PM context
*
* Return: void
*/
void hif_runtime_pm_prevent_suspend_deinit(void *data)
{
struct hif_pm_runtime_context *context = data;
void *vos_context = vos_get_global_context(VOS_MODULE_ID_HIF, NULL);
struct ol_softc *scn = vos_get_context(VOS_MODULE_ID_HIF,
vos_context);
struct hif_pci_softc *sc;
if (!scn)
return;
sc = scn->hif_sc;
if (!sc)
return;
if (!context)
return;
/*
* Ensure to delete the context list entry and reduce the usage count
* before freeing the context if context is active.
*/
spin_lock_bh(&sc->runtime_lock);
__hif_pm_runtime_allow_suspend(sc, context);
spin_unlock_bh(&sc->runtime_lock);
adf_os_mem_free(context);
}
/**
* hif_pm_ssr_runtime_allow_suspend() - Release Runtime Context during SSR
* @sc: hif_pci context
* @context: runtime context
*
* API is used to release runtime pm context from prevent suspend list and
* reduce the usage count taken by the context and set the context state to
* false.
*
* Return: void
*/
void hif_pm_ssr_runtime_allow_suspend(struct hif_pci_softc *sc, void *context)
{
__hif_pm_runtime_allow_suspend(sc, context);
}
/**
* hif_request_runtime_pm_resume() - API to do runtime resume
* @ol_sc: HIF context
*
* API to request runtime resume
*
* Return: void
*/
void hif_request_runtime_pm_resume(void *ol_sc)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
struct device *dev = hif_sc->dev;
hif_pm_request_resume(dev);
hif_pm_runtime_mark_last_busy(dev);
}
#endif
/**
* hif_is_80211_fw_wow_required() - API to check if target suspend is needed
*
* API determines if fw can be suspended and returns true/false to the caller.
* Caller will call WMA WoW API's to suspend.
* This API returns true only for SDIO bus types, for others it's a false.
*
* Return: bool
*/
bool hif_is_80211_fw_wow_required(void)
{
return false;
}
/* hif_addr_in_boundary() - API to check if addr is with in PCIE BAR range
* @hif_device: context of cd
* @offset: offset from PCI BAR mapped base address.
*
* API determines if address to be accessed is with in range or out
* of bound.
*
* Return: success if address is with in PCI BAR range.
*/
int hif_addr_in_boundary(HIF_DEVICE *hif_device, A_UINT32 offset)
{
struct HIF_CE_state *hif_state;
struct hif_pci_softc *sc;
hif_state = (struct HIF_CE_state *)hif_device;
sc = hif_state->sc;
if (unlikely(offset + sizeof(unsigned int) > sc->mem_len)) {
VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR,
"refusing to read mmio out of bounds at 0x%08x - 0x%08zx (max 0x%08zx)\n",
offset, offset + sizeof(unsigned int), sc->mem_len);
return -EINVAL;
}
return 0;
}