1 /*
2 * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
3 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #ifdef WLAN_DP_FEATURE_SW_LATENCY_MGR
19
20 #include <dp_types.h>
21 #include <dp_internal.h>
22 #include <wlan_cfg.h>
23 #include "wlan_dp_swlm.h"
24 #include "qdf_time.h"
25 #include "qdf_util.h"
26 #include "hal_internal.h"
27 #include "hal_api.h"
28 #include "hif.h"
29 #include <qdf_status.h>
30 #include <qdf_nbuf.h>
31
32 /**
33 * dp_swlm_is_tput_thresh_reached() - Calculate the current tx and rx TPUT
34 * and check if it passes the pre-set
35 * threshold.
36 * @soc: Datapath global soc handle
37 * @rid: TCL ring id
38 *
39 * This function calculates the current TX and RX throughput and checks
40 * if it is above the pre-set thresholds by SWLM.
41 *
42 * Returns: true, if the TX/RX throughput is passing the threshold
43 * false, otherwise
44 */
dp_swlm_is_tput_thresh_reached(struct dp_soc * soc,uint8_t rid)45 static bool dp_swlm_is_tput_thresh_reached(struct dp_soc *soc, uint8_t rid)
46 {
47 struct dp_swlm_params *params = &soc->swlm.params;
48 int rx_delta, tx_delta, tx_packet_delta;
49 bool result = false;
50
51 tx_delta = soc->stats.tx.egress[rid].bytes -
52 params->tcl[rid].prev_tx_bytes;
53 params->tcl[rid].prev_tx_bytes = soc->stats.tx.egress[rid].bytes;
54 if (tx_delta > params->tx_traffic_thresh) {
55 params->tcl[rid].sampling_session_tx_bytes = tx_delta;
56 result = true;
57 }
58
59 rx_delta = soc->stats.rx.ingress.bytes - params->tcl[rid].prev_rx_bytes;
60 params->tcl[rid].prev_rx_bytes = soc->stats.rx.ingress.bytes;
61 if (!result && rx_delta > params->rx_traffic_thresh) {
62 params->tcl[rid].sampling_session_tx_bytes = tx_delta;
63 result = true;
64 }
65
66 tx_packet_delta = soc->stats.tx.egress[rid].num -
67 params->tcl[rid].prev_tx_packets;
68 params->tcl[rid].prev_tx_packets = soc->stats.tx.egress[rid].num;
69 if (tx_packet_delta < params->tx_pkt_thresh)
70 result = false;
71
72 return result;
73 }
74
75 /**
76 * dp_swlm_can_tcl_wr_coalesce() - To check if current TCL reg write can be
77 * coalesced or not.
78 * @soc: Datapath global soc handle
79 * @tcl_data: priv data for tcl coalescing
80 *
81 * This function takes into account the current tx and rx throughput and
82 * decides whether the TCL register write corresponding to the current packet,
83 * to be transmitted, is to be processed or coalesced.
84 * It maintains a session for which the TCL register writes are coalesced and
85 * then flushed if a certain time/bytes threshold is reached.
86 *
87 * Returns: 1 if the current TCL write is to be coalesced
88 * 0, if the current TCL write is to be processed.
89 */
90 static int
dp_swlm_can_tcl_wr_coalesce(struct dp_soc * soc,struct dp_swlm_tcl_data * tcl_data)91 dp_swlm_can_tcl_wr_coalesce(struct dp_soc *soc,
92 struct dp_swlm_tcl_data *tcl_data)
93 {
94 u64 curr_time = qdf_get_log_timestamp_usecs();
95 int tput_level_pass, coalesce = 0;
96 struct dp_swlm *swlm = &soc->swlm;
97 uint8_t rid = tcl_data->ring_id;
98 struct dp_swlm_params *params = &soc->swlm.params;
99
100 if (curr_time >= params->tcl[rid].expire_time) {
101 params->tcl[rid].expire_time = qdf_get_log_timestamp_usecs() +
102 params->sampling_time;
103 tput_level_pass = dp_swlm_is_tput_thresh_reached(soc, rid);
104 if (tput_level_pass) {
105 params->tcl[rid].tput_pass_cnt++;
106 } else {
107 params->tcl[rid].tput_pass_cnt = 0;
108 DP_STATS_INC(swlm, tcl[rid].tput_criteria_fail, 1);
109 goto coalescing_fail;
110 }
111 }
112
113 params->tcl[rid].bytes_coalesced += tcl_data->pkt_len;
114
115 if (params->tcl[rid].tput_pass_cnt > DP_SWLM_TCL_TPUT_PASS_THRESH) {
116 coalesce = 1;
117 if (params->tcl[rid].bytes_coalesced >
118 params->tcl[rid].bytes_flush_thresh) {
119 coalesce = 0;
120 DP_STATS_INC(swlm, tcl[rid].bytes_thresh_reached, 1);
121 } else if (curr_time > params->tcl[rid].coalesce_end_time) {
122 coalesce = 0;
123 DP_STATS_INC(swlm, tcl[rid].time_thresh_reached, 1);
124 }
125 }
126
127 coalescing_fail:
128 if (!coalesce) {
129 dp_swlm_tcl_reset_session_data(soc, rid);
130 return 0;
131 }
132
133 qdf_timer_mod(¶ms->tcl[rid].flush_timer, 1);
134
135 return 1;
136 }
137
dp_print_swlm_stats(struct dp_soc * soc)138 QDF_STATUS dp_print_swlm_stats(struct dp_soc *soc)
139 {
140 struct dp_swlm *swlm = &soc->swlm;
141 int i;
142
143 for (i = 0; i < soc->num_tcl_data_rings; i++) {
144 dp_info("TCL: %u Coalescing stats:", i);
145 dp_info("Num coalesce success: %d",
146 swlm->stats.tcl[i].coalesce_success);
147 dp_info("Num coalesce fail: %d",
148 swlm->stats.tcl[i].coalesce_fail);
149 dp_info("Timer flush success: %d",
150 swlm->stats.tcl[i].timer_flush_success);
151 dp_info("Timer flush fail: %d",
152 swlm->stats.tcl[i].timer_flush_fail);
153 dp_info("Coalesce fail (TID): %d",
154 swlm->stats.tcl[i].tid_fail);
155 dp_info("Coalesce fail (special frame): %d",
156 swlm->stats.tcl[i].sp_frames);
157 dp_info("Coalesce fail (Low latency connection): %d",
158 swlm->stats.tcl[i].ll_connection);
159 dp_info("Coalesce fail (bytes thresh crossed): %d",
160 swlm->stats.tcl[i].bytes_thresh_reached);
161 dp_info("Coalesce fail (time thresh crossed): %d",
162 swlm->stats.tcl[i].time_thresh_reached);
163 dp_info("Coalesce fail (TPUT sampling fail): %d",
164 swlm->stats.tcl[i].tput_criteria_fail);
165 }
166
167 return QDF_STATUS_SUCCESS;
168 }
169
170 static struct dp_swlm_ops dp_latency_mgr_ops = {
171 .tcl_wr_coalesce_check = dp_swlm_can_tcl_wr_coalesce,
172 };
173
174 /**
175 * dp_swlm_tcl_flush_timer() - Timer handler for tcl register write coalescing
176 * @arg: private data of the timer
177 *
178 * Returns: none
179 */
dp_swlm_tcl_flush_timer(void * arg)180 static void dp_swlm_tcl_flush_timer(void *arg)
181 {
182 struct dp_swlm_tcl_params *tcl = arg;
183 struct dp_soc *soc = tcl->soc;
184 struct dp_swlm *swlm = &soc->swlm;
185 int ret;
186
187 ret = soc->arch_ops.dp_flush_tx_ring(soc->pdev_list[0], tcl->ring_id);
188 if (ret) {
189 DP_STATS_INC(swlm, tcl[tcl->ring_id].timer_flush_fail, 1);
190 return;
191 }
192
193 DP_STATS_INC(swlm, tcl[tcl->ring_id].timer_flush_success, 1);
194 }
195
196 /**
197 * dp_soc_swlm_tcl_attach() - attach the TCL resources for the software
198 * latency manager.
199 * @soc: Datapath global soc handle
200 *
201 * Returns: QDF_STATUS
202 */
dp_soc_swlm_tcl_attach(struct dp_soc * soc)203 static inline QDF_STATUS dp_soc_swlm_tcl_attach(struct dp_soc *soc)
204 {
205 struct dp_swlm *swlm = &soc->swlm;
206 int i;
207
208 swlm->params.rx_traffic_thresh = DP_SWLM_TCL_RX_TRAFFIC_THRESH;
209 swlm->params.tx_traffic_thresh = DP_SWLM_TCL_TX_TRAFFIC_THRESH;
210 swlm->params.sampling_time = DP_SWLM_TCL_TRAFFIC_SAMPLING_TIME;
211 swlm->params.time_flush_thresh = DP_SWLM_TCL_TIME_FLUSH_THRESH;
212 swlm->params.tx_thresh_multiplier = DP_SWLM_TCL_TX_THRESH_MULTIPLIER;
213 swlm->params.tx_pkt_thresh = DP_SWLM_TCL_TX_PKT_THRESH;
214
215 for (i = 0; i < soc->num_tcl_data_rings; i++) {
216 swlm->params.tcl[i].soc = soc;
217 swlm->params.tcl[i].ring_id = i;
218 swlm->params.tcl[i].bytes_flush_thresh = 0;
219 qdf_timer_init(soc->osdev,
220 &swlm->params.tcl[i].flush_timer,
221 dp_swlm_tcl_flush_timer,
222 (void *)&swlm->params.tcl[i],
223 QDF_TIMER_TYPE_WAKE_APPS);
224 }
225
226 return QDF_STATUS_SUCCESS;
227 }
228
229 /**
230 * dp_soc_swlm_tcl_detach() - detach the TCL resources for the software
231 * latency manager.
232 * @swlm: SWLM data pointer
233 * @ring_id: TCL ring id
234 *
235 * Returns: QDF_STATUS
236 */
dp_soc_swlm_tcl_detach(struct dp_swlm * swlm,uint8_t ring_id)237 static inline QDF_STATUS dp_soc_swlm_tcl_detach(struct dp_swlm *swlm,
238 uint8_t ring_id)
239 {
240 qdf_timer_stop(&swlm->params.tcl[ring_id].flush_timer);
241 qdf_timer_free(&swlm->params.tcl[ring_id].flush_timer);
242
243 return QDF_STATUS_SUCCESS;
244 }
245
dp_soc_swlm_attach(struct dp_soc * soc)246 QDF_STATUS dp_soc_swlm_attach(struct dp_soc *soc)
247 {
248 struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx;
249 struct dp_swlm *swlm = &soc->swlm;
250 QDF_STATUS ret;
251
252 /* Check if it is enabled in the INI */
253 if (!wlan_cfg_is_swlm_enabled(cfg)) {
254 dp_err("SWLM feature is disabled");
255 swlm->is_init = false;
256 swlm->is_enabled = false;
257 return QDF_STATUS_E_NOSUPPORT;
258 }
259
260 swlm->ops = &dp_latency_mgr_ops;
261
262 ret = dp_soc_swlm_tcl_attach(soc);
263 if (QDF_IS_STATUS_ERROR(ret))
264 goto swlm_tcl_setup_fail;
265
266 swlm->is_init = true;
267 swlm->is_enabled = true;
268
269 return QDF_STATUS_SUCCESS;
270
271 swlm_tcl_setup_fail:
272 swlm->is_enabled = false;
273 return ret;
274 }
275
dp_soc_swlm_detach(struct dp_soc * soc)276 QDF_STATUS dp_soc_swlm_detach(struct dp_soc *soc)
277 {
278 struct dp_swlm *swlm = &soc->swlm;
279 QDF_STATUS ret;
280 int i;
281
282 if (!swlm->is_enabled)
283 return QDF_STATUS_SUCCESS;
284
285 swlm->is_enabled = false;
286
287 for (i = 0; i < soc->num_tcl_data_rings; i++) {
288 ret = dp_soc_swlm_tcl_detach(swlm, i);
289 if (QDF_IS_STATUS_ERROR(ret))
290 return ret;
291 }
292
293 swlm->ops = NULL;
294
295 return QDF_STATUS_SUCCESS;
296 }
297 #endif /* WLAN_DP_FEATURE_SW_LATENCY_MGR */
298