linux/drivers/net/wireless/ath/ath6kl/htc_mbox.c
Vasanthakumar Thiagarajan 93b42cae16 ath6kl: Complete failed tx packet in ath6kl_htc_tx_from_queue()
Return status of ath6kl_htc_tx_issue() is ignored in
ath6kl_htc_tx_from_queue(), but failed tx packet is
is not cleaned up. To fix memory leak in this case, call
completion with error. Also, throw an error debug message
when tx fails in ath6kl_sdio_write_async() due to shortage
in bus request buffer.

kvalo: change the error message to WARN_ON_ONCE()

Signed-off-by: Vasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
2012-04-30 10:56:24 +03:00

2935 lines
74 KiB
C

/*
* Copyright (c) 2007-2011 Atheros Communications Inc.
* Copyright (c) 2011-2012 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.
*/
#include "core.h"
#include "hif.h"
#include "debug.h"
#include "hif-ops.h"
#include <asm/unaligned.h>
#define CALC_TXRX_PADDED_LEN(dev, len) (__ALIGN_MASK((len), (dev)->block_mask))
static void ath6kl_htc_mbox_cleanup(struct htc_target *target);
static void ath6kl_htc_mbox_stop(struct htc_target *target);
static int ath6kl_htc_mbox_add_rxbuf_multiple(struct htc_target *target,
struct list_head *pkt_queue);
static void ath6kl_htc_set_credit_dist(struct htc_target *target,
struct ath6kl_htc_credit_info *cred_info,
u16 svc_pri_order[], int len);
/* threshold to re-enable Tx bundling for an AC*/
#define TX_RESUME_BUNDLE_THRESHOLD 1500
/* Functions for Tx credit handling */
static void ath6kl_credit_deposit(struct ath6kl_htc_credit_info *cred_info,
struct htc_endpoint_credit_dist *ep_dist,
int credits)
{
ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit deposit ep %d credits %d\n",
ep_dist->endpoint, credits);
ep_dist->credits += credits;
ep_dist->cred_assngd += credits;
cred_info->cur_free_credits -= credits;
}
static void ath6kl_credit_init(struct ath6kl_htc_credit_info *cred_info,
struct list_head *ep_list,
int tot_credits)
{
struct htc_endpoint_credit_dist *cur_ep_dist;
int count;
ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit init total %d\n", tot_credits);
cred_info->cur_free_credits = tot_credits;
cred_info->total_avail_credits = tot_credits;
list_for_each_entry(cur_ep_dist, ep_list, list) {
if (cur_ep_dist->endpoint == ENDPOINT_0)
continue;
cur_ep_dist->cred_min = cur_ep_dist->cred_per_msg;
if (tot_credits > 4) {
if ((cur_ep_dist->svc_id == WMI_DATA_BK_SVC) ||
(cur_ep_dist->svc_id == WMI_DATA_BE_SVC)) {
ath6kl_credit_deposit(cred_info,
cur_ep_dist,
cur_ep_dist->cred_min);
cur_ep_dist->dist_flags |= HTC_EP_ACTIVE;
}
}
if (cur_ep_dist->svc_id == WMI_CONTROL_SVC) {
ath6kl_credit_deposit(cred_info, cur_ep_dist,
cur_ep_dist->cred_min);
/*
* Control service is always marked active, it
* never goes inactive EVER.
*/
cur_ep_dist->dist_flags |= HTC_EP_ACTIVE;
}
/*
* Streams have to be created (explicit | implicit) for all
* kinds of traffic. BE endpoints are also inactive in the
* beginning. When BE traffic starts it creates implicit
* streams that redistributes credits.
*
* Note: all other endpoints have minimums set but are
* initially given NO credits. credits will be distributed
* as traffic activity demands
*/
}
/*
* For ath6kl_credit_seek function,
* it use list_for_each_entry_reverse to walk around the whole ep list.
* Therefore assign this lowestpri_ep_dist after walk around the ep_list
*/
cred_info->lowestpri_ep_dist = cur_ep_dist->list;
WARN_ON(cred_info->cur_free_credits <= 0);
list_for_each_entry(cur_ep_dist, ep_list, list) {
if (cur_ep_dist->endpoint == ENDPOINT_0)
continue;
if (cur_ep_dist->svc_id == WMI_CONTROL_SVC)
cur_ep_dist->cred_norm = cur_ep_dist->cred_per_msg;
else {
/*
* For the remaining data endpoints, we assume that
* each cred_per_msg are the same. We use a simple
* calculation here, we take the remaining credits
* and determine how many max messages this can
* cover and then set each endpoint's normal value
* equal to 3/4 this amount.
*/
count = (cred_info->cur_free_credits /
cur_ep_dist->cred_per_msg)
* cur_ep_dist->cred_per_msg;
count = (count * 3) >> 2;
count = max(count, cur_ep_dist->cred_per_msg);
cur_ep_dist->cred_norm = count;
}
ath6kl_dbg(ATH6KL_DBG_CREDIT,
"credit ep %d svc_id %d credits %d per_msg %d norm %d min %d\n",
cur_ep_dist->endpoint,
cur_ep_dist->svc_id,
cur_ep_dist->credits,
cur_ep_dist->cred_per_msg,
cur_ep_dist->cred_norm,
cur_ep_dist->cred_min);
}
}
/* initialize and setup credit distribution */
static int ath6kl_htc_mbox_credit_setup(struct htc_target *htc_target,
struct ath6kl_htc_credit_info *cred_info)
{
u16 servicepriority[5];
memset(cred_info, 0, sizeof(struct ath6kl_htc_credit_info));
servicepriority[0] = WMI_CONTROL_SVC; /* highest */
servicepriority[1] = WMI_DATA_VO_SVC;
servicepriority[2] = WMI_DATA_VI_SVC;
servicepriority[3] = WMI_DATA_BE_SVC;
servicepriority[4] = WMI_DATA_BK_SVC; /* lowest */
/* set priority list */
ath6kl_htc_set_credit_dist(htc_target, cred_info, servicepriority, 5);
return 0;
}
/* reduce an ep's credits back to a set limit */
static void ath6kl_credit_reduce(struct ath6kl_htc_credit_info *cred_info,
struct htc_endpoint_credit_dist *ep_dist,
int limit)
{
int credits;
ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit reduce ep %d limit %d\n",
ep_dist->endpoint, limit);
ep_dist->cred_assngd = limit;
if (ep_dist->credits <= limit)
return;
credits = ep_dist->credits - limit;
ep_dist->credits -= credits;
cred_info->cur_free_credits += credits;
}
static void ath6kl_credit_update(struct ath6kl_htc_credit_info *cred_info,
struct list_head *epdist_list)
{
struct htc_endpoint_credit_dist *cur_list;
list_for_each_entry(cur_list, epdist_list, list) {
if (cur_list->endpoint == ENDPOINT_0)
continue;
if (cur_list->cred_to_dist > 0) {
cur_list->credits += cur_list->cred_to_dist;
cur_list->cred_to_dist = 0;
if (cur_list->credits > cur_list->cred_assngd)
ath6kl_credit_reduce(cred_info,
cur_list,
cur_list->cred_assngd);
if (cur_list->credits > cur_list->cred_norm)
ath6kl_credit_reduce(cred_info, cur_list,
cur_list->cred_norm);
if (!(cur_list->dist_flags & HTC_EP_ACTIVE)) {
if (cur_list->txq_depth == 0)
ath6kl_credit_reduce(cred_info,
cur_list, 0);
}
}
}
}
/*
* HTC has an endpoint that needs credits, ep_dist is the endpoint in
* question.
*/
static void ath6kl_credit_seek(struct ath6kl_htc_credit_info *cred_info,
struct htc_endpoint_credit_dist *ep_dist)
{
struct htc_endpoint_credit_dist *curdist_list;
int credits = 0;
int need;
if (ep_dist->svc_id == WMI_CONTROL_SVC)
goto out;
if ((ep_dist->svc_id == WMI_DATA_VI_SVC) ||
(ep_dist->svc_id == WMI_DATA_VO_SVC))
if ((ep_dist->cred_assngd >= ep_dist->cred_norm))
goto out;
/*
* For all other services, we follow a simple algorithm of:
*
* 1. checking the free pool for credits
* 2. checking lower priority endpoints for credits to take
*/
credits = min(cred_info->cur_free_credits, ep_dist->seek_cred);
if (credits >= ep_dist->seek_cred)
goto out;
/*
* We don't have enough in the free pool, try taking away from
* lower priority services The rule for taking away credits:
*
* 1. Only take from lower priority endpoints
* 2. Only take what is allocated above the minimum (never
* starve an endpoint completely)
* 3. Only take what you need.
*/
list_for_each_entry_reverse(curdist_list,
&cred_info->lowestpri_ep_dist,
list) {
if (curdist_list == ep_dist)
break;
need = ep_dist->seek_cred - cred_info->cur_free_credits;
if ((curdist_list->cred_assngd - need) >=
curdist_list->cred_min) {
/*
* The current one has been allocated more than
* it's minimum and it has enough credits assigned
* above it's minimum to fulfill our need try to
* take away just enough to fulfill our need.
*/
ath6kl_credit_reduce(cred_info, curdist_list,
curdist_list->cred_assngd - need);
if (cred_info->cur_free_credits >=
ep_dist->seek_cred)
break;
}
if (curdist_list->endpoint == ENDPOINT_0)
break;
}
credits = min(cred_info->cur_free_credits, ep_dist->seek_cred);
out:
/* did we find some credits? */
if (credits)
ath6kl_credit_deposit(cred_info, ep_dist, credits);
ep_dist->seek_cred = 0;
}
/* redistribute credits based on activity change */
static void ath6kl_credit_redistribute(struct ath6kl_htc_credit_info *info,
struct list_head *ep_dist_list)
{
struct htc_endpoint_credit_dist *curdist_list;
list_for_each_entry(curdist_list, ep_dist_list, list) {
if (curdist_list->endpoint == ENDPOINT_0)
continue;
if ((curdist_list->svc_id == WMI_DATA_BK_SVC) ||
(curdist_list->svc_id == WMI_DATA_BE_SVC))
curdist_list->dist_flags |= HTC_EP_ACTIVE;
if ((curdist_list->svc_id != WMI_CONTROL_SVC) &&
!(curdist_list->dist_flags & HTC_EP_ACTIVE)) {
if (curdist_list->txq_depth == 0)
ath6kl_credit_reduce(info, curdist_list, 0);
else
ath6kl_credit_reduce(info,
curdist_list,
curdist_list->cred_min);
}
}
}
/*
*
* This function is invoked whenever endpoints require credit
* distributions. A lock is held while this function is invoked, this
* function shall NOT block. The ep_dist_list is a list of distribution
* structures in prioritized order as defined by the call to the
* htc_set_credit_dist() api.
*/
static void ath6kl_credit_distribute(struct ath6kl_htc_credit_info *cred_info,
struct list_head *ep_dist_list,
enum htc_credit_dist_reason reason)
{
switch (reason) {
case HTC_CREDIT_DIST_SEND_COMPLETE:
ath6kl_credit_update(cred_info, ep_dist_list);
break;
case HTC_CREDIT_DIST_ACTIVITY_CHANGE:
ath6kl_credit_redistribute(cred_info, ep_dist_list);
break;
default:
break;
}
WARN_ON(cred_info->cur_free_credits > cred_info->total_avail_credits);
WARN_ON(cred_info->cur_free_credits < 0);
}
static void ath6kl_htc_tx_buf_align(u8 **buf, unsigned long len)
{
u8 *align_addr;
if (!IS_ALIGNED((unsigned long) *buf, 4)) {
align_addr = PTR_ALIGN(*buf - 4, 4);
memmove(align_addr, *buf, len);
*buf = align_addr;
}
}
static void ath6kl_htc_tx_prep_pkt(struct htc_packet *packet, u8 flags,
int ctrl0, int ctrl1)
{
struct htc_frame_hdr *hdr;
packet->buf -= HTC_HDR_LENGTH;
hdr = (struct htc_frame_hdr *)packet->buf;
/* Endianess? */
put_unaligned((u16)packet->act_len, &hdr->payld_len);
hdr->flags = flags;
hdr->eid = packet->endpoint;
hdr->ctrl[0] = ctrl0;
hdr->ctrl[1] = ctrl1;
}
static void htc_reclaim_txctrl_buf(struct htc_target *target,
struct htc_packet *pkt)
{
spin_lock_bh(&target->htc_lock);
list_add_tail(&pkt->list, &target->free_ctrl_txbuf);
spin_unlock_bh(&target->htc_lock);
}
static struct htc_packet *htc_get_control_buf(struct htc_target *target,
bool tx)
{
struct htc_packet *packet = NULL;
struct list_head *buf_list;
buf_list = tx ? &target->free_ctrl_txbuf : &target->free_ctrl_rxbuf;
spin_lock_bh(&target->htc_lock);
if (list_empty(buf_list)) {
spin_unlock_bh(&target->htc_lock);
return NULL;
}
packet = list_first_entry(buf_list, struct htc_packet, list);
list_del(&packet->list);
spin_unlock_bh(&target->htc_lock);
if (tx)
packet->buf = packet->buf_start + HTC_HDR_LENGTH;
return packet;
}
static void htc_tx_comp_update(struct htc_target *target,
struct htc_endpoint *endpoint,
struct htc_packet *packet)
{
packet->completion = NULL;
packet->buf += HTC_HDR_LENGTH;
if (!packet->status)
return;
ath6kl_err("req failed (status:%d, ep:%d, len:%d creds:%d)\n",
packet->status, packet->endpoint, packet->act_len,
packet->info.tx.cred_used);
/* on failure to submit, reclaim credits for this packet */
spin_lock_bh(&target->tx_lock);
endpoint->cred_dist.cred_to_dist +=
packet->info.tx.cred_used;
endpoint->cred_dist.txq_depth = get_queue_depth(&endpoint->txq);
ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx ctxt 0x%p dist 0x%p\n",
target->credit_info, &target->cred_dist_list);
ath6kl_credit_distribute(target->credit_info,
&target->cred_dist_list,
HTC_CREDIT_DIST_SEND_COMPLETE);
spin_unlock_bh(&target->tx_lock);
}
static void htc_tx_complete(struct htc_endpoint *endpoint,
struct list_head *txq)
{
if (list_empty(txq))
return;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx complete ep %d pkts %d\n",
endpoint->eid, get_queue_depth(txq));
ath6kl_tx_complete(endpoint->target, txq);
}
static void htc_tx_comp_handler(struct htc_target *target,
struct htc_packet *packet)
{
struct htc_endpoint *endpoint = &target->endpoint[packet->endpoint];
struct list_head container;
ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx complete seqno %d\n",
packet->info.tx.seqno);
htc_tx_comp_update(target, endpoint, packet);
INIT_LIST_HEAD(&container);
list_add_tail(&packet->list, &container);
/* do completion */
htc_tx_complete(endpoint, &container);
}
static void htc_async_tx_scat_complete(struct htc_target *target,
struct hif_scatter_req *scat_req)
{
struct htc_endpoint *endpoint;
struct htc_packet *packet;
struct list_head tx_compq;
int i;
INIT_LIST_HEAD(&tx_compq);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx scat complete len %d entries %d\n",
scat_req->len, scat_req->scat_entries);
if (scat_req->status)
ath6kl_err("send scatter req failed: %d\n", scat_req->status);
packet = scat_req->scat_list[0].packet;
endpoint = &target->endpoint[packet->endpoint];
/* walk through the scatter list and process */
for (i = 0; i < scat_req->scat_entries; i++) {
packet = scat_req->scat_list[i].packet;
if (!packet) {
WARN_ON(1);
return;
}
packet->status = scat_req->status;
htc_tx_comp_update(target, endpoint, packet);
list_add_tail(&packet->list, &tx_compq);
}
/* free scatter request */
hif_scatter_req_add(target->dev->ar, scat_req);
/* complete all packets */
htc_tx_complete(endpoint, &tx_compq);
}
static int ath6kl_htc_tx_issue(struct htc_target *target,
struct htc_packet *packet)
{
int status;
bool sync = false;
u32 padded_len, send_len;
if (!packet->completion)
sync = true;
send_len = packet->act_len + HTC_HDR_LENGTH;
padded_len = CALC_TXRX_PADDED_LEN(target, send_len);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx issue len %d seqno %d padded_len %d mbox 0x%X %s\n",
send_len, packet->info.tx.seqno, padded_len,
target->dev->ar->mbox_info.htc_addr,
sync ? "sync" : "async");
if (sync) {
status = hif_read_write_sync(target->dev->ar,
target->dev->ar->mbox_info.htc_addr,
packet->buf, padded_len,
HIF_WR_SYNC_BLOCK_INC);
packet->status = status;
packet->buf += HTC_HDR_LENGTH;
} else
status = hif_write_async(target->dev->ar,
target->dev->ar->mbox_info.htc_addr,
packet->buf, padded_len,
HIF_WR_ASYNC_BLOCK_INC, packet);
return status;
}
static int htc_check_credits(struct htc_target *target,
struct htc_endpoint *ep, u8 *flags,
enum htc_endpoint_id eid, unsigned int len,
int *req_cred)
{
*req_cred = (len > target->tgt_cred_sz) ?
DIV_ROUND_UP(len, target->tgt_cred_sz) : 1;
ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit check need %d got %d\n",
*req_cred, ep->cred_dist.credits);
if (ep->cred_dist.credits < *req_cred) {
if (eid == ENDPOINT_0)
return -EINVAL;
/* Seek more credits */
ep->cred_dist.seek_cred = *req_cred - ep->cred_dist.credits;
ath6kl_credit_seek(target->credit_info, &ep->cred_dist);
ep->cred_dist.seek_cred = 0;
if (ep->cred_dist.credits < *req_cred) {
ath6kl_dbg(ATH6KL_DBG_CREDIT,
"credit not found for ep %d\n",
eid);
return -EINVAL;
}
}
ep->cred_dist.credits -= *req_cred;
ep->ep_st.cred_cosumd += *req_cred;
/* When we are getting low on credits, ask for more */
if (ep->cred_dist.credits < ep->cred_dist.cred_per_msg) {
ep->cred_dist.seek_cred =
ep->cred_dist.cred_per_msg - ep->cred_dist.credits;
ath6kl_credit_seek(target->credit_info, &ep->cred_dist);
/* see if we were successful in getting more */
if (ep->cred_dist.credits < ep->cred_dist.cred_per_msg) {
/* tell the target we need credits ASAP! */
*flags |= HTC_FLAGS_NEED_CREDIT_UPDATE;
ep->ep_st.cred_low_indicate += 1;
ath6kl_dbg(ATH6KL_DBG_CREDIT,
"credit we need credits asap\n");
}
}
return 0;
}
static void ath6kl_htc_tx_pkts_get(struct htc_target *target,
struct htc_endpoint *endpoint,
struct list_head *queue)
{
int req_cred;
u8 flags;
struct htc_packet *packet;
unsigned int len;
while (true) {
flags = 0;
if (list_empty(&endpoint->txq))
break;
packet = list_first_entry(&endpoint->txq, struct htc_packet,
list);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx got packet 0x%p queue depth %d\n",
packet, get_queue_depth(&endpoint->txq));
len = CALC_TXRX_PADDED_LEN(target,
packet->act_len + HTC_HDR_LENGTH);
if (htc_check_credits(target, endpoint, &flags,
packet->endpoint, len, &req_cred))
break;
/* now we can fully move onto caller's queue */
packet = list_first_entry(&endpoint->txq, struct htc_packet,
list);
list_move_tail(&packet->list, queue);
/* save the number of credits this packet consumed */
packet->info.tx.cred_used = req_cred;
/* all TX packets are handled asynchronously */
packet->completion = htc_tx_comp_handler;
packet->context = target;
endpoint->ep_st.tx_issued += 1;
/* save send flags */
packet->info.tx.flags = flags;
packet->info.tx.seqno = endpoint->seqno;
endpoint->seqno++;
}
}
/* See if the padded tx length falls on a credit boundary */
static int htc_get_credit_padding(unsigned int cred_sz, int *len,
struct htc_endpoint *ep)
{
int rem_cred, cred_pad;
rem_cred = *len % cred_sz;
/* No padding needed */
if (!rem_cred)
return 0;
if (!(ep->conn_flags & HTC_FLGS_TX_BNDL_PAD_EN))
return -1;
/*
* The transfer consumes a "partial" credit, this
* packet cannot be bundled unless we add
* additional "dummy" padding (max 255 bytes) to
* consume the entire credit.
*/
cred_pad = *len < cred_sz ? (cred_sz - *len) : rem_cred;
if ((cred_pad > 0) && (cred_pad <= 255))
*len += cred_pad;
else
/* The amount of padding is too large, send as non-bundled */
return -1;
return cred_pad;
}
static int ath6kl_htc_tx_setup_scat_list(struct htc_target *target,
struct htc_endpoint *endpoint,
struct hif_scatter_req *scat_req,
int n_scat,
struct list_head *queue)
{
struct htc_packet *packet;
int i, len, rem_scat, cred_pad;
int status = 0;
u8 flags;
rem_scat = target->max_tx_bndl_sz;
for (i = 0; i < n_scat; i++) {
scat_req->scat_list[i].packet = NULL;
if (list_empty(queue))
break;
packet = list_first_entry(queue, struct htc_packet, list);
len = CALC_TXRX_PADDED_LEN(target,
packet->act_len + HTC_HDR_LENGTH);
cred_pad = htc_get_credit_padding(target->tgt_cred_sz,
&len, endpoint);
if (cred_pad < 0 || rem_scat < len) {
status = -ENOSPC;
break;
}
rem_scat -= len;
/* now remove it from the queue */
list_del(&packet->list);
scat_req->scat_list[i].packet = packet;
/* prepare packet and flag message as part of a send bundle */
flags = packet->info.tx.flags | HTC_FLAGS_SEND_BUNDLE;
ath6kl_htc_tx_prep_pkt(packet, flags,
cred_pad, packet->info.tx.seqno);
/* Make sure the buffer is 4-byte aligned */
ath6kl_htc_tx_buf_align(&packet->buf,
packet->act_len + HTC_HDR_LENGTH);
scat_req->scat_list[i].buf = packet->buf;
scat_req->scat_list[i].len = len;
scat_req->len += len;
scat_req->scat_entries++;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx adding (%d) pkt 0x%p seqno %d len %d remaining %d\n",
i, packet, packet->info.tx.seqno, len, rem_scat);
}
/* Roll back scatter setup in case of any failure */
if (scat_req->scat_entries < HTC_MIN_HTC_MSGS_TO_BUNDLE) {
for (i = scat_req->scat_entries - 1; i >= 0; i--) {
packet = scat_req->scat_list[i].packet;
if (packet) {
packet->buf += HTC_HDR_LENGTH;
list_add(&packet->list, queue);
}
}
return -EAGAIN;
}
return status;
}
/*
* Drain a queue and send as bundles this function may return without fully
* draining the queue when
*
* 1. scatter resources are exhausted
* 2. a message that will consume a partial credit will stop the
* bundling process early
* 3. we drop below the minimum number of messages for a bundle
*/
static void ath6kl_htc_tx_bundle(struct htc_endpoint *endpoint,
struct list_head *queue,
int *sent_bundle, int *n_bundle_pkts)
{
struct htc_target *target = endpoint->target;
struct hif_scatter_req *scat_req = NULL;
int n_scat, n_sent_bundle = 0, tot_pkts_bundle = 0;
int status;
u32 txb_mask;
u8 ac = WMM_NUM_AC;
if ((HTC_CTRL_RSVD_SVC != endpoint->svc_id) &&
(WMI_CONTROL_SVC != endpoint->svc_id))
ac = target->dev->ar->ep2ac_map[endpoint->eid];
while (true) {
status = 0;
n_scat = get_queue_depth(queue);
n_scat = min(n_scat, target->msg_per_bndl_max);
if (n_scat < HTC_MIN_HTC_MSGS_TO_BUNDLE)
/* not enough to bundle */
break;
scat_req = hif_scatter_req_get(target->dev->ar);
if (!scat_req) {
/* no scatter resources */
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx no more scatter resources\n");
break;
}
if ((ac < WMM_NUM_AC) && (ac != WMM_AC_BK)) {
if (WMM_AC_BE == ac)
/*
* BE, BK have priorities and bit
* positions reversed
*/
txb_mask = (1 << WMM_AC_BK);
else
/*
* any AC with priority lower than
* itself
*/
txb_mask = ((1 << ac) - 1);
/*
* when the scatter request resources drop below a
* certain threshold, disable Tx bundling for all
* AC's with priority lower than the current requesting
* AC. Otherwise re-enable Tx bundling for them
*/
if (scat_req->scat_q_depth < ATH6KL_SCATTER_REQS)
target->tx_bndl_mask &= ~txb_mask;
else
target->tx_bndl_mask |= txb_mask;
}
ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx pkts to scatter: %d\n",
n_scat);
scat_req->len = 0;
scat_req->scat_entries = 0;
status = ath6kl_htc_tx_setup_scat_list(target, endpoint,
scat_req, n_scat,
queue);
if (status == -EAGAIN) {
hif_scatter_req_add(target->dev->ar, scat_req);
break;
}
/* send path is always asynchronous */
scat_req->complete = htc_async_tx_scat_complete;
n_sent_bundle++;
tot_pkts_bundle += scat_req->scat_entries;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx scatter bytes %d entries %d\n",
scat_req->len, scat_req->scat_entries);
ath6kl_hif_submit_scat_req(target->dev, scat_req, false);
if (status)
break;
}
*sent_bundle = n_sent_bundle;
*n_bundle_pkts = tot_pkts_bundle;
ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx bundle sent %d pkts\n",
n_sent_bundle);
return;
}
static void ath6kl_htc_tx_from_queue(struct htc_target *target,
struct htc_endpoint *endpoint)
{
struct list_head txq;
struct htc_packet *packet;
int bundle_sent;
int n_pkts_bundle;
u8 ac = WMM_NUM_AC;
int status;
spin_lock_bh(&target->tx_lock);
endpoint->tx_proc_cnt++;
if (endpoint->tx_proc_cnt > 1) {
endpoint->tx_proc_cnt--;
spin_unlock_bh(&target->tx_lock);
ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx busy\n");
return;
}
/*
* drain the endpoint TX queue for transmission as long
* as we have enough credits.
*/
INIT_LIST_HEAD(&txq);
if ((HTC_CTRL_RSVD_SVC != endpoint->svc_id) &&
(WMI_CONTROL_SVC != endpoint->svc_id))
ac = target->dev->ar->ep2ac_map[endpoint->eid];
while (true) {
if (list_empty(&endpoint->txq))
break;
ath6kl_htc_tx_pkts_get(target, endpoint, &txq);
if (list_empty(&txq))
break;
spin_unlock_bh(&target->tx_lock);
bundle_sent = 0;
n_pkts_bundle = 0;
while (true) {
/* try to send a bundle on each pass */
if ((target->tx_bndl_mask) &&
(get_queue_depth(&txq) >=
HTC_MIN_HTC_MSGS_TO_BUNDLE)) {
int temp1 = 0, temp2 = 0;
/* check if bundling is enabled for an AC */
if (target->tx_bndl_mask & (1 << ac)) {
ath6kl_htc_tx_bundle(endpoint, &txq,
&temp1, &temp2);
bundle_sent += temp1;
n_pkts_bundle += temp2;
}
}
if (list_empty(&txq))
break;
packet = list_first_entry(&txq, struct htc_packet,
list);
list_del(&packet->list);
ath6kl_htc_tx_prep_pkt(packet, packet->info.tx.flags,
0, packet->info.tx.seqno);
status = ath6kl_htc_tx_issue(target, packet);
if (status) {
packet->status = status;
packet->completion(packet->context, packet);
}
}
spin_lock_bh(&target->tx_lock);
endpoint->ep_st.tx_bundles += bundle_sent;
endpoint->ep_st.tx_pkt_bundled += n_pkts_bundle;
/*
* if an AC has bundling disabled and no tx bundling
* has occured continously for a certain number of TX,
* enable tx bundling for this AC
*/
if (!bundle_sent) {
if (!(target->tx_bndl_mask & (1 << ac)) &&
(ac < WMM_NUM_AC)) {
if (++target->ac_tx_count[ac] >=
TX_RESUME_BUNDLE_THRESHOLD) {
target->ac_tx_count[ac] = 0;
target->tx_bndl_mask |= (1 << ac);
}
}
} else {
/* tx bundling will reset the counter */
if (ac < WMM_NUM_AC)
target->ac_tx_count[ac] = 0;
}
}
endpoint->tx_proc_cnt = 0;
spin_unlock_bh(&target->tx_lock);
}
static bool ath6kl_htc_tx_try(struct htc_target *target,
struct htc_endpoint *endpoint,
struct htc_packet *tx_pkt)
{
struct htc_ep_callbacks ep_cb;
int txq_depth;
bool overflow = false;
ep_cb = endpoint->ep_cb;
spin_lock_bh(&target->tx_lock);
txq_depth = get_queue_depth(&endpoint->txq);
spin_unlock_bh(&target->tx_lock);
if (txq_depth >= endpoint->max_txq_depth)
overflow = true;
if (overflow)
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx overflow ep %d depth %d max %d\n",
endpoint->eid, txq_depth,
endpoint->max_txq_depth);
if (overflow && ep_cb.tx_full) {
if (ep_cb.tx_full(endpoint->target, tx_pkt) ==
HTC_SEND_FULL_DROP) {
endpoint->ep_st.tx_dropped += 1;
return false;
}
}
spin_lock_bh(&target->tx_lock);
list_add_tail(&tx_pkt->list, &endpoint->txq);
spin_unlock_bh(&target->tx_lock);
ath6kl_htc_tx_from_queue(target, endpoint);
return true;
}
static void htc_chk_ep_txq(struct htc_target *target)
{
struct htc_endpoint *endpoint;
struct htc_endpoint_credit_dist *cred_dist;
/*
* Run through the credit distribution list to see if there are
* packets queued. NOTE: no locks need to be taken since the
* distribution list is not dynamic (cannot be re-ordered) and we
* are not modifying any state.
*/
list_for_each_entry(cred_dist, &target->cred_dist_list, list) {
endpoint = cred_dist->htc_ep;
spin_lock_bh(&target->tx_lock);
if (!list_empty(&endpoint->txq)) {
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc creds ep %d credits %d pkts %d\n",
cred_dist->endpoint,
endpoint->cred_dist.credits,
get_queue_depth(&endpoint->txq));
spin_unlock_bh(&target->tx_lock);
/*
* Try to start the stalled queue, this list is
* ordered by priority. If there are credits
* available the highest priority queue will get a
* chance to reclaim credits from lower priority
* ones.
*/
ath6kl_htc_tx_from_queue(target, endpoint);
spin_lock_bh(&target->tx_lock);
}
spin_unlock_bh(&target->tx_lock);
}
}
static int htc_setup_tx_complete(struct htc_target *target)
{
struct htc_packet *send_pkt = NULL;
int status;
send_pkt = htc_get_control_buf(target, true);
if (!send_pkt)
return -ENOMEM;
if (target->htc_tgt_ver >= HTC_VERSION_2P1) {
struct htc_setup_comp_ext_msg *setup_comp_ext;
u32 flags = 0;
setup_comp_ext =
(struct htc_setup_comp_ext_msg *)send_pkt->buf;
memset(setup_comp_ext, 0, sizeof(*setup_comp_ext));
setup_comp_ext->msg_id =
cpu_to_le16(HTC_MSG_SETUP_COMPLETE_EX_ID);
if (target->msg_per_bndl_max > 0) {
/* Indicate HTC bundling to the target */
flags |= HTC_SETUP_COMP_FLG_RX_BNDL_EN;
setup_comp_ext->msg_per_rxbndl =
target->msg_per_bndl_max;
}
memcpy(&setup_comp_ext->flags, &flags,
sizeof(setup_comp_ext->flags));
set_htc_pkt_info(send_pkt, NULL, (u8 *) setup_comp_ext,
sizeof(struct htc_setup_comp_ext_msg),
ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG);
} else {
struct htc_setup_comp_msg *setup_comp;
setup_comp = (struct htc_setup_comp_msg *)send_pkt->buf;
memset(setup_comp, 0, sizeof(struct htc_setup_comp_msg));
setup_comp->msg_id = cpu_to_le16(HTC_MSG_SETUP_COMPLETE_ID);
set_htc_pkt_info(send_pkt, NULL, (u8 *) setup_comp,
sizeof(struct htc_setup_comp_msg),
ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG);
}
/* we want synchronous operation */
send_pkt->completion = NULL;
ath6kl_htc_tx_prep_pkt(send_pkt, 0, 0, 0);
status = ath6kl_htc_tx_issue(target, send_pkt);
if (send_pkt != NULL)
htc_reclaim_txctrl_buf(target, send_pkt);
return status;
}
static void ath6kl_htc_set_credit_dist(struct htc_target *target,
struct ath6kl_htc_credit_info *credit_info,
u16 srvc_pri_order[], int list_len)
{
struct htc_endpoint *endpoint;
int i, ep;
target->credit_info = credit_info;
list_add_tail(&target->endpoint[ENDPOINT_0].cred_dist.list,
&target->cred_dist_list);
for (i = 0; i < list_len; i++) {
for (ep = ENDPOINT_1; ep < ENDPOINT_MAX; ep++) {
endpoint = &target->endpoint[ep];
if (endpoint->svc_id == srvc_pri_order[i]) {
list_add_tail(&endpoint->cred_dist.list,
&target->cred_dist_list);
break;
}
}
if (ep >= ENDPOINT_MAX) {
WARN_ON(1);
return;
}
}
}
static int ath6kl_htc_mbox_tx(struct htc_target *target,
struct htc_packet *packet)
{
struct htc_endpoint *endpoint;
struct list_head queue;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx ep id %d buf 0x%p len %d\n",
packet->endpoint, packet->buf, packet->act_len);
if (packet->endpoint >= ENDPOINT_MAX) {
WARN_ON(1);
return -EINVAL;
}
endpoint = &target->endpoint[packet->endpoint];
if (!ath6kl_htc_tx_try(target, endpoint, packet)) {
packet->status = (target->htc_flags & HTC_OP_STATE_STOPPING) ?
-ECANCELED : -ENOSPC;
INIT_LIST_HEAD(&queue);
list_add(&packet->list, &queue);
htc_tx_complete(endpoint, &queue);
}
return 0;
}
/* flush endpoint TX queue */
static void ath6kl_htc_mbox_flush_txep(struct htc_target *target,
enum htc_endpoint_id eid, u16 tag)
{
struct htc_packet *packet, *tmp_pkt;
struct list_head discard_q, container;
struct htc_endpoint *endpoint = &target->endpoint[eid];
if (!endpoint->svc_id) {
WARN_ON(1);
return;
}
/* initialize the discard queue */
INIT_LIST_HEAD(&discard_q);
spin_lock_bh(&target->tx_lock);
list_for_each_entry_safe(packet, tmp_pkt, &endpoint->txq, list) {
if ((tag == HTC_TX_PACKET_TAG_ALL) ||
(tag == packet->info.tx.tag))
list_move_tail(&packet->list, &discard_q);
}
spin_unlock_bh(&target->tx_lock);
list_for_each_entry_safe(packet, tmp_pkt, &discard_q, list) {
packet->status = -ECANCELED;
list_del(&packet->list);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx flushing pkt 0x%p len %d ep %d tag 0x%x\n",
packet, packet->act_len,
packet->endpoint, packet->info.tx.tag);
INIT_LIST_HEAD(&container);
list_add_tail(&packet->list, &container);
htc_tx_complete(endpoint, &container);
}
}
static void ath6kl_htc_flush_txep_all(struct htc_target *target)
{
struct htc_endpoint *endpoint;
int i;
dump_cred_dist_stats(target);
for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) {
endpoint = &target->endpoint[i];
if (endpoint->svc_id == 0)
/* not in use.. */
continue;
ath6kl_htc_mbox_flush_txep(target, i, HTC_TX_PACKET_TAG_ALL);
}
}
static void ath6kl_htc_mbox_activity_changed(struct htc_target *target,
enum htc_endpoint_id eid,
bool active)
{
struct htc_endpoint *endpoint = &target->endpoint[eid];
bool dist = false;
if (endpoint->svc_id == 0) {
WARN_ON(1);
return;
}
spin_lock_bh(&target->tx_lock);
if (active) {
if (!(endpoint->cred_dist.dist_flags & HTC_EP_ACTIVE)) {
endpoint->cred_dist.dist_flags |= HTC_EP_ACTIVE;
dist = true;
}
} else {
if (endpoint->cred_dist.dist_flags & HTC_EP_ACTIVE) {
endpoint->cred_dist.dist_flags &= ~HTC_EP_ACTIVE;
dist = true;
}
}
if (dist) {
endpoint->cred_dist.txq_depth =
get_queue_depth(&endpoint->txq);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc tx activity ctxt 0x%p dist 0x%p\n",
target->credit_info, &target->cred_dist_list);
ath6kl_credit_distribute(target->credit_info,
&target->cred_dist_list,
HTC_CREDIT_DIST_ACTIVITY_CHANGE);
}
spin_unlock_bh(&target->tx_lock);
if (dist && !active)
htc_chk_ep_txq(target);
}
/* HTC Rx */
static inline void ath6kl_htc_rx_update_stats(struct htc_endpoint *endpoint,
int n_look_ahds)
{
endpoint->ep_st.rx_pkts++;
if (n_look_ahds == 1)
endpoint->ep_st.rx_lkahds++;
else if (n_look_ahds > 1)
endpoint->ep_st.rx_bundle_lkahd++;
}
static inline bool htc_valid_rx_frame_len(struct htc_target *target,
enum htc_endpoint_id eid, int len)
{
return (eid == target->dev->ar->ctrl_ep) ?
len <= ATH6KL_BUFFER_SIZE : len <= ATH6KL_AMSDU_BUFFER_SIZE;
}
static int htc_add_rxbuf(struct htc_target *target, struct htc_packet *packet)
{
struct list_head queue;
INIT_LIST_HEAD(&queue);
list_add_tail(&packet->list, &queue);
return ath6kl_htc_mbox_add_rxbuf_multiple(target, &queue);
}
static void htc_reclaim_rxbuf(struct htc_target *target,
struct htc_packet *packet,
struct htc_endpoint *ep)
{
if (packet->info.rx.rx_flags & HTC_RX_PKT_NO_RECYCLE) {
htc_rxpkt_reset(packet);
packet->status = -ECANCELED;
ep->ep_cb.rx(ep->target, packet);
} else {
htc_rxpkt_reset(packet);
htc_add_rxbuf((void *)(target), packet);
}
}
static void reclaim_rx_ctrl_buf(struct htc_target *target,
struct htc_packet *packet)
{
spin_lock_bh(&target->htc_lock);
list_add_tail(&packet->list, &target->free_ctrl_rxbuf);
spin_unlock_bh(&target->htc_lock);
}
static int ath6kl_htc_rx_packet(struct htc_target *target,
struct htc_packet *packet,
u32 rx_len)
{
struct ath6kl_device *dev = target->dev;
u32 padded_len;
int status;
padded_len = CALC_TXRX_PADDED_LEN(target, rx_len);
if (padded_len > packet->buf_len) {
ath6kl_err("not enough receive space for packet - padlen %d recvlen %d bufferlen %d\n",
padded_len, rx_len, packet->buf_len);
return -ENOMEM;
}
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx 0x%p hdr x%x len %d mbox 0x%x\n",
packet, packet->info.rx.exp_hdr,
padded_len, dev->ar->mbox_info.htc_addr);
status = hif_read_write_sync(dev->ar,
dev->ar->mbox_info.htc_addr,
packet->buf, padded_len,
HIF_RD_SYNC_BLOCK_FIX);
packet->status = status;
return status;
}
/*
* optimization for recv packets, we can indicate a
* "hint" that there are more single-packets to fetch
* on this endpoint.
*/
static void ath6kl_htc_rx_set_indicate(u32 lk_ahd,
struct htc_endpoint *endpoint,
struct htc_packet *packet)
{
struct htc_frame_hdr *htc_hdr = (struct htc_frame_hdr *)&lk_ahd;
if (htc_hdr->eid == packet->endpoint) {
if (!list_empty(&endpoint->rx_bufq))
packet->info.rx.indicat_flags |=
HTC_RX_FLAGS_INDICATE_MORE_PKTS;
}
}
static void ath6kl_htc_rx_chk_water_mark(struct htc_endpoint *endpoint)
{
struct htc_ep_callbacks ep_cb = endpoint->ep_cb;
if (ep_cb.rx_refill_thresh > 0) {
spin_lock_bh(&endpoint->target->rx_lock);
if (get_queue_depth(&endpoint->rx_bufq)
< ep_cb.rx_refill_thresh) {
spin_unlock_bh(&endpoint->target->rx_lock);
ep_cb.rx_refill(endpoint->target, endpoint->eid);
return;
}
spin_unlock_bh(&endpoint->target->rx_lock);
}
}
/* This function is called with rx_lock held */
static int ath6kl_htc_rx_setup(struct htc_target *target,
struct htc_endpoint *ep,
u32 *lk_ahds, struct list_head *queue, int n_msg)
{
struct htc_packet *packet;
/* FIXME: type of lk_ahds can't be right */
struct htc_frame_hdr *htc_hdr = (struct htc_frame_hdr *)lk_ahds;
struct htc_ep_callbacks ep_cb;
int status = 0, j, full_len;
bool no_recycle;
full_len = CALC_TXRX_PADDED_LEN(target,
le16_to_cpu(htc_hdr->payld_len) +
sizeof(*htc_hdr));
if (!htc_valid_rx_frame_len(target, ep->eid, full_len)) {
ath6kl_warn("Rx buffer requested with invalid length htc_hdr:eid %d, flags 0x%x, len %d\n",
htc_hdr->eid, htc_hdr->flags,
le16_to_cpu(htc_hdr->payld_len));
return -EINVAL;
}
ep_cb = ep->ep_cb;
for (j = 0; j < n_msg; j++) {
/*
* Reset flag, any packets allocated using the
* rx_alloc() API cannot be recycled on
* cleanup,they must be explicitly returned.
*/
no_recycle = false;
if (ep_cb.rx_allocthresh &&
(full_len > ep_cb.rx_alloc_thresh)) {
ep->ep_st.rx_alloc_thresh_hit += 1;
ep->ep_st.rxalloc_thresh_byte +=
le16_to_cpu(htc_hdr->payld_len);
spin_unlock_bh(&target->rx_lock);
no_recycle = true;
packet = ep_cb.rx_allocthresh(ep->target, ep->eid,
full_len);
spin_lock_bh(&target->rx_lock);
} else {
/* refill handler is being used */
if (list_empty(&ep->rx_bufq)) {
if (ep_cb.rx_refill) {
spin_unlock_bh(&target->rx_lock);
ep_cb.rx_refill(ep->target, ep->eid);
spin_lock_bh(&target->rx_lock);
}
}
if (list_empty(&ep->rx_bufq))
packet = NULL;
else {
packet = list_first_entry(&ep->rx_bufq,
struct htc_packet, list);
list_del(&packet->list);
}
}
if (!packet) {
target->rx_st_flags |= HTC_RECV_WAIT_BUFFERS;
target->ep_waiting = ep->eid;
return -ENOSPC;
}
/* clear flags */
packet->info.rx.rx_flags = 0;
packet->info.rx.indicat_flags = 0;
packet->status = 0;
if (no_recycle)
/*
* flag that these packets cannot be
* recycled, they have to be returned to
* the user
*/
packet->info.rx.rx_flags |= HTC_RX_PKT_NO_RECYCLE;
/* Caller needs to free this upon any failure */
list_add_tail(&packet->list, queue);
if (target->htc_flags & HTC_OP_STATE_STOPPING) {
status = -ECANCELED;
break;
}
if (j) {
packet->info.rx.rx_flags |= HTC_RX_PKT_REFRESH_HDR;
packet->info.rx.exp_hdr = 0xFFFFFFFF;
} else
/* set expected look ahead */
packet->info.rx.exp_hdr = *lk_ahds;
packet->act_len = le16_to_cpu(htc_hdr->payld_len) +
HTC_HDR_LENGTH;
}
return status;
}
static int ath6kl_htc_rx_alloc(struct htc_target *target,
u32 lk_ahds[], int msg,
struct htc_endpoint *endpoint,
struct list_head *queue)
{
int status = 0;
struct htc_packet *packet, *tmp_pkt;
struct htc_frame_hdr *htc_hdr;
int i, n_msg;
spin_lock_bh(&target->rx_lock);
for (i = 0; i < msg; i++) {
htc_hdr = (struct htc_frame_hdr *)&lk_ahds[i];
if (htc_hdr->eid >= ENDPOINT_MAX) {
ath6kl_err("invalid ep in look-ahead: %d\n",
htc_hdr->eid);
status = -ENOMEM;
break;
}
if (htc_hdr->eid != endpoint->eid) {
ath6kl_err("invalid ep in look-ahead: %d should be : %d (index:%d)\n",
htc_hdr->eid, endpoint->eid, i);
status = -ENOMEM;
break;
}
if (le16_to_cpu(htc_hdr->payld_len) > HTC_MAX_PAYLOAD_LENGTH) {
ath6kl_err("payload len %d exceeds max htc : %d !\n",
htc_hdr->payld_len,
(u32) HTC_MAX_PAYLOAD_LENGTH);
status = -ENOMEM;
break;
}
if (endpoint->svc_id == 0) {
ath6kl_err("ep %d is not connected !\n", htc_hdr->eid);
status = -ENOMEM;
break;
}
if (htc_hdr->flags & HTC_FLG_RX_BNDL_CNT) {
/*
* HTC header indicates that every packet to follow
* has the same padded length so that it can be
* optimally fetched as a full bundle.
*/
n_msg = (htc_hdr->flags & HTC_FLG_RX_BNDL_CNT) >>
HTC_FLG_RX_BNDL_CNT_S;
/* the count doesn't include the starter frame */
n_msg++;
if (n_msg > target->msg_per_bndl_max) {
status = -ENOMEM;
break;
}
endpoint->ep_st.rx_bundle_from_hdr += 1;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx bundle pkts %d\n",
n_msg);
} else
/* HTC header only indicates 1 message to fetch */
n_msg = 1;
/* Setup packet buffers for each message */
status = ath6kl_htc_rx_setup(target, endpoint, &lk_ahds[i],
queue, n_msg);
/*
* This is due to unavailabilty of buffers to rx entire data.
* Return no error so that free buffers from queue can be used
* to receive partial data.
*/
if (status == -ENOSPC) {
spin_unlock_bh(&target->rx_lock);
return 0;
}
if (status)
break;
}
spin_unlock_bh(&target->rx_lock);
if (status) {
list_for_each_entry_safe(packet, tmp_pkt, queue, list) {
list_del(&packet->list);
htc_reclaim_rxbuf(target, packet,
&target->endpoint[packet->endpoint]);
}
}
return status;
}
static void htc_ctrl_rx(struct htc_target *context, struct htc_packet *packets)
{
if (packets->endpoint != ENDPOINT_0) {
WARN_ON(1);
return;
}
if (packets->status == -ECANCELED) {
reclaim_rx_ctrl_buf(context, packets);
return;
}
if (packets->act_len > 0) {
ath6kl_err("htc_ctrl_rx, got message with len:%zu\n",
packets->act_len + HTC_HDR_LENGTH);
ath6kl_dbg_dump(ATH6KL_DBG_HTC,
"htc rx unexpected endpoint 0 message", "",
packets->buf - HTC_HDR_LENGTH,
packets->act_len + HTC_HDR_LENGTH);
}
htc_reclaim_rxbuf(context, packets, &context->endpoint[0]);
}
static void htc_proc_cred_rpt(struct htc_target *target,
struct htc_credit_report *rpt,
int n_entries,
enum htc_endpoint_id from_ep)
{
struct htc_endpoint *endpoint;
int tot_credits = 0, i;
bool dist = false;
spin_lock_bh(&target->tx_lock);
for (i = 0; i < n_entries; i++, rpt++) {
if (rpt->eid >= ENDPOINT_MAX) {
WARN_ON(1);
spin_unlock_bh(&target->tx_lock);
return;
}
endpoint = &target->endpoint[rpt->eid];
ath6kl_dbg(ATH6KL_DBG_CREDIT,
"credit report ep %d credits %d\n",
rpt->eid, rpt->credits);
endpoint->ep_st.tx_cred_rpt += 1;
endpoint->ep_st.cred_retnd += rpt->credits;
if (from_ep == rpt->eid) {
/*
* This credit report arrived on the same endpoint
* indicating it arrived in an RX packet.
*/
endpoint->ep_st.cred_from_rx += rpt->credits;
endpoint->ep_st.cred_rpt_from_rx += 1;
} else if (from_ep == ENDPOINT_0) {
/* credit arrived on endpoint 0 as a NULL message */
endpoint->ep_st.cred_from_ep0 += rpt->credits;
endpoint->ep_st.cred_rpt_ep0 += 1;
} else {
endpoint->ep_st.cred_from_other += rpt->credits;
endpoint->ep_st.cred_rpt_from_other += 1;
}
if (rpt->eid == ENDPOINT_0)
/* always give endpoint 0 credits back */
endpoint->cred_dist.credits += rpt->credits;
else {
endpoint->cred_dist.cred_to_dist += rpt->credits;
dist = true;
}
/*
* Refresh tx depth for distribution function that will
* recover these credits NOTE: this is only valid when
* there are credits to recover!
*/
endpoint->cred_dist.txq_depth =
get_queue_depth(&endpoint->txq);
tot_credits += rpt->credits;
}
if (dist) {
/*
* This was a credit return based on a completed send
* operations note, this is done with the lock held
*/
ath6kl_credit_distribute(target->credit_info,
&target->cred_dist_list,
HTC_CREDIT_DIST_SEND_COMPLETE);
}
spin_unlock_bh(&target->tx_lock);
if (tot_credits)
htc_chk_ep_txq(target);
}
static int htc_parse_trailer(struct htc_target *target,
struct htc_record_hdr *record,
u8 *record_buf, u32 *next_lk_ahds,
enum htc_endpoint_id endpoint,
int *n_lk_ahds)
{
struct htc_bundle_lkahd_rpt *bundle_lkahd_rpt;
struct htc_lookahead_report *lk_ahd;
int len;
switch (record->rec_id) {
case HTC_RECORD_CREDITS:
len = record->len / sizeof(struct htc_credit_report);
if (!len) {
WARN_ON(1);
return -EINVAL;
}
htc_proc_cred_rpt(target,
(struct htc_credit_report *) record_buf,
len, endpoint);
break;
case HTC_RECORD_LOOKAHEAD:
len = record->len / sizeof(*lk_ahd);
if (!len) {
WARN_ON(1);
return -EINVAL;
}
lk_ahd = (struct htc_lookahead_report *) record_buf;
if ((lk_ahd->pre_valid == ((~lk_ahd->post_valid) & 0xFF)) &&
next_lk_ahds) {
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx lk_ahd found pre_valid 0x%x post_valid 0x%x\n",
lk_ahd->pre_valid, lk_ahd->post_valid);
/* look ahead bytes are valid, copy them over */
memcpy((u8 *)&next_lk_ahds[0], lk_ahd->lk_ahd, 4);
ath6kl_dbg_dump(ATH6KL_DBG_HTC,
"htc rx next look ahead",
"", next_lk_ahds, 4);
*n_lk_ahds = 1;
}
break;
case HTC_RECORD_LOOKAHEAD_BUNDLE:
len = record->len / sizeof(*bundle_lkahd_rpt);
if (!len || (len > HTC_HOST_MAX_MSG_PER_BUNDLE)) {
WARN_ON(1);
return -EINVAL;
}
if (next_lk_ahds) {
int i;
bundle_lkahd_rpt =
(struct htc_bundle_lkahd_rpt *) record_buf;
ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx bundle lk_ahd",
"", record_buf, record->len);
for (i = 0; i < len; i++) {
memcpy((u8 *)&next_lk_ahds[i],
bundle_lkahd_rpt->lk_ahd, 4);
bundle_lkahd_rpt++;
}
*n_lk_ahds = i;
}
break;
default:
ath6kl_err("unhandled record: id:%d len:%d\n",
record->rec_id, record->len);
break;
}
return 0;
}
static int htc_proc_trailer(struct htc_target *target,
u8 *buf, int len, u32 *next_lk_ahds,
int *n_lk_ahds, enum htc_endpoint_id endpoint)
{
struct htc_record_hdr *record;
int orig_len;
int status;
u8 *record_buf;
u8 *orig_buf;
ath6kl_dbg(ATH6KL_DBG_HTC, "htc rx trailer len %d\n", len);
ath6kl_dbg_dump(ATH6KL_DBG_HTC, NULL, "", buf, len);
orig_buf = buf;
orig_len = len;
status = 0;
while (len > 0) {
if (len < sizeof(struct htc_record_hdr)) {
status = -ENOMEM;
break;
}
/* these are byte aligned structs */
record = (struct htc_record_hdr *) buf;
len -= sizeof(struct htc_record_hdr);
buf += sizeof(struct htc_record_hdr);
if (record->len > len) {
ath6kl_err("invalid record len: %d (id:%d) buf has: %d bytes left\n",
record->len, record->rec_id, len);
status = -ENOMEM;
break;
}
record_buf = buf;
status = htc_parse_trailer(target, record, record_buf,
next_lk_ahds, endpoint, n_lk_ahds);
if (status)
break;
/* advance buffer past this record for next time around */
buf += record->len;
len -= record->len;
}
if (status)
ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx bad trailer",
"", orig_buf, orig_len);
return status;
}
static int ath6kl_htc_rx_process_hdr(struct htc_target *target,
struct htc_packet *packet,
u32 *next_lkahds, int *n_lkahds)
{
int status = 0;
u16 payload_len;
u32 lk_ahd;
struct htc_frame_hdr *htc_hdr = (struct htc_frame_hdr *)packet->buf;
if (n_lkahds != NULL)
*n_lkahds = 0;
/*
* NOTE: we cannot assume the alignment of buf, so we use the safe
* macros to retrieve 16 bit fields.
*/
payload_len = le16_to_cpu(get_unaligned(&htc_hdr->payld_len));
memcpy((u8 *)&lk_ahd, packet->buf, sizeof(lk_ahd));
if (packet->info.rx.rx_flags & HTC_RX_PKT_REFRESH_HDR) {
/*
* Refresh the expected header and the actual length as it
* was unknown when this packet was grabbed as part of the
* bundle.
*/
packet->info.rx.exp_hdr = lk_ahd;
packet->act_len = payload_len + HTC_HDR_LENGTH;
/* validate the actual header that was refreshed */
if (packet->act_len > packet->buf_len) {
ath6kl_err("refreshed hdr payload len (%d) in bundled recv is invalid (hdr: 0x%X)\n",
payload_len, lk_ahd);
/*
* Limit this to max buffer just to print out some
* of the buffer.
*/
packet->act_len = min(packet->act_len, packet->buf_len);
status = -ENOMEM;
goto fail_rx;
}
if (packet->endpoint != htc_hdr->eid) {
ath6kl_err("refreshed hdr ep (%d) does not match expected ep (%d)\n",
htc_hdr->eid, packet->endpoint);
status = -ENOMEM;
goto fail_rx;
}
}
if (lk_ahd != packet->info.rx.exp_hdr) {
ath6kl_err("%s(): lk_ahd mismatch! (pPkt:0x%p flags:0x%X)\n",
__func__, packet, packet->info.rx.rx_flags);
ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx expected lk_ahd",
"", &packet->info.rx.exp_hdr, 4);
ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx current header",
"", (u8 *)&lk_ahd, sizeof(lk_ahd));
status = -ENOMEM;
goto fail_rx;
}
if (htc_hdr->flags & HTC_FLG_RX_TRAILER) {
if (htc_hdr->ctrl[0] < sizeof(struct htc_record_hdr) ||
htc_hdr->ctrl[0] > payload_len) {
ath6kl_err("%s(): invalid hdr (payload len should be :%d, CB[0] is:%d)\n",
__func__, payload_len, htc_hdr->ctrl[0]);
status = -ENOMEM;
goto fail_rx;
}
if (packet->info.rx.rx_flags & HTC_RX_PKT_IGNORE_LOOKAHEAD) {
next_lkahds = NULL;
n_lkahds = NULL;
}
status = htc_proc_trailer(target, packet->buf + HTC_HDR_LENGTH
+ payload_len - htc_hdr->ctrl[0],
htc_hdr->ctrl[0], next_lkahds,
n_lkahds, packet->endpoint);
if (status)
goto fail_rx;
packet->act_len -= htc_hdr->ctrl[0];
}
packet->buf += HTC_HDR_LENGTH;
packet->act_len -= HTC_HDR_LENGTH;
fail_rx:
if (status)
ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx bad packet",
"", packet->buf, packet->act_len);
return status;
}
static void ath6kl_htc_rx_complete(struct htc_endpoint *endpoint,
struct htc_packet *packet)
{
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx complete ep %d packet 0x%p\n",
endpoint->eid, packet);
endpoint->ep_cb.rx(endpoint->target, packet);
}
static int ath6kl_htc_rx_bundle(struct htc_target *target,
struct list_head *rxq,
struct list_head *sync_compq,
int *n_pkt_fetched, bool part_bundle)
{
struct hif_scatter_req *scat_req;
struct htc_packet *packet;
int rem_space = target->max_rx_bndl_sz;
int n_scat_pkt, status = 0, i, len;
n_scat_pkt = get_queue_depth(rxq);
n_scat_pkt = min(n_scat_pkt, target->msg_per_bndl_max);
if ((get_queue_depth(rxq) - n_scat_pkt) > 0) {
/*
* We were forced to split this bundle receive operation
* all packets in this partial bundle must have their
* lookaheads ignored.
*/
part_bundle = true;
/*
* This would only happen if the target ignored our max
* bundle limit.
*/
ath6kl_warn("%s(): partial bundle detected num:%d , %d\n",
__func__, get_queue_depth(rxq), n_scat_pkt);
}
len = 0;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx bundle depth %d pkts %d\n",
get_queue_depth(rxq), n_scat_pkt);
scat_req = hif_scatter_req_get(target->dev->ar);
if (scat_req == NULL)
goto fail_rx_pkt;
for (i = 0; i < n_scat_pkt; i++) {
int pad_len;
packet = list_first_entry(rxq, struct htc_packet, list);
list_del(&packet->list);
pad_len = CALC_TXRX_PADDED_LEN(target,
packet->act_len);
if ((rem_space - pad_len) < 0) {
list_add(&packet->list, rxq);
break;
}
rem_space -= pad_len;
if (part_bundle || (i < (n_scat_pkt - 1)))
/*
* Packet 0..n-1 cannot be checked for look-aheads
* since we are fetching a bundle the last packet
* however can have it's lookahead used
*/
packet->info.rx.rx_flags |=
HTC_RX_PKT_IGNORE_LOOKAHEAD;
/* NOTE: 1 HTC packet per scatter entry */
scat_req->scat_list[i].buf = packet->buf;
scat_req->scat_list[i].len = pad_len;
packet->info.rx.rx_flags |= HTC_RX_PKT_PART_OF_BUNDLE;
list_add_tail(&packet->list, sync_compq);
WARN_ON(!scat_req->scat_list[i].len);
len += scat_req->scat_list[i].len;
}
scat_req->len = len;
scat_req->scat_entries = i;
status = ath6kl_hif_submit_scat_req(target->dev, scat_req, true);
if (!status)
*n_pkt_fetched = i;
/* free scatter request */
hif_scatter_req_add(target->dev->ar, scat_req);
fail_rx_pkt:
return status;
}
static int ath6kl_htc_rx_process_packets(struct htc_target *target,
struct list_head *comp_pktq,
u32 lk_ahds[],
int *n_lk_ahd)
{
struct htc_packet *packet, *tmp_pkt;
struct htc_endpoint *ep;
int status = 0;
list_for_each_entry_safe(packet, tmp_pkt, comp_pktq, list) {
ep = &target->endpoint[packet->endpoint];
/* process header for each of the recv packet */
status = ath6kl_htc_rx_process_hdr(target, packet, lk_ahds,
n_lk_ahd);
if (status)
return status;
list_del(&packet->list);
if (list_empty(comp_pktq)) {
/*
* Last packet's more packet flag is set
* based on the lookahead.
*/
if (*n_lk_ahd > 0)
ath6kl_htc_rx_set_indicate(lk_ahds[0],
ep, packet);
} else
/*
* Packets in a bundle automatically have
* this flag set.
*/
packet->info.rx.indicat_flags |=
HTC_RX_FLAGS_INDICATE_MORE_PKTS;
ath6kl_htc_rx_update_stats(ep, *n_lk_ahd);
if (packet->info.rx.rx_flags & HTC_RX_PKT_PART_OF_BUNDLE)
ep->ep_st.rx_bundl += 1;
ath6kl_htc_rx_complete(ep, packet);
}
return status;
}
static int ath6kl_htc_rx_fetch(struct htc_target *target,
struct list_head *rx_pktq,
struct list_head *comp_pktq)
{
int fetched_pkts;
bool part_bundle = false;
int status = 0;
struct list_head tmp_rxq;
struct htc_packet *packet, *tmp_pkt;
/* now go fetch the list of HTC packets */
while (!list_empty(rx_pktq)) {
fetched_pkts = 0;
INIT_LIST_HEAD(&tmp_rxq);
if (target->rx_bndl_enable && (get_queue_depth(rx_pktq) > 1)) {
/*
* There are enough packets to attempt a
* bundle transfer and recv bundling is
* allowed.
*/
status = ath6kl_htc_rx_bundle(target, rx_pktq,
&tmp_rxq,
&fetched_pkts,
part_bundle);
if (status)
goto fail_rx;
if (!list_empty(rx_pktq))
part_bundle = true;
list_splice_tail_init(&tmp_rxq, comp_pktq);
}
if (!fetched_pkts) {
packet = list_first_entry(rx_pktq, struct htc_packet,
list);
/* fully synchronous */
packet->completion = NULL;
if (!list_is_singular(rx_pktq))
/*
* look_aheads in all packet
* except the last one in the
* bundle must be ignored
*/
packet->info.rx.rx_flags |=
HTC_RX_PKT_IGNORE_LOOKAHEAD;
/* go fetch the packet */
status = ath6kl_htc_rx_packet(target, packet,
packet->act_len);
list_move_tail(&packet->list, &tmp_rxq);
if (status)
goto fail_rx;
list_splice_tail_init(&tmp_rxq, comp_pktq);
}
}
return 0;
fail_rx:
/*
* Cleanup any packets we allocated but didn't use to
* actually fetch any packets.
*/
list_for_each_entry_safe(packet, tmp_pkt, rx_pktq, list) {
list_del(&packet->list);
htc_reclaim_rxbuf(target, packet,
&target->endpoint[packet->endpoint]);
}
list_for_each_entry_safe(packet, tmp_pkt, &tmp_rxq, list) {
list_del(&packet->list);
htc_reclaim_rxbuf(target, packet,
&target->endpoint[packet->endpoint]);
}
return status;
}
int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target,
u32 msg_look_ahead, int *num_pkts)
{
struct htc_packet *packets, *tmp_pkt;
struct htc_endpoint *endpoint;
struct list_head rx_pktq, comp_pktq;
int status = 0;
u32 look_aheads[HTC_HOST_MAX_MSG_PER_BUNDLE];
int num_look_ahead = 1;
enum htc_endpoint_id id;
int n_fetched = 0;
INIT_LIST_HEAD(&comp_pktq);
*num_pkts = 0;
/*
* On first entry copy the look_aheads into our temp array for
* processing
*/
look_aheads[0] = msg_look_ahead;
while (true) {
/*
* First lookahead sets the expected endpoint IDs for all
* packets in a bundle.
*/
id = ((struct htc_frame_hdr *)&look_aheads[0])->eid;
endpoint = &target->endpoint[id];
if (id >= ENDPOINT_MAX) {
ath6kl_err("MsgPend, invalid endpoint in look-ahead: %d\n",
id);
status = -ENOMEM;
break;
}
INIT_LIST_HEAD(&rx_pktq);
INIT_LIST_HEAD(&comp_pktq);
/*
* Try to allocate as many HTC RX packets indicated by the
* look_aheads.
*/
status = ath6kl_htc_rx_alloc(target, look_aheads,
num_look_ahead, endpoint,
&rx_pktq);
if (status)
break;
if (get_queue_depth(&rx_pktq) >= 2)
/*
* A recv bundle was detected, force IRQ status
* re-check again
*/
target->chk_irq_status_cnt = 1;
n_fetched += get_queue_depth(&rx_pktq);
num_look_ahead = 0;
status = ath6kl_htc_rx_fetch(target, &rx_pktq, &comp_pktq);
if (!status)
ath6kl_htc_rx_chk_water_mark(endpoint);
/* Process fetched packets */
status = ath6kl_htc_rx_process_packets(target, &comp_pktq,
look_aheads,
&num_look_ahead);
if (!num_look_ahead || status)
break;
/*
* For SYNCH processing, if we get here, we are running
* through the loop again due to a detected lookahead. Set
* flag that we should re-check IRQ status registers again
* before leaving IRQ processing, this can net better
* performance in high throughput situations.
*/
target->chk_irq_status_cnt = 1;
}
if (status) {
ath6kl_err("failed to get pending recv messages: %d\n",
status);
/* cleanup any packets in sync completion queue */
list_for_each_entry_safe(packets, tmp_pkt, &comp_pktq, list) {
list_del(&packets->list);
htc_reclaim_rxbuf(target, packets,
&target->endpoint[packets->endpoint]);
}
if (target->htc_flags & HTC_OP_STATE_STOPPING) {
ath6kl_warn("host is going to stop blocking receiver for htc_stop\n");
ath6kl_hif_rx_control(target->dev, false);
}
}
/*
* Before leaving, check to see if host ran out of buffers and
* needs to stop the receiver.
*/
if (target->rx_st_flags & HTC_RECV_WAIT_BUFFERS) {
ath6kl_warn("host has no rx buffers blocking receiver to prevent overrun\n");
ath6kl_hif_rx_control(target->dev, false);
}
*num_pkts = n_fetched;
return status;
}
/*
* Synchronously wait for a control message from the target,
* This function is used at initialization time ONLY. At init messages
* on ENDPOINT 0 are expected.
*/
static struct htc_packet *htc_wait_for_ctrl_msg(struct htc_target *target)
{
struct htc_packet *packet = NULL;
struct htc_frame_hdr *htc_hdr;
u32 look_ahead;
if (ath6kl_hif_poll_mboxmsg_rx(target->dev, &look_ahead,
HTC_TARGET_RESPONSE_TIMEOUT))
return NULL;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx wait ctrl look_ahead 0x%X\n", look_ahead);
htc_hdr = (struct htc_frame_hdr *)&look_ahead;
if (htc_hdr->eid != ENDPOINT_0)
return NULL;
packet = htc_get_control_buf(target, false);
if (!packet)
return NULL;
packet->info.rx.rx_flags = 0;
packet->info.rx.exp_hdr = look_ahead;
packet->act_len = le16_to_cpu(htc_hdr->payld_len) + HTC_HDR_LENGTH;
if (packet->act_len > packet->buf_len)
goto fail_ctrl_rx;
/* we want synchronous operation */
packet->completion = NULL;
/* get the message from the device, this will block */
if (ath6kl_htc_rx_packet(target, packet, packet->act_len))
goto fail_ctrl_rx;
/* process receive header */
packet->status = ath6kl_htc_rx_process_hdr(target, packet, NULL, NULL);
if (packet->status) {
ath6kl_err("htc_wait_for_ctrl_msg, ath6kl_htc_rx_process_hdr failed (status = %d)\n",
packet->status);
goto fail_ctrl_rx;
}
return packet;
fail_ctrl_rx:
if (packet != NULL) {
htc_rxpkt_reset(packet);
reclaim_rx_ctrl_buf(target, packet);
}
return NULL;
}
static int ath6kl_htc_mbox_add_rxbuf_multiple(struct htc_target *target,
struct list_head *pkt_queue)
{
struct htc_endpoint *endpoint;
struct htc_packet *first_pkt;
bool rx_unblock = false;
int status = 0, depth;
if (list_empty(pkt_queue))
return -ENOMEM;
first_pkt = list_first_entry(pkt_queue, struct htc_packet, list);
if (first_pkt->endpoint >= ENDPOINT_MAX)
return status;
depth = get_queue_depth(pkt_queue);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx add multiple ep id %d cnt %d len %d\n",
first_pkt->endpoint, depth, first_pkt->buf_len);
endpoint = &target->endpoint[first_pkt->endpoint];
if (target->htc_flags & HTC_OP_STATE_STOPPING) {
struct htc_packet *packet, *tmp_pkt;
/* walk through queue and mark each one canceled */
list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) {
packet->status = -ECANCELED;
list_del(&packet->list);
ath6kl_htc_rx_complete(endpoint, packet);
}
return status;
}
spin_lock_bh(&target->rx_lock);
list_splice_tail_init(pkt_queue, &endpoint->rx_bufq);
/* check if we are blocked waiting for a new buffer */
if (target->rx_st_flags & HTC_RECV_WAIT_BUFFERS) {
if (target->ep_waiting == first_pkt->endpoint) {
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx blocked on ep %d, unblocking\n",
target->ep_waiting);
target->rx_st_flags &= ~HTC_RECV_WAIT_BUFFERS;
target->ep_waiting = ENDPOINT_MAX;
rx_unblock = true;
}
}
spin_unlock_bh(&target->rx_lock);
if (rx_unblock && !(target->htc_flags & HTC_OP_STATE_STOPPING))
/* TODO : implement a buffer threshold count? */
ath6kl_hif_rx_control(target->dev, true);
return status;
}
static void ath6kl_htc_mbox_flush_rx_buf(struct htc_target *target)
{
struct htc_endpoint *endpoint;
struct htc_packet *packet, *tmp_pkt;
int i;
for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) {
endpoint = &target->endpoint[i];
if (!endpoint->svc_id)
/* not in use.. */
continue;
spin_lock_bh(&target->rx_lock);
list_for_each_entry_safe(packet, tmp_pkt,
&endpoint->rx_bufq, list) {
list_del(&packet->list);
spin_unlock_bh(&target->rx_lock);
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc rx flush pkt 0x%p len %d ep %d\n",
packet, packet->buf_len,
packet->endpoint);
/*
* packets in rx_bufq of endpoint 0 have originally
* been queued from target->free_ctrl_rxbuf where
* packet and packet->buf_start are allocated
* separately using kmalloc(). For other endpoint
* rx_bufq, it is allocated as skb where packet is
* skb->head. Take care of this difference while freeing
* the memory.
*/
if (packet->endpoint == ENDPOINT_0) {
kfree(packet->buf_start);
kfree(packet);
} else {
dev_kfree_skb(packet->pkt_cntxt);
}
spin_lock_bh(&target->rx_lock);
}
spin_unlock_bh(&target->rx_lock);
}
}
static int ath6kl_htc_mbox_conn_service(struct htc_target *target,
struct htc_service_connect_req *conn_req,
struct htc_service_connect_resp *conn_resp)
{
struct htc_packet *rx_pkt = NULL;
struct htc_packet *tx_pkt = NULL;
struct htc_conn_service_resp *resp_msg;
struct htc_conn_service_msg *conn_msg;
struct htc_endpoint *endpoint;
enum htc_endpoint_id assigned_ep = ENDPOINT_MAX;
unsigned int max_msg_sz = 0;
int status = 0;
u16 msg_id;
ath6kl_dbg(ATH6KL_DBG_HTC,
"htc connect service target 0x%p service id 0x%x\n",
target, conn_req->svc_id);
if (conn_req->svc_id == HTC_CTRL_RSVD_SVC) {
/* special case for pseudo control service */
assigned_ep = ENDPOINT_0;
max_msg_sz = HTC_MAX_CTRL_MSG_LEN;
} else {
/* allocate a packet to send to the target */
tx_pkt = htc_get_control_buf(target, true);
if (!tx_pkt)
return -ENOMEM;
conn_msg = (struct htc_conn_service_msg *)tx_pkt->buf;
memset(conn_msg, 0, sizeof(*conn_msg));
conn_msg->msg_id = cpu_to_le16(HTC_MSG_CONN_SVC_ID);
conn_msg->svc_id = cpu_to_le16(conn_req->svc_id);
conn_msg->conn_flags = cpu_to_le16(conn_req->conn_flags);
set_htc_pkt_info(tx_pkt, NULL, (u8 *) conn_msg,
sizeof(*conn_msg) + conn_msg->svc_meta_len,
ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG);
/* we want synchronous operation */
tx_pkt->completion = NULL;
ath6kl_htc_tx_prep_pkt(tx_pkt, 0, 0, 0);
status = ath6kl_htc_tx_issue(target, tx_pkt);
if (status)
goto fail_tx;
/* wait for response */
rx_pkt = htc_wait_for_ctrl_msg(target);
if (!rx_pkt) {
status = -ENOMEM;
goto fail_tx;
}
resp_msg = (struct htc_conn_service_resp *)rx_pkt->buf;
msg_id = le16_to_cpu(resp_msg->msg_id);
if ((msg_id != HTC_MSG_CONN_SVC_RESP_ID) ||
(rx_pkt->act_len < sizeof(*resp_msg))) {
status = -ENOMEM;
goto fail_tx;
}
conn_resp->resp_code = resp_msg->status;
/* check response status */
if (resp_msg->status != HTC_SERVICE_SUCCESS) {
ath6kl_err("target failed service 0x%X connect request (status:%d)\n",
resp_msg->svc_id, resp_msg->status);
status = -ENOMEM;
goto fail_tx;
}
assigned_ep = (enum htc_endpoint_id)resp_msg->eid;
max_msg_sz = le16_to_cpu(resp_msg->max_msg_sz);
}
if (assigned_ep >= ENDPOINT_MAX || !max_msg_sz) {
status = -ENOMEM;
goto fail_tx;
}
endpoint = &target->endpoint[assigned_ep];
endpoint->eid = assigned_ep;
if (endpoint->svc_id) {
status = -ENOMEM;
goto fail_tx;
}
/* return assigned endpoint to caller */
conn_resp->endpoint = assigned_ep;
conn_resp->len_max = max_msg_sz;
/* setup the endpoint */
/* this marks the endpoint in use */
endpoint->svc_id = conn_req->svc_id;
endpoint->max_txq_depth = conn_req->max_txq_depth;
endpoint->len_max = max_msg_sz;
endpoint->ep_cb = conn_req->ep_cb;
endpoint->cred_dist.svc_id = conn_req->svc_id;
endpoint->cred_dist.htc_ep = endpoint;
endpoint->cred_dist.endpoint = assigned_ep;
endpoint->cred_dist.cred_sz = target->tgt_cred_sz;
switch (endpoint->svc_id) {
case WMI_DATA_BK_SVC:
endpoint->tx_drop_packet_threshold = MAX_DEF_COOKIE_NUM / 3;
break;
default:
endpoint->tx_drop_packet_threshold = MAX_HI_COOKIE_NUM;
break;
}
if (conn_req->max_rxmsg_sz) {
/*
* Override cred_per_msg calculation, this optimizes
* the credit-low indications since the host will actually
* issue smaller messages in the Send path.
*/
if (conn_req->max_rxmsg_sz > max_msg_sz) {
status = -ENOMEM;
goto fail_tx;
}
endpoint->cred_dist.cred_per_msg =
conn_req->max_rxmsg_sz / target->tgt_cred_sz;
} else
endpoint->cred_dist.cred_per_msg =
max_msg_sz / target->tgt_cred_sz;
if (!endpoint->cred_dist.cred_per_msg)
endpoint->cred_dist.cred_per_msg = 1;
/* save local connection flags */
endpoint->conn_flags = conn_req->flags;
fail_tx:
if (tx_pkt)
htc_reclaim_txctrl_buf(target, tx_pkt);
if (rx_pkt) {
htc_rxpkt_reset(rx_pkt);
reclaim_rx_ctrl_buf(target, rx_pkt);
}
return status;
}
static void reset_ep_state(struct htc_target *target)
{
struct htc_endpoint *endpoint;
int i;
for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) {
endpoint = &target->endpoint[i];
memset(&endpoint->cred_dist, 0, sizeof(endpoint->cred_dist));
endpoint->svc_id = 0;
endpoint->len_max = 0;
endpoint->max_txq_depth = 0;
memset(&endpoint->ep_st, 0,
sizeof(endpoint->ep_st));
INIT_LIST_HEAD(&endpoint->rx_bufq);
INIT_LIST_HEAD(&endpoint->txq);
endpoint->target = target;
}
/* reset distribution list */
/* FIXME: free existing entries */
INIT_LIST_HEAD(&target->cred_dist_list);
}
static int ath6kl_htc_mbox_get_rxbuf_num(struct htc_target *target,
enum htc_endpoint_id endpoint)
{
int num;
spin_lock_bh(&target->rx_lock);
num = get_queue_depth(&(target->endpoint[endpoint].rx_bufq));
spin_unlock_bh(&target->rx_lock);
return num;
}
static void htc_setup_msg_bndl(struct htc_target *target)
{
/* limit what HTC can handle */
target->msg_per_bndl_max = min(HTC_HOST_MAX_MSG_PER_BUNDLE,
target->msg_per_bndl_max);
if (ath6kl_hif_enable_scatter(target->dev->ar)) {
target->msg_per_bndl_max = 0;
return;
}
/* limit bundle what the device layer can handle */
target->msg_per_bndl_max = min(target->max_scat_entries,
target->msg_per_bndl_max);
ath6kl_dbg(ATH6KL_DBG_BOOT,
"htc bundling allowed msg_per_bndl_max %d\n",
target->msg_per_bndl_max);
/* Max rx bundle size is limited by the max tx bundle size */
target->max_rx_bndl_sz = target->max_xfer_szper_scatreq;
/* Max tx bundle size if limited by the extended mbox address range */
target->max_tx_bndl_sz = min(HIF_MBOX0_EXT_WIDTH,
target->max_xfer_szper_scatreq);
ath6kl_dbg(ATH6KL_DBG_BOOT, "htc max_rx_bndl_sz %d max_tx_bndl_sz %d\n",
target->max_rx_bndl_sz, target->max_tx_bndl_sz);
if (target->max_tx_bndl_sz)
/* tx_bndl_mask is enabled per AC, each has 1 bit */
target->tx_bndl_mask = (1 << WMM_NUM_AC) - 1;
if (target->max_rx_bndl_sz)
target->rx_bndl_enable = true;
if ((target->tgt_cred_sz % target->block_sz) != 0) {
ath6kl_warn("credit size: %d is not block aligned! Disabling send bundling\n",
target->tgt_cred_sz);
/*
* Disallow send bundling since the credit size is
* not aligned to a block size the I/O block
* padding will spill into the next credit buffer
* which is fatal.
*/
target->tx_bndl_mask = 0;
}
}
static int ath6kl_htc_mbox_wait_target(struct htc_target *target)
{
struct htc_packet *packet = NULL;
struct htc_ready_ext_msg *rdy_msg;
struct htc_service_connect_req connect;
struct htc_service_connect_resp resp;
int status;
/* FIXME: remove once USB support is implemented */
if (target->dev->ar->hif_type == ATH6KL_HIF_TYPE_USB) {
ath6kl_err("HTC doesn't support USB yet. Patience!\n");
return -EOPNOTSUPP;
}
/* we should be getting 1 control message that the target is ready */
packet = htc_wait_for_ctrl_msg(target);
if (!packet)
return -ENOMEM;
/* we controlled the buffer creation so it's properly aligned */
rdy_msg = (struct htc_ready_ext_msg *)packet->buf;
if ((le16_to_cpu(rdy_msg->ver2_0_info.msg_id) != HTC_MSG_READY_ID) ||
(packet->act_len < sizeof(struct htc_ready_msg))) {
status = -ENOMEM;
goto fail_wait_target;
}
if (!rdy_msg->ver2_0_info.cred_cnt || !rdy_msg->ver2_0_info.cred_sz) {
status = -ENOMEM;
goto fail_wait_target;
}
target->tgt_creds = le16_to_cpu(rdy_msg->ver2_0_info.cred_cnt);
target->tgt_cred_sz = le16_to_cpu(rdy_msg->ver2_0_info.cred_sz);
ath6kl_dbg(ATH6KL_DBG_BOOT,
"htc target ready credits %d size %d\n",
target->tgt_creds, target->tgt_cred_sz);
/* check if this is an extended ready message */
if (packet->act_len >= sizeof(struct htc_ready_ext_msg)) {
/* this is an extended message */
target->htc_tgt_ver = rdy_msg->htc_ver;
target->msg_per_bndl_max = rdy_msg->msg_per_htc_bndl;
} else {
/* legacy */
target->htc_tgt_ver = HTC_VERSION_2P0;
target->msg_per_bndl_max = 0;
}
ath6kl_dbg(ATH6KL_DBG_BOOT, "htc using protocol %s (%d)\n",
(target->htc_tgt_ver == HTC_VERSION_2P0) ? "2.0" : ">= 2.1",
target->htc_tgt_ver);
if (target->msg_per_bndl_max > 0)
htc_setup_msg_bndl(target);
/* setup our pseudo HTC control endpoint connection */
memset(&connect, 0, sizeof(connect));
memset(&resp, 0, sizeof(resp));
connect.ep_cb.rx = htc_ctrl_rx;
connect.ep_cb.rx_refill = NULL;
connect.ep_cb.tx_full = NULL;
connect.max_txq_depth = NUM_CONTROL_BUFFERS;
connect.svc_id = HTC_CTRL_RSVD_SVC;
/* connect fake service */
status = ath6kl_htc_mbox_conn_service((void *)target, &connect, &resp);
if (status)
/*
* FIXME: this call doesn't make sense, the caller should
* call ath6kl_htc_mbox_cleanup() when it wants remove htc
*/
ath6kl_hif_cleanup_scatter(target->dev->ar);
fail_wait_target:
if (packet) {
htc_rxpkt_reset(packet);
reclaim_rx_ctrl_buf(target, packet);
}
return status;
}
/*
* Start HTC, enable interrupts and let the target know
* host has finished setup.
*/
static int ath6kl_htc_mbox_start(struct htc_target *target)
{
struct htc_packet *packet;
int status;
memset(&target->dev->irq_proc_reg, 0,
sizeof(target->dev->irq_proc_reg));
/* Disable interrupts at the chip level */
ath6kl_hif_disable_intrs(target->dev);
target->htc_flags = 0;
target->rx_st_flags = 0;
/* Push control receive buffers into htc control endpoint */
while ((packet = htc_get_control_buf(target, false)) != NULL) {
status = htc_add_rxbuf(target, packet);
if (status)
return status;
}
/* NOTE: the first entry in the distribution list is ENDPOINT_0 */
ath6kl_credit_init(target->credit_info, &target->cred_dist_list,
target->tgt_creds);
dump_cred_dist_stats(target);
/* Indicate to the target of the setup completion */
status = htc_setup_tx_complete(target);
if (status)
return status;
/* unmask interrupts */
status = ath6kl_hif_unmask_intrs(target->dev);
if (status)
ath6kl_htc_mbox_stop(target);
return status;
}
static int ath6kl_htc_reset(struct htc_target *target)
{
u32 block_size, ctrl_bufsz;
struct htc_packet *packet;
int i;
reset_ep_state(target);
block_size = target->dev->ar->mbox_info.block_size;
ctrl_bufsz = (block_size > HTC_MAX_CTRL_MSG_LEN) ?
(block_size + HTC_HDR_LENGTH) :
(HTC_MAX_CTRL_MSG_LEN + HTC_HDR_LENGTH);
for (i = 0; i < NUM_CONTROL_BUFFERS; i++) {
packet = kzalloc(sizeof(*packet), GFP_KERNEL);
if (!packet)
return -ENOMEM;
packet->buf_start = kzalloc(ctrl_bufsz, GFP_KERNEL);
if (!packet->buf_start) {
kfree(packet);
return -ENOMEM;
}
packet->buf_len = ctrl_bufsz;
if (i < NUM_CONTROL_RX_BUFFERS) {
packet->act_len = 0;
packet->buf = packet->buf_start;
packet->endpoint = ENDPOINT_0;
list_add_tail(&packet->list, &target->free_ctrl_rxbuf);
} else
list_add_tail(&packet->list, &target->free_ctrl_txbuf);
}
return 0;
}
/* htc_stop: stop interrupt reception, and flush all queued buffers */
static void ath6kl_htc_mbox_stop(struct htc_target *target)
{
spin_lock_bh(&target->htc_lock);
target->htc_flags |= HTC_OP_STATE_STOPPING;
spin_unlock_bh(&target->htc_lock);
/*
* Masking interrupts is a synchronous operation, when this
* function returns all pending HIF I/O has completed, we can
* safely flush the queues.
*/
ath6kl_hif_mask_intrs(target->dev);
ath6kl_htc_flush_txep_all(target);
ath6kl_htc_mbox_flush_rx_buf(target);
ath6kl_htc_reset(target);
}
static void *ath6kl_htc_mbox_create(struct ath6kl *ar)
{
struct htc_target *target = NULL;
int status = 0;
target = kzalloc(sizeof(*target), GFP_KERNEL);
if (!target) {
ath6kl_err("unable to allocate memory\n");
return NULL;
}
target->dev = kzalloc(sizeof(*target->dev), GFP_KERNEL);
if (!target->dev) {
ath6kl_err("unable to allocate memory\n");
status = -ENOMEM;
goto err_htc_cleanup;
}
spin_lock_init(&target->htc_lock);
spin_lock_init(&target->rx_lock);
spin_lock_init(&target->tx_lock);
INIT_LIST_HEAD(&target->free_ctrl_txbuf);
INIT_LIST_HEAD(&target->free_ctrl_rxbuf);
INIT_LIST_HEAD(&target->cred_dist_list);
target->dev->ar = ar;
target->dev->htc_cnxt = target;
target->ep_waiting = ENDPOINT_MAX;
status = ath6kl_hif_setup(target->dev);
if (status)
goto err_htc_cleanup;
status = ath6kl_htc_reset(target);
if (status)
goto err_htc_cleanup;
return target;
err_htc_cleanup:
ath6kl_htc_mbox_cleanup(target);
return NULL;
}
/* cleanup the HTC instance */
static void ath6kl_htc_mbox_cleanup(struct htc_target *target)
{
struct htc_packet *packet, *tmp_packet;
/* FIXME: remove check once USB support is implemented */
if (target->dev->ar->hif_type != ATH6KL_HIF_TYPE_USB)
ath6kl_hif_cleanup_scatter(target->dev->ar);
list_for_each_entry_safe(packet, tmp_packet,
&target->free_ctrl_txbuf, list) {
list_del(&packet->list);
kfree(packet->buf_start);
kfree(packet);
}
list_for_each_entry_safe(packet, tmp_packet,
&target->free_ctrl_rxbuf, list) {
list_del(&packet->list);
kfree(packet->buf_start);
kfree(packet);
}
kfree(target->dev);
kfree(target);
}
static const struct ath6kl_htc_ops ath6kl_htc_mbox_ops = {
.create = ath6kl_htc_mbox_create,
.wait_target = ath6kl_htc_mbox_wait_target,
.start = ath6kl_htc_mbox_start,
.conn_service = ath6kl_htc_mbox_conn_service,
.tx = ath6kl_htc_mbox_tx,
.stop = ath6kl_htc_mbox_stop,
.cleanup = ath6kl_htc_mbox_cleanup,
.flush_txep = ath6kl_htc_mbox_flush_txep,
.flush_rx_buf = ath6kl_htc_mbox_flush_rx_buf,
.activity_changed = ath6kl_htc_mbox_activity_changed,
.get_rxbuf_num = ath6kl_htc_mbox_get_rxbuf_num,
.add_rxbuf_multiple = ath6kl_htc_mbox_add_rxbuf_multiple,
.credit_setup = ath6kl_htc_mbox_credit_setup,
};
void ath6kl_htc_mbox_attach(struct ath6kl *ar)
{
ar->htc_ops = &ath6kl_htc_mbox_ops;
}