From: Kalle Valo Date: Sun, 25 Mar 2012 14:15:27 +0000 (+0300) Subject: ath6kl: add htc ops X-Git-Tag: firefly_0821_release~3680^2~2381^2~57^2~287^2~60 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=e76ac2bf637defbe3b7fc644813be584b941ff0a;p=firefly-linux-kernel-4.4.55.git ath6kl: add htc ops In preparation for adding HTC pipe implementation add htc-ops.h to make it possible dynamically choose which HTC type is used. Needed for full USB support. Based on the code by Ray Chen . Signed-off-by: Kalle Valo Signed-off-by: Ray Chen Signed-off-by: Kalle Valo --- diff --git a/drivers/net/wireless/ath/ath6kl/Makefile b/drivers/net/wireless/ath/ath6kl/Makefile index 85746c3eb027..f4ac8174b24c 100644 --- a/drivers/net/wireless/ath/ath6kl/Makefile +++ b/drivers/net/wireless/ath/ath6kl/Makefile @@ -25,7 +25,7 @@ obj-$(CONFIG_ATH6KL) += ath6kl_core.o ath6kl_core-y += debug.o ath6kl_core-y += hif.o -ath6kl_core-y += htc.o +ath6kl_core-y += htc_mbox.o ath6kl_core-y += bmi.o ath6kl_core-y += cfg80211.o ath6kl_core-y += init.o diff --git a/drivers/net/wireless/ath/ath6kl/core.c b/drivers/net/wireless/ath/ath6kl/core.c index 45e641f3a41b..bb9fe381c3c6 100644 --- a/drivers/net/wireless/ath/ath6kl/core.c +++ b/drivers/net/wireless/ath/ath6kl/core.c @@ -23,6 +23,7 @@ #include "debug.h" #include "hif-ops.h" +#include "htc-ops.h" #include "cfg80211.h" unsigned int debug_mask; @@ -39,12 +40,21 @@ module_param(uart_debug, uint, 0644); module_param(ath6kl_p2p, uint, 0644); module_param(testmode, uint, 0644); -int ath6kl_core_init(struct ath6kl *ar) +int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type) { struct ath6kl_bmi_target_info targ_info; struct net_device *ndev; int ret = 0, i; + switch (htc_type) { + case ATH6KL_HTC_TYPE_MBOX: + ath6kl_htc_mbox_attach(ar); + break; + default: + WARN_ON(1); + return -ENOMEM; + } + ar->ath6kl_wq = create_singlethread_workqueue("ath6kl"); if (!ar->ath6kl_wq) return -ENOMEM; diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h index f1ce00314a99..f71c3a7a5e72 100644 --- a/drivers/net/wireless/ath/ath6kl/core.h +++ b/drivers/net/wireless/ath/ath6kl/core.h @@ -462,6 +462,10 @@ enum ath6kl_hif_type { ATH6KL_HIF_TYPE_USB, }; +enum ath6kl_htc_type { + ATH6KL_HTC_TYPE_MBOX, +}; + /* Max number of filters that hw supports */ #define ATH6K_MAX_MC_FILTERS_PER_LIST 7 struct ath6kl_mc_filter { @@ -576,6 +580,7 @@ struct ath6kl { struct ath6kl_bmi bmi; const struct ath6kl_hif_ops *hif_ops; + const struct ath6kl_htc_ops *htc_ops; struct wmi *wmi; int tx_pending[ENDPOINT_MAX]; int total_tx_data_pend; @@ -831,7 +836,7 @@ int ath6kl_init_hw_params(struct ath6kl *ar); void ath6kl_check_wow_status(struct ath6kl *ar); struct ath6kl *ath6kl_core_create(struct device *dev); -int ath6kl_core_init(struct ath6kl *ar); +int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type); void ath6kl_core_cleanup(struct ath6kl *ar); void ath6kl_core_destroy(struct ath6kl *ar); diff --git a/drivers/net/wireless/ath/ath6kl/htc-ops.h b/drivers/net/wireless/ath/ath6kl/htc-ops.h new file mode 100644 index 000000000000..2d4eed55cfd1 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc-ops.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications 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. + */ + +#ifndef HTC_OPS_H +#define HTC_OPS_H + +#include "htc.h" +#include "debug.h" + +static inline void *ath6kl_htc_create(struct ath6kl *ar) +{ + return ar->htc_ops->create(ar); +} + +static inline int ath6kl_htc_wait_target(struct htc_target *target) +{ + return target->dev->ar->htc_ops->wait_target(target); +} + +static inline int ath6kl_htc_start(struct htc_target *target) +{ + return target->dev->ar->htc_ops->start(target); +} + +static inline int ath6kl_htc_conn_service(struct htc_target *target, + struct htc_service_connect_req *req, + struct htc_service_connect_resp *resp) +{ + return target->dev->ar->htc_ops->conn_service(target, req, resp); +} + +static inline int ath6kl_htc_tx(struct htc_target *target, + struct htc_packet *packet) +{ + return target->dev->ar->htc_ops->tx(target, packet); +} + +static inline void ath6kl_htc_stop(struct htc_target *target) +{ + return target->dev->ar->htc_ops->stop(target); +} + +static inline void ath6kl_htc_cleanup(struct htc_target *target) +{ + return target->dev->ar->htc_ops->cleanup(target); +} + +static inline void ath6kl_htc_flush_txep(struct htc_target *target, + enum htc_endpoint_id endpoint, + u16 tag) +{ + return target->dev->ar->htc_ops->flush_txep(target, endpoint, tag); +} + +static inline void ath6kl_htc_flush_rx_buf(struct htc_target *target) +{ + return target->dev->ar->htc_ops->flush_rx_buf(target); +} + +static inline void ath6kl_htc_activity_changed(struct htc_target *target, + enum htc_endpoint_id endpoint, + bool active) +{ + return target->dev->ar->htc_ops->activity_changed(target, endpoint, + active); +} + +static inline int ath6kl_htc_get_rxbuf_num(struct htc_target *target, + enum htc_endpoint_id endpoint) +{ + return target->dev->ar->htc_ops->get_rxbuf_num(target, endpoint); +} + +static inline int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pktq) +{ + return target->dev->ar->htc_ops->add_rxbuf_multiple(target, pktq); +} + +static inline int ath6kl_htc_credit_setup(struct htc_target *target, + struct ath6kl_htc_credit_info *info) +{ + return target->dev->ar->htc_ops->credit_setup(target, info); +} + +static inline void ath6kl_htc_tx_complete(struct ath6kl *ar, + struct sk_buff *skb) +{ + ar->htc_ops->tx_complete(ar, skb); +} + + +static inline void ath6kl_htc_rx_complete(struct ath6kl *ar, + struct sk_buff *skb, u8 pipe) +{ + ar->htc_ops->rx_complete(ar, skb, pipe); +} + + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/htc.c b/drivers/net/wireless/ath/ath6kl/htc.c deleted file mode 100644 index f282772d9121..000000000000 --- a/drivers/net/wireless/ath/ath6kl/htc.c +++ /dev/null @@ -1,2892 +0,0 @@ -/* - * 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 - -#define CALC_TXRX_PADDED_LEN(dev, len) (__ALIGN_MASK((len), (dev)->block_mask)) - -/* 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; - } else if (cur_ep_dist->svc_id == WMI_DATA_BK_SVC) - /* this is the lowest priority data endpoint */ - /* FIXME: this looks fishy, check */ - cred_info->lowestpri_ep_dist = cur_ep_dist->list; - - /* - * 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 - */ - } - - 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 */ -int ath6kl_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; - - 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); - ath6kl_htc_tx_issue(target, 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; -} - -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; - } - } -} - -int ath6kl_htc_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 */ -void ath6kl_htc_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_flush_txep(target, i, HTC_TX_PACKET_TAG_ALL); - } -} - -void ath6kl_htc_indicate_activity_change(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_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; -} - -int ath6kl_htc_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; -} - -void ath6kl_htc_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); - } -} - -int ath6kl_htc_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); -} - -int ath6kl_htc_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; - } -} - -int ath6kl_htc_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_conn_service((void *)target, &connect, &resp); - - if (status) - /* - * FIXME: this call doesn't make sense, the caller should - * call ath6kl_htc_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. - */ -int ath6kl_htc_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_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 */ -void ath6kl_htc_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_flush_rx_buf(target); - - ath6kl_htc_reset(target); -} - -void *ath6kl_htc_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_cleanup(target); - - return NULL; -} - -/* cleanup the HTC instance */ -void ath6kl_htc_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); -} diff --git a/drivers/net/wireless/ath/ath6kl/htc.h b/drivers/net/wireless/ath/ath6kl/htc.h index 0ba8deb2f096..43cb2cf270d6 100644 --- a/drivers/net/wireless/ath/ath6kl/htc.h +++ b/drivers/net/wireless/ath/ath6kl/htc.h @@ -519,6 +519,32 @@ struct htc_control_buffer { u8 *buf; }; +struct ath6kl_htc_ops { + void* (*create)(struct ath6kl *ar); + int (*wait_target)(struct htc_target *target); + int (*start)(struct htc_target *target); + int (*conn_service)(struct htc_target *target, + struct htc_service_connect_req *req, + struct htc_service_connect_resp *resp); + int (*tx)(struct htc_target *target, struct htc_packet *packet); + void (*stop)(struct htc_target *target); + void (*cleanup)(struct htc_target *target); + void (*flush_txep)(struct htc_target *target, + enum htc_endpoint_id endpoint, u16 tag); + void (*flush_rx_buf)(struct htc_target *target); + void (*activity_changed)(struct htc_target *target, + enum htc_endpoint_id endpoint, + bool active); + int (*get_rxbuf_num)(struct htc_target *target, + enum htc_endpoint_id endpoint); + int (*add_rxbuf_multiple)(struct htc_target *target, + struct list_head *pktq); + int (*credit_setup)(struct htc_target *target, + struct ath6kl_htc_credit_info *cred_info); + int (*tx_complete)(struct ath6kl *ar, struct sk_buff *skb); + int (*rx_complete)(struct ath6kl *ar, struct sk_buff *skb, u8 pipe); +}; + struct ath6kl_device; /* our HTC target state */ @@ -569,34 +595,9 @@ struct htc_target { u32 ac_tx_count[WMM_NUM_AC]; }; -void *ath6kl_htc_create(struct ath6kl *ar); -void ath6kl_htc_set_credit_dist(struct htc_target *target, - struct ath6kl_htc_credit_info *cred_info, - u16 svc_pri_order[], int len); -int ath6kl_htc_wait_target(struct htc_target *target); -int ath6kl_htc_start(struct htc_target *target); -int ath6kl_htc_conn_service(struct htc_target *target, - struct htc_service_connect_req *req, - struct htc_service_connect_resp *resp); -int ath6kl_htc_tx(struct htc_target *target, struct htc_packet *packet); -void ath6kl_htc_stop(struct htc_target *target); -void ath6kl_htc_cleanup(struct htc_target *target); -void ath6kl_htc_flush_txep(struct htc_target *target, - enum htc_endpoint_id endpoint, u16 tag); -void ath6kl_htc_flush_rx_buf(struct htc_target *target); -void ath6kl_htc_indicate_activity_change(struct htc_target *target, - enum htc_endpoint_id endpoint, - bool active); -int ath6kl_htc_get_rxbuf_num(struct htc_target *target, - enum htc_endpoint_id endpoint); -int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, - struct list_head *pktq); int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, u32 msg_look_ahead, int *n_pkts); -int ath6kl_credit_setup(struct htc_target *htc_target, - struct ath6kl_htc_credit_info *cred_info); - static inline void set_htc_pkt_info(struct htc_packet *packet, void *context, u8 *buf, unsigned int len, enum htc_endpoint_id eid, u16 tag) @@ -636,4 +637,6 @@ static inline int get_queue_depth(struct list_head *queue) return depth; } +void ath6kl_htc_mbox_attach(struct ath6kl *ar); + #endif diff --git a/drivers/net/wireless/ath/ath6kl/htc_mbox.c b/drivers/net/wireless/ath/ath6kl/htc_mbox.c new file mode 100644 index 000000000000..065e61516d7a --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc_mbox.c @@ -0,0 +1,2923 @@ +/* + * 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 + +#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; + } else if (cur_ep_dist->svc_id == WMI_DATA_BK_SVC) + /* this is the lowest priority data endpoint */ + /* FIXME: this looks fishy, check */ + cred_info->lowestpri_ep_dist = cur_ep_dist->list; + + /* + * 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 + */ + } + + 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; + + 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); + ath6kl_htc_tx_issue(target, 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; +} diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c index d33691e8f128..092e4cddfed3 100644 --- a/drivers/net/wireless/ath/ath6kl/init.c +++ b/drivers/net/wireless/ath/ath6kl/init.c @@ -27,6 +27,7 @@ #include "target.h" #include "debug.h" #include "hif-ops.h" +#include "htc-ops.h" static const struct ath6kl_hw hw_list[] = { { @@ -1510,7 +1511,7 @@ int ath6kl_init_hw_start(struct ath6kl *ar) } /* setup credit distribution */ - ath6kl_credit_setup(ar->htc_target, &ar->credit_state_info); + ath6kl_htc_credit_setup(ar->htc_target, &ar->credit_state_info); /* start HTC */ ret = ath6kl_htc_start(ar->htc_target); diff --git a/drivers/net/wireless/ath/ath6kl/sdio.c b/drivers/net/wireless/ath/ath6kl/sdio.c index 53528648b425..44ea7a742101 100644 --- a/drivers/net/wireless/ath/ath6kl/sdio.c +++ b/drivers/net/wireless/ath/ath6kl/sdio.c @@ -1362,7 +1362,7 @@ static int ath6kl_sdio_probe(struct sdio_func *func, goto err_core_alloc; } - ret = ath6kl_core_init(ar); + ret = ath6kl_core_init(ar, ATH6KL_HTC_TYPE_MBOX); if (ret) { ath6kl_err("Failed to init ath6kl core\n"); goto err_core_alloc; diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c index 5559c9b281b6..fdcc6ee5fe32 100644 --- a/drivers/net/wireless/ath/ath6kl/txrx.c +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -17,6 +17,7 @@ #include "core.h" #include "debug.h" +#include "htc-ops.h" /* * tid - tid_mux0..tid_mux3 @@ -572,7 +573,7 @@ void ath6kl_indicate_tx_activity(void *devt, u8 traffic_class, bool active) notify_htc: /* notify HTC, this may cause credit distribution changes */ - ath6kl_htc_indicate_activity_change(ar->htc_target, eid, active); + ath6kl_htc_activity_changed(ar->htc_target, eid, active); } enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, diff --git a/drivers/net/wireless/ath/ath6kl/usb.c b/drivers/net/wireless/ath/ath6kl/usb.c index 325b1224c2b1..991fbc5ca1f8 100644 --- a/drivers/net/wireless/ath/ath6kl/usb.c +++ b/drivers/net/wireless/ath/ath6kl/usb.c @@ -368,7 +368,7 @@ static int ath6kl_usb_probe(struct usb_interface *interface, ar_usb->ar = ar; - ret = ath6kl_core_init(ar); + ret = ath6kl_core_init(ar, ATH6KL_HTC_TYPE_MBOX); if (ret) { ath6kl_err("Failed to init ath6kl core: %d\n", ret); goto err_core_free;