ath11k: Disabling credit flow for WMI path
authorP Praneesh <quic_ppranees@quicinc.com>
Mon, 15 Nov 2021 09:50:52 +0000 (11:50 +0200)
committerKalle Valo <kvalo@codeaurora.org>
Wed, 17 Nov 2021 07:28:04 +0000 (09:28 +0200)
Firmware credit flow control is enabled for WMI control services,
which expects available tokens should be acquired before sending a
command to the target. Also the token gets released when firmware
receives the command.

This credit-based flow limits driver to send WMI command only
when the token available which is causing WMI commands to timeout and
return -EAGAIN, whereas firmware has enough capability to process the
WMI command. To fix this Tx starvation issue, introduce the ability to
disable the credit flow for the WMI path.

The driver sends WMI configuration for disabling credit flow to firmware
by two ways.
1. By using a global flag
(HTC_MSG_SETUP_COMPLETE_EX_ID msg type flags)
2. By using a local flag
(ATH11K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL = 1 << 3)

Ath11k uses both these configurations to disable credit flow for the
WMI path completely.

Also added a hw_param member for credit flow control by which we can
enable or disable it based on per-target basis. Currently we are disabling
credit flow for IPQ8074, IPQ6018, and QCN9074 as recommended by firmware.

Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.4.0.1-01492-QCAHKSWPL_SILICONZ-1
Tested-on: IPQ6018 hw1.0 AHB WLAN.HK.2.4.0.1-00330-QCAHKSWPL_SILICONZ-1

Co-developed-by: Pravas Kumar Panda <kumarpan@codeaurora.org>
Signed-off-by: Pravas Kumar Panda <kumarpan@codeaurora.org>
Signed-off-by: P Praneesh <quic_ppranees@quicinc.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/1635156494-20059-1-git-send-email-quic_ppranees@quicinc.com
drivers/net/wireless/ath/ath11k/ce.c
drivers/net/wireless/ath/ath11k/ce.h
drivers/net/wireless/ath/ath11k/core.c
drivers/net/wireless/ath/ath11k/htc.c
drivers/net/wireless/ath/ath11k/htc.h
drivers/net/wireless/ath/ath11k/hw.h
drivers/net/wireless/ath/ath11k/wmi.c
drivers/net/wireless/ath/ath11k/wmi.h

index 3bc4242c10596de52686d3df41ea8549ce28439a..aaa7b05ff49d36da66867795ac4b0ca185e8535f 100644 (file)
@@ -14,6 +14,7 @@ const struct ce_attr ath11k_host_ce_config_ipq8074[] = {
                .src_nentries = 16,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE1: target->host HTT + HTC control */
@@ -40,6 +41,7 @@ const struct ce_attr ath11k_host_ce_config_ipq8074[] = {
                .src_nentries = 32,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE4: host->target HTT */
@@ -73,6 +75,7 @@ const struct ce_attr ath11k_host_ce_config_ipq8074[] = {
                .src_nentries = 32,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE8: target autonomous hif_memcpy */
@@ -89,6 +92,7 @@ const struct ce_attr ath11k_host_ce_config_ipq8074[] = {
                .src_nentries = 32,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE10: target->host HTT */
@@ -142,6 +146,7 @@ const struct ce_attr ath11k_host_ce_config_qca6390[] = {
                .src_nentries = 32,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE4: host->target HTT */
@@ -175,6 +180,7 @@ const struct ce_attr ath11k_host_ce_config_qca6390[] = {
                .src_nentries = 32,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE8: target autonomous hif_memcpy */
@@ -220,6 +226,7 @@ const struct ce_attr ath11k_host_ce_config_qcn9074[] = {
                .src_nentries = 32,
                .src_sz_max = 2048,
                .dest_nentries = 0,
+               .send_cb = ath11k_htc_tx_completion_handler,
        },
 
        /* CE4: host->target HTT */
@@ -489,18 +496,32 @@ err_unlock:
        return skb;
 }
 
-static void ath11k_ce_send_done_cb(struct ath11k_ce_pipe *pipe)
+static void ath11k_ce_tx_process_cb(struct ath11k_ce_pipe *pipe)
 {
        struct ath11k_base *ab = pipe->ab;
        struct sk_buff *skb;
+       struct sk_buff_head list;
 
+       __skb_queue_head_init(&list);
        while (!IS_ERR(skb = ath11k_ce_completed_send_next(pipe))) {
                if (!skb)
                        continue;
 
                dma_unmap_single(ab->dev, ATH11K_SKB_CB(skb)->paddr, skb->len,
                                 DMA_TO_DEVICE);
-               dev_kfree_skb_any(skb);
+
+               if ((!pipe->send_cb) || ab->hw_params.credit_flow) {
+                       dev_kfree_skb_any(skb);
+                       continue;
+               }
+
+               __skb_queue_tail(&list, skb);
+       }
+
+       while ((skb = __skb_dequeue(&list))) {
+               ath11k_dbg(ab, ATH11K_DBG_AHB, "tx ce pipe %d len %d\n",
+                          pipe->pipe_num, skb->len);
+               pipe->send_cb(ab, skb);
        }
 }
 
@@ -636,7 +657,7 @@ static int ath11k_ce_alloc_pipe(struct ath11k_base *ab, int ce_id)
        pipe->attr_flags = attr->flags;
 
        if (attr->src_nentries) {
-               pipe->send_cb = ath11k_ce_send_done_cb;
+               pipe->send_cb = attr->send_cb;
                nentries = roundup_pow_of_two(attr->src_nentries);
                desc_sz = ath11k_hal_ce_get_desc_size(HAL_CE_DESC_SRC);
                ring = ath11k_ce_alloc_ring(ab, nentries, desc_sz);
@@ -667,9 +688,10 @@ static int ath11k_ce_alloc_pipe(struct ath11k_base *ab, int ce_id)
 void ath11k_ce_per_engine_service(struct ath11k_base *ab, u16 ce_id)
 {
        struct ath11k_ce_pipe *pipe = &ab->ce.ce_pipe[ce_id];
+       const struct ce_attr *attr = &ab->hw_params.host_ce_config[ce_id];
 
-       if (pipe->send_cb)
-               pipe->send_cb(pipe);
+       if (attr->src_nentries)
+               ath11k_ce_tx_process_cb(pipe);
 
        if (pipe->recv_cb)
                ath11k_ce_recv_process_cb(pipe);
@@ -678,9 +700,10 @@ void ath11k_ce_per_engine_service(struct ath11k_base *ab, u16 ce_id)
 void ath11k_ce_poll_send_completed(struct ath11k_base *ab, u8 pipe_id)
 {
        struct ath11k_ce_pipe *pipe = &ab->ce.ce_pipe[pipe_id];
+       const struct ce_attr *attr =  &ab->hw_params.host_ce_config[pipe_id];
 
-       if ((pipe->attr_flags & CE_ATTR_DIS_INTR) && pipe->send_cb)
-               pipe->send_cb(pipe);
+       if ((pipe->attr_flags & CE_ATTR_DIS_INTR) && attr->src_nentries)
+               ath11k_ce_tx_process_cb(pipe);
 }
 EXPORT_SYMBOL(ath11k_ce_per_engine_service);
 
index 713f766cac223d3c852fbbc48fd150896414b275..8255b6cfab0c767b46e56fde46649b3cc2df4d2f 100644 (file)
@@ -101,6 +101,7 @@ struct ce_attr {
        unsigned int dest_nentries;
 
        void (*recv_cb)(struct ath11k_base *, struct sk_buff *);
+       void (*send_cb)(struct ath11k_base *, struct sk_buff *);
 };
 
 #define CE_DESC_RING_ALIGN 8
@@ -154,7 +155,7 @@ struct ath11k_ce_pipe {
        unsigned int buf_sz;
        unsigned int rx_buf_needed;
 
-       void (*send_cb)(struct ath11k_ce_pipe *);
+       void (*send_cb)(struct ath11k_base *, struct sk_buff *);
        void (*recv_cb)(struct ath11k_base *, struct sk_buff *);
 
        struct tasklet_struct intr_tq;
index 2ba3bf8d3fde265daed7c776e4c4f3e152f57ac6..4e994db996db2e3880bc7a772ed5645f1a17616e 100644 (file)
@@ -81,6 +81,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_suspend = false,
                .hal_desc_sz = sizeof(struct hal_rx_desc_ipq8074),
                .fix_l1ss = true,
+               .credit_flow = false,
                .max_tx_ring = DP_TCL_NUM_RING_MAX,
                .hal_params = &ath11k_hw_hal_params_ipq8074,
                .supports_dynamic_smps_6ghz = false,
@@ -133,6 +134,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_suspend = false,
                .hal_desc_sz = sizeof(struct hal_rx_desc_ipq8074),
                .fix_l1ss = true,
+               .credit_flow = false,
                .max_tx_ring = DP_TCL_NUM_RING_MAX,
                .hal_params = &ath11k_hw_hal_params_ipq8074,
                .supports_dynamic_smps_6ghz = false,
@@ -184,6 +186,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_suspend = true,
                .hal_desc_sz = sizeof(struct hal_rx_desc_ipq8074),
                .fix_l1ss = true,
+               .credit_flow = true,
                .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
                .hal_params = &ath11k_hw_hal_params_qca6390,
                .supports_dynamic_smps_6ghz = false,
@@ -235,6 +238,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_suspend = false,
                .hal_desc_sz = sizeof(struct hal_rx_desc_qcn9074),
                .fix_l1ss = true,
+               .credit_flow = false,
                .max_tx_ring = DP_TCL_NUM_RING_MAX,
                .hal_params = &ath11k_hw_hal_params_ipq8074,
                .supports_dynamic_smps_6ghz = true,
@@ -286,6 +290,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
                .supports_suspend = true,
                .hal_desc_sz = sizeof(struct hal_rx_desc_wcn6855),
                .fix_l1ss = false,
+               .credit_flow = true,
                .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
                .hal_params = &ath11k_hw_hal_params_qca6390,
                .supports_dynamic_smps_6ghz = false,
index 54b1d34724d7d595cab982c7f90261f0bc283358..6913b7494b9bfc83dd2cb41a767cd9d6fd1c4d0e 100644 (file)
@@ -81,6 +81,8 @@ int ath11k_htc_send(struct ath11k_htc *htc,
        struct ath11k_base *ab = htc->ab;
        int credits = 0;
        int ret;
+       bool credit_flow_enabled = (ab->hw_params.credit_flow &&
+                                   ep->tx_credit_flow_enabled);
 
        if (eid >= ATH11K_HTC_EP_COUNT) {
                ath11k_warn(ab, "Invalid endpoint id: %d\n", eid);
@@ -89,7 +91,7 @@ int ath11k_htc_send(struct ath11k_htc *htc,
 
        skb_push(skb, sizeof(struct ath11k_htc_hdr));
 
-       if (ep->tx_credit_flow_enabled) {
+       if (credit_flow_enabled) {
                credits = DIV_ROUND_UP(skb->len, htc->target_credit_size);
                spin_lock_bh(&htc->tx_lock);
                if (ep->tx_credits < credits) {
@@ -126,7 +128,7 @@ int ath11k_htc_send(struct ath11k_htc *htc,
 err_unmap:
        dma_unmap_single(dev, skb_cb->paddr, skb->len, DMA_TO_DEVICE);
 err_credits:
-       if (ep->tx_credit_flow_enabled) {
+       if (credit_flow_enabled) {
                spin_lock_bh(&htc->tx_lock);
                ep->tx_credits += credits;
                ath11k_dbg(ab, ATH11K_DBG_HTC,
@@ -203,23 +205,25 @@ static int ath11k_htc_process_trailer(struct ath11k_htc *htc,
                        break;
                }
 
-               switch (record->hdr.id) {
-               case ATH11K_HTC_RECORD_CREDITS:
-                       len = sizeof(struct ath11k_htc_credit_report);
-                       if (record->hdr.len < len) {
-                               ath11k_warn(ab, "Credit report too long\n");
-                               status = -EINVAL;
+               if (ab->hw_params.credit_flow) {
+                       switch (record->hdr.id) {
+                       case ATH11K_HTC_RECORD_CREDITS:
+                               len = sizeof(struct ath11k_htc_credit_report);
+                               if (record->hdr.len < len) {
+                                       ath11k_warn(ab, "Credit report too long\n");
+                                       status = -EINVAL;
+                                       break;
+                               }
+                               ath11k_htc_process_credit_report(htc,
+                                                                record->credit_report,
+                                                                record->hdr.len,
+                                                                src_eid);
+                               break;
+                       default:
+                               ath11k_warn(ab, "Unhandled record: id:%d length:%d\n",
+                                           record->hdr.id, record->hdr.len);
                                break;
                        }
-                       ath11k_htc_process_credit_report(htc,
-                                                        record->credit_report,
-                                                        record->hdr.len,
-                                                        src_eid);
-                       break;
-               default:
-                       ath11k_warn(ab, "Unhandled record: id:%d length:%d\n",
-                                   record->hdr.id, record->hdr.len);
-                       break;
                }
 
                if (status)
@@ -245,6 +249,29 @@ static void ath11k_htc_suspend_complete(struct ath11k_base *ab, bool ack)
        complete(&ab->htc_suspend);
 }
 
+void ath11k_htc_tx_completion_handler(struct ath11k_base *ab,
+                                     struct sk_buff *skb)
+{
+       struct ath11k_htc *htc = &ab->htc;
+       struct ath11k_htc_ep *ep;
+       void (*ep_tx_complete)(struct ath11k_base *, struct sk_buff *);
+       u8 eid;
+
+       eid = ATH11K_SKB_CB(skb)->eid;
+       if (eid >= ATH11K_HTC_EP_COUNT)
+               return;
+
+       ep = &htc->endpoint[eid];
+       spin_lock_bh(&htc->tx_lock);
+       ep_tx_complete = ep->ep_ops.ep_tx_complete;
+       spin_unlock_bh(&htc->tx_lock);
+       if (!ep_tx_complete) {
+               dev_kfree_skb_any(skb);
+               return;
+       }
+       ep_tx_complete(htc->ab, skb);
+}
+
 void ath11k_htc_rx_completion_handler(struct ath11k_base *ab,
                                      struct sk_buff *skb)
 {
@@ -607,6 +634,11 @@ int ath11k_htc_connect_service(struct ath11k_htc *htc,
                disable_credit_flow_ctrl = true;
        }
 
+       if (!ab->hw_params.credit_flow) {
+               flags |= ATH11K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL;
+               disable_credit_flow_ctrl = true;
+       }
+
        req_msg->flags_len = FIELD_PREP(HTC_SVC_MSG_CONNECTIONFLAGS, flags);
        req_msg->msg_svc_id |= FIELD_PREP(HTC_SVC_MSG_SERVICE_ID,
                                          conn_req->service_id);
@@ -732,7 +764,10 @@ int ath11k_htc_start(struct ath11k_htc *htc)
        msg->msg_id = FIELD_PREP(HTC_MSG_MESSAGEID,
                                 ATH11K_HTC_MSG_SETUP_COMPLETE_EX_ID);
 
-       ath11k_dbg(ab, ATH11K_DBG_HTC, "HTC is using TX credit flow control\n");
+       if (ab->hw_params.credit_flow)
+               ath11k_dbg(ab, ATH11K_DBG_HTC, "HTC is using TX credit flow control\n");
+       else
+               msg->flags |= ATH11K_GLOBAL_DISABLE_CREDIT_FLOW;
 
        status = ath11k_htc_send(htc, ATH11K_HTC_EP_0, skb);
        if (status) {
index 6c8a469d7f9dfc24bef95204e1e26bcd38d91339..f429b37cfdf759d073af7a33efa8bfda86ce1710 100644 (file)
@@ -83,8 +83,8 @@ enum ath11k_htc_conn_flags {
        ATH11K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_ONE_HALF      = 0x1,
        ATH11K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_THREE_FOURTHS = 0x2,
        ATH11K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_UNITY         = 0x3,
-       ATH11K_HTC_CONN_FLAGS_REDUCE_CREDIT_DRIBBLE    = 1 << 2,
-       ATH11K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL = 1 << 3
+       ATH11K_HTC_CONN_FLAGS_REDUCE_CREDIT_DRIBBLE         = 0x4,
+       ATH11K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL      = 0x8,
 };
 
 enum ath11k_htc_conn_svc_status {
@@ -116,6 +116,8 @@ struct ath11k_htc_conn_svc_resp {
        u32 svc_meta_pad;
 } __packed;
 
+#define ATH11K_GLOBAL_DISABLE_CREDIT_FLOW BIT(1)
+
 struct ath11k_htc_setup_complete_extended {
        u32 msg_id;
        u32 flags;
@@ -305,5 +307,6 @@ int ath11k_htc_send(struct ath11k_htc *htc, enum ath11k_htc_ep_id eid,
 struct sk_buff *ath11k_htc_alloc_skb(struct ath11k_base *ar, int size);
 void ath11k_htc_rx_completion_handler(struct ath11k_base *ar,
                                      struct sk_buff *skb);
-
+void ath11k_htc_tx_completion_handler(struct ath11k_base *ab,
+                                     struct sk_buff *skb);
 #endif
index 3e64c9b94db00f467cce21b48f4037b15de0df6f..dd2202f23b99c647417a118c626648fb8830c829 100644 (file)
@@ -175,6 +175,7 @@ struct ath11k_hw_params {
        bool supports_suspend;
        u32 hal_desc_sz;
        bool fix_l1ss;
+       bool credit_flow;
        u8 max_tx_ring;
        const struct ath11k_hw_hal_params *hal_params;
        bool supports_dynamic_smps_6ghz;
index 6248e174bd8fd5db26760c47efcbea95e9b3307e..6bd355ce88d346ccbc450111538278dcfeba0c16 100644 (file)
@@ -267,21 +267,39 @@ int ath11k_wmi_cmd_send(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb,
 {
        struct ath11k_wmi_base *wmi_sc = wmi->wmi_ab;
        int ret = -EOPNOTSUPP;
+       struct ath11k_base *ab = wmi_sc->ab;
 
        might_sleep();
 
-       wait_event_timeout(wmi_sc->tx_credits_wq, ({
-               ret = ath11k_wmi_cmd_send_nowait(wmi, skb, cmd_id);
+       if (ab->hw_params.credit_flow) {
+               wait_event_timeout(wmi_sc->tx_credits_wq, ({
+                       ret = ath11k_wmi_cmd_send_nowait(wmi, skb, cmd_id);
+
+                       if (ret && test_bit(ATH11K_FLAG_CRASH_FLUSH,
+                                           &wmi_sc->ab->dev_flags))
+                               ret = -ESHUTDOWN;
 
-               if (ret && test_bit(ATH11K_FLAG_CRASH_FLUSH, &wmi_sc->ab->dev_flags))
-                       ret = -ESHUTDOWN;
+                       (ret != -EAGAIN);
+                       }), WMI_SEND_TIMEOUT_HZ);
+       } else {
+               wait_event_timeout(wmi->tx_ce_desc_wq, ({
+                       ret = ath11k_wmi_cmd_send_nowait(wmi, skb, cmd_id);
 
-               (ret != -EAGAIN);
-       }), WMI_SEND_TIMEOUT_HZ);
+                       if (ret && test_bit(ATH11K_FLAG_CRASH_FLUSH,
+                                           &wmi_sc->ab->dev_flags))
+                               ret = -ESHUTDOWN;
+
+                       (ret != -ENOBUFS);
+                       }), WMI_SEND_TIMEOUT_HZ);
+       }
 
        if (ret == -EAGAIN)
                ath11k_warn(wmi_sc->ab, "wmi command %d timeout\n", cmd_id);
 
+       if (ret == -ENOBUFS)
+               ath11k_warn(wmi_sc->ab, "ce desc not available for wmi command %d\n",
+                           cmd_id);
+
        return ret;
 }
 
@@ -5816,7 +5834,30 @@ static void ath11k_wmi_op_ep_tx_credits(struct ath11k_base *ab)
 static void ath11k_wmi_htc_tx_complete(struct ath11k_base *ab,
                                       struct sk_buff *skb)
 {
+       struct ath11k_pdev_wmi *wmi = NULL;
+       u32 i;
+       u8 wmi_ep_count;
+       u8 eid;
+
+       eid = ATH11K_SKB_CB(skb)->eid;
        dev_kfree_skb(skb);
+
+       if (eid >= ATH11K_HTC_EP_COUNT)
+               return;
+
+       wmi_ep_count = ab->htc.wmi_ep_count;
+       if (wmi_ep_count > ab->hw_params.max_radios)
+               return;
+
+       for (i = 0; i < ab->htc.wmi_ep_count; i++) {
+               if (ab->wmi_ab.wmi[i].eid == eid) {
+                       wmi = &ab->wmi_ab.wmi[i];
+                       break;
+               }
+       }
+
+       if (wmi)
+               wake_up(&wmi->tx_ce_desc_wq);
 }
 
 static bool ath11k_reg_is_world_alpha(char *alpha)
@@ -7207,6 +7248,7 @@ static int ath11k_connect_pdev_htc_service(struct ath11k_base *ab,
        ab->wmi_ab.wmi_endpoint_id[pdev_idx] = conn_resp.eid;
        ab->wmi_ab.wmi[pdev_idx].eid = conn_resp.eid;
        ab->wmi_ab.max_msg_len[pdev_idx] = conn_resp.max_msg_len;
+       init_waitqueue_head(&ab->wmi_ab.wmi[pdev_idx].tx_ce_desc_wq);
 
        return 0;
 }
index 81d12405be5412faae8ee25e3e56971ef2bd87de..b5b9f2c22e2ee611feb49c037c1529fd5cd77f6d 100644 (file)
@@ -2522,6 +2522,7 @@ struct ath11k_pdev_wmi {
        enum ath11k_htc_ep_id eid;
        const struct wmi_peer_flags_map *peer_flags;
        u32 rx_decap_mode;
+       wait_queue_head_t tx_ce_desc_wq;
 };
 
 struct vdev_create_params {