1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Portions copyright (C) 2023 Broadcom Limited
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include <stdint.h>
20 #include <fcntl.h>
21 #include <sys/socket.h>
22 #include <netlink/genl/genl.h>
23 #include <netlink/genl/family.h>
24 #include <netlink/genl/ctrl.h>
25 #include <linux/rtnetlink.h>
26 #include <netpacket/packet.h>
27 #include <linux/filter.h>
28 #include <linux/errqueue.h>
29
30 #include <linux/pkt_sched.h>
31 #include <netlink/object-api.h>
32 #include <netlink/netlink.h>
33 #include <netlink/socket.h>
34 #include <netlink/handlers.h>
35
36 #include "sync.h"
37
38 #define LOG_TAG "WifiHAL"
39
40 #include <utils/Log.h>
41
42 #include <hardware_legacy/wifi_hal.h>
43 #include "common.h"
44 #include "cpp_bindings.h"
45
46 typedef enum {
47 ANDR_WIFI_ATTRIBUTE_INVALID = 0,
48 ANDR_WIFI_ATTRIBUTE_NUM_RADIO = 1,
49 ANDR_WIFI_ATTRIBUTE_STATS_INFO = 2,
50 ANDR_WIFI_ATTRIBUTE_ML_STATS_INFO = 3,
51 ANDR_WIFI_ATTRIBUTE_STATS_MAX = 4
52 } LINK_STAT_ATTRIBUTE;
53
54 /* Internal radio statistics structure in the driver */
55 typedef struct {
56 wifi_radio radio;
57 uint32_t on_time;
58 uint32_t tx_time;
59 uint32_t rx_time;
60 uint32_t on_time_scan;
61 uint32_t on_time_nbd;
62 uint32_t on_time_gscan;
63 uint32_t on_time_roam_scan;
64 uint32_t on_time_pno_scan;
65 uint32_t on_time_hs20;
66 uint32_t num_channels;
67 wifi_channel_stat channels[];
68 } wifi_radio_stat_internal;
69
70 enum {
71 LSTATS_SUBCMD_GET_INFO = ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START,
72 };
73
74 class GetLinkStatsCommand : public WifiCommand
75 {
76 wifi_stats_result_handler mHandler;
77 public:
GetLinkStatsCommand(wifi_interface_handle iface,wifi_stats_result_handler handler)78 GetLinkStatsCommand(wifi_interface_handle iface, wifi_stats_result_handler handler)
79 : WifiCommand("GetLinkStatsCommand", iface, 0), mHandler(handler)
80 { }
81
create()82 virtual int create() {
83 ALOGI("Creating message to get link statistics; iface = %d", mIfaceInfo->id);
84
85 int ret = mMsg.create(GOOGLE_OUI, LSTATS_SUBCMD_GET_INFO);
86 if (ret < 0) {
87 ALOGE("Failed to create %x - %d", LSTATS_SUBCMD_GET_INFO, ret);
88 return ret;
89 }
90
91 return ret;
92 }
93
94 protected:
handleResponse(WifiEvent & reply)95 virtual int handleResponse(WifiEvent& reply) {
96 bool ml_data = false;
97 wifi_iface_ml_stat *iface_ml_stat_ptr = NULL;
98 u8 *radioStatsBuf = NULL, *ifaceMlStatsBuf = NULL, *outbuf = NULL, *data_ptr = NULL;
99 u8 *data = NULL, *iface_stat = NULL;
100 uint32_t offset = 0, num_radios = 0, per_radio_size = 0, data_len = 0;
101 uint32_t outbuf_rem_len = 0, data_rem_len = 0;
102 int ret = 0, id = 0, subcmd = 0, len = 0;
103 u32 fixed_iface_ml_stat_size = 0, all_links_stat_size = 0;
104 u8 num_links = 0;
105
106 // ALOGI("In GetLinkStatsCommand::handleResponse");
107
108 if (reply.get_cmd() != NL80211_CMD_VENDOR) {
109 ALOGD("Ignoring reply with cmd = %d", reply.get_cmd());
110 return NL_SKIP;
111 }
112
113 id = reply.get_vendor_id();
114 subcmd = reply.get_vendor_subcmd();
115 nlattr *vendor_data = reply.get_attribute(NL80211_ATTR_VENDOR_DATA);
116 len = reply.get_vendor_data_len();
117
118 ALOGV("Id = %0x, subcmd = %d, len = %d\n", id, subcmd, len);
119 if (vendor_data == NULL || len == 0) {
120 ALOGE("no vendor data in GetLinkStatCommand response; ignoring it");
121 return NL_SKIP;
122 }
123
124 for (nl_iterator it(vendor_data); it.has_next(); it.next()) {
125 if (it.get_type() == ANDR_WIFI_ATTRIBUTE_NUM_RADIO) {
126 num_radios = it.get_u32();
127 } else if (it.get_type() == ANDR_WIFI_ATTRIBUTE_STATS_INFO) {
128 data = (u8 *)it.get_data();
129 data_len = it.get_len();
130 } else if (it.get_type() == ANDR_WIFI_ATTRIBUTE_ML_STATS_INFO) {
131 data = (u8 *)it.get_data();
132 data_len = it.get_len();
133 ml_data = true;
134 } else {
135 ALOGW("Ignoring invalid attribute type = %d, size = %d",
136 it.get_type(), it.get_len());
137 return NL_SKIP;
138 }
139 }
140
141 if (num_radios > MAX_NUM_RADIOS) {
142 ALOGE("Invalid num radios :%d\n", num_radios);
143 ret = WIFI_ERROR_INVALID_ARGS;
144 goto exit;
145 }
146
147 outbuf_rem_len = MAX_CMD_RESP_BUF_LEN;
148 radioStatsBuf = (u8 *)malloc(MAX_CMD_RESP_BUF_LEN);
149 if (!radioStatsBuf) {
150 ALOGE("No memory\n");
151 return NL_SKIP;
152 }
153 memset(radioStatsBuf, 0, MAX_CMD_RESP_BUF_LEN);
154 outbuf = radioStatsBuf;
155
156 if (!data || !data_len) {
157 ALOGE("%s: null data\n", __func__);
158 ret = WIFI_ERROR_INVALID_ARGS;
159 goto exit;
160 }
161
162 data_ptr = data;
163 for (int i = 0; i < num_radios; i++) {
164 outbuf_rem_len -= per_radio_size;
165 if (outbuf_rem_len < per_radio_size) {
166 ALOGE("No data left for radio %d\n", i);
167 ret = WIFI_ERROR_INVALID_ARGS;
168 goto exit;
169 }
170
171 data_ptr = data + offset;
172 if (!data_ptr) {
173 ALOGE("Invalid data for radio index = %d\n", i);
174 ret = WIFI_ERROR_INVALID_ARGS;
175 goto exit;
176 }
177 ret = convertToExternalRadioStatStructure((wifi_radio_stat*)data_ptr,
178 &per_radio_size, &outbuf, &outbuf_rem_len);
179 if (ret < 0) {
180 ALOGE(("Failed to map data radioStat struct\n"));
181 goto exit;
182 }
183 if (!per_radio_size) {
184 ALOGE("No data for radio %d\n", i);
185 continue;
186 }
187 outbuf += per_radio_size;
188 /* Accounted size of all the radio data len */
189 offset += per_radio_size;
190 }
191
192 if (ml_data) {
193 outbuf = NULL;
194 data_rem_len = data_len - offset;
195 fixed_iface_ml_stat_size = offsetof(wifi_iface_ml_stat, links);
196
197 /* Allocate vendor hal buffer, instead of using the nlmsg allocated buffer */
198 ifaceMlStatsBuf = (u8 *)malloc(MAX_CMD_RESP_BUF_LEN);
199 if (!ifaceMlStatsBuf) {
200 ALOGE("No memory\n");
201 ret = WIFI_ERROR_OUT_OF_MEMORY;
202 goto exit;
203 }
204 outbuf_rem_len = MAX_CMD_RESP_BUF_LEN;
205 memset(ifaceMlStatsBuf, 0, MAX_CMD_RESP_BUF_LEN);
206 outbuf = ifaceMlStatsBuf;
207
208 if (data_rem_len >= fixed_iface_ml_stat_size) {
209 data_ptr = (data + offset);
210 if (!data_ptr) {
211 ALOGE("No iface ml stats fixed data!!, data_len = %d, offset = %d\n",
212 data_len, offset);
213 ret = WIFI_ERROR_INVALID_ARGS;
214 goto exit;
215 }
216
217 if (outbuf_rem_len < fixed_iface_ml_stat_size) {
218 ALOGE("No space to copy fixed iface ml stats!, rem_len %d, req_len %d\n",
219 outbuf_rem_len, fixed_iface_ml_stat_size);
220 ret = WIFI_ERROR_OUT_OF_MEMORY;
221 goto exit;
222 }
223
224 memcpy(outbuf, data_ptr, fixed_iface_ml_stat_size);
225 data_rem_len -= fixed_iface_ml_stat_size;
226 outbuf_rem_len -= fixed_iface_ml_stat_size;
227 outbuf += fixed_iface_ml_stat_size;
228 offset += fixed_iface_ml_stat_size;
229
230 iface_ml_stat_ptr = (wifi_iface_ml_stat *)ifaceMlStatsBuf;
231 if (!iface_ml_stat_ptr) {
232 ALOGE("No iface ml stats data!!");
233 ret = WIFI_ERROR_INVALID_ARGS;
234 goto exit;
235 }
236
237 num_links = iface_ml_stat_ptr->num_links;
238 all_links_stat_size = (num_links * offsetof(wifi_link_stat, peer_info));
239 if (num_links > MAX_MLO_LINK) {
240 ALOGE("Invalid num links :%d\n", num_links);
241 ret = WIFI_ERROR_INVALID_ARGS;
242 goto exit;
243 }
244
245 if (num_links && (data_rem_len >= all_links_stat_size)) {
246 ret = convertToExternalIfaceMlstatStructure(&data, &offset, &outbuf,
247 &data_rem_len, num_links, &outbuf_rem_len);
248 if (ret < 0) {
249 ALOGE(("Failed to map data to iface ml struct\n"));
250 goto exit;
251 }
252 } else {
253 ALOGE("num_links %d, Required data not found: expected len %d,"
254 " data_rem_len %d\n", num_links, all_links_stat_size, data_rem_len);
255 ret = WIFI_ERROR_INVALID_ARGS;
256 goto exit;
257 }
258 (*mHandler.on_multi_link_stats_results)(id,
259 (wifi_iface_ml_stat *)ifaceMlStatsBuf, num_radios,
260 (wifi_radio_stat *)radioStatsBuf);
261 }
262 } else if ((data_len >= (offset + sizeof(wifi_iface_stat)))) {
263 iface_stat = (data + offset);
264 if (!iface_stat) {
265 ALOGE("No data for legacy iface stats!!, data_len = %d, offset = %d\n",
266 data_len, offset);
267 ret = WIFI_ERROR_INVALID_ARGS;
268 goto exit;
269 }
270 (*mHandler.on_link_stats_results)(id, (wifi_iface_stat *)iface_stat,
271 num_radios, (wifi_radio_stat *)radioStatsBuf);
272 } else {
273 ALOGE("No data for iface stats!!, data_len = %d, offset = %d\n",
274 data_len, offset);
275 ret = WIFI_ERROR_INVALID_ARGS;
276 goto exit;
277 }
278
279 exit:
280 /* report valid radiostat eventhough there is no linkstat info
281 * (non assoc/error case)
282 */
283 if ((ret != WIFI_SUCCESS) && num_radios && per_radio_size) {
284 (*mHandler.on_link_stats_results)(id, NULL,
285 num_radios, (wifi_radio_stat *)radioStatsBuf);
286 }
287
288 if (radioStatsBuf) {
289 free(radioStatsBuf);
290 radioStatsBuf = NULL;
291 }
292 if (ifaceMlStatsBuf) {
293 free(ifaceMlStatsBuf);
294 ifaceMlStatsBuf = NULL;
295 }
296 return NL_OK;
297 }
298
299 private:
convertToExternalRadioStatStructure(wifi_radio_stat * internal_stat_ptr,uint32_t * per_radio_size,u8 ** outbuf,uint32_t * outbuf_rem_len)300 int convertToExternalRadioStatStructure(wifi_radio_stat *internal_stat_ptr,
301 uint32_t *per_radio_size, u8 **outbuf, uint32_t *outbuf_rem_len)
302 {
303 int ret = 0;
304 if (!internal_stat_ptr) {
305 ALOGE("Incoming data is null\n");
306 ret = WIFI_ERROR_INVALID_ARGS;
307 } else {
308 uint32_t channel_size = internal_stat_ptr->num_channels * sizeof(wifi_channel_stat);
309 *per_radio_size = offsetof(wifi_radio_stat, channels) + channel_size;
310 if (*outbuf_rem_len >= *per_radio_size) {
311 memcpy(*outbuf, internal_stat_ptr, *per_radio_size);
312 } else {
313 ALOGE("Insufficient buf size to copy. rem len %d, req size %d\n",
314 *outbuf_rem_len, *per_radio_size);
315 ret = WIFI_ERROR_OUT_OF_MEMORY;
316 }
317 }
318 return ret;
319 }
320
convertToExternalRatestatsStructure(u8 ** data,u32 * offset,u8 ** outbuf,u32 * data_rem_len,wifi_peer_info * peer_info,u8 num_rate,u32 * outbuf_rem_len)321 int convertToExternalRatestatsStructure(u8 **data, u32 *offset, u8 **outbuf,
322 u32 *data_rem_len, wifi_peer_info *peer_info, u8 num_rate, u32 *outbuf_rem_len)
323 {
324 u8 k = 0, num_peers = 0;
325 int ret = 0;
326 u8 *data_ptr = NULL;
327 u32 all_rates_size = 0, per_rate_size = 0;
328
329 per_rate_size = sizeof(wifi_rate_stat);
330 all_rates_size = num_rate * per_rate_size;
331 if (!peer_info && (*data_rem_len != all_rates_size)) {
332 ALOGE("Insufficient data for rate_stats, data_rem_len %d,"
333 "required data size %d \n", *data_rem_len, all_rates_size);
334 ret = WIFI_ERROR_INVALID_ARGS;
335 goto exit;
336 }
337
338 for (k = 0; k < num_rate; k++) {
339 data_ptr = ((*data) + (*offset));
340 if (!data_ptr) {
341 ALOGE("rate_stats not found!! num_rate %d, *data_rem_len = %d, *offset = %d\n",
342 num_rate, *data_rem_len, *offset);
343 ret = WIFI_ERROR_OUT_OF_MEMORY;
344 goto exit;
345 }
346
347 if (*data_rem_len < per_rate_size) {
348 ALOGE("no rate_stats!!, data_rem_len %d, rate_stat size %d\n",
349 *data_rem_len, per_rate_size);
350 ret = WIFI_ERROR_INVALID_ARGS;
351 goto exit;
352 }
353
354 if (*outbuf_rem_len < per_rate_size) {
355 ALOGE("No space to copy rate_stats of index [%d]!, rem_len %d, req_len %d\n",
356 k, *outbuf_rem_len, per_rate_size);
357 ret = WIFI_ERROR_OUT_OF_MEMORY;
358 goto exit;
359 }
360
361 memcpy(*outbuf, data_ptr, per_rate_size);
362 *data_rem_len -= per_rate_size;
363 *outbuf_rem_len -= per_rate_size;
364 *outbuf += per_rate_size;
365 *offset += per_rate_size;
366 }
367 exit:
368 return ret;
369 }
370
convertToExternalIfaceLinkstatStructure(u8 ** data,uint32_t * offset,u8 ** outbuf,u32 * data_rem_len,u8 num_peers,wifi_link_stat * links,u32 * outbuf_rem_len)371 int convertToExternalIfaceLinkstatStructure(u8 **data, uint32_t *offset, u8 **outbuf,
372 u32 *data_rem_len, u8 num_peers, wifi_link_stat *links, u32 *outbuf_rem_len)
373 {
374 int ret = 0, j = 0, num_rate = 0;
375 u32 all_rate_stats_per_peer_per_link_size = 0, fixed_peer_info_size = 0;
376 u8 *data_ptr = NULL;
377 wifi_peer_info *peer_info_ptr = NULL;
378
379 for (j = 0; j < num_peers; j++) {
380 data_ptr = ((*data) + (*offset));
381 if (!data_ptr || (!*data_rem_len)) {
382 ALOGE("no peer_info data!! num_peers %d, data_rem_len = %d, *offset = %d\n",
383 num_peers, *data_rem_len, *offset);
384 ret = WIFI_ERROR_OUT_OF_MEMORY;
385 goto exit;
386 }
387
388 fixed_peer_info_size = offsetof(wifi_peer_info, rate_stats);
389 if (*data_rem_len < fixed_peer_info_size) {
390 ALOGE("no fixed peer_info data!!, data_rem_len %d, fixed peer info %d\n",
391 *data_rem_len, fixed_peer_info_size);
392 ret = WIFI_ERROR_INVALID_ARGS;
393 goto exit;
394 }
395
396 if (*outbuf_rem_len < fixed_peer_info_size) {
397 ALOGE("No space to copy fixed peer_info of index[%d]!, rem_len %d, req_len %d\n",
398 j, *outbuf_rem_len, fixed_peer_info_size);
399 ret = WIFI_ERROR_OUT_OF_MEMORY;
400 goto exit;
401 }
402
403 memcpy(*outbuf, data_ptr, fixed_peer_info_size);
404 *data_rem_len -= fixed_peer_info_size;
405 *outbuf_rem_len -= fixed_peer_info_size;
406 *outbuf += fixed_peer_info_size;
407 *offset += fixed_peer_info_size;
408
409 peer_info_ptr = (wifi_peer_info *)data_ptr;
410 if (!peer_info_ptr) {
411 ALOGE("no peer_info data!!");
412 ret = WIFI_ERROR_INVALID_ARGS;
413 goto exit;
414 }
415
416 num_rate = peer_info_ptr->num_rate;
417 if ((num_rate == NUM_RATE) || (num_rate == NUM_RATE_NON_BE)) {
418 all_rate_stats_per_peer_per_link_size = num_rate * sizeof(wifi_rate_stat);
419 if (num_rate && (*data_rem_len >= all_rate_stats_per_peer_per_link_size)) {
420 ret = convertToExternalRatestatsStructure(data, offset, outbuf, data_rem_len,
421 peer_info_ptr, num_rate, outbuf_rem_len);
422 if (ret != WIFI_SUCCESS) {
423 ALOGE(("Failed to convert it to rate stats\n"));
424 goto exit;
425 }
426 } else {
427 ALOGI("num_rate %d, Required rate_stats not found: expected len %d,"
428 " data_rem_len %d\n", num_rate, all_rate_stats_per_peer_per_link_size,
429 *data_rem_len);
430 continue;
431 }
432 } else if (num_rate == 0) {
433 ALOGI("Sta is not associated, num rate :%d\n", num_rate);
434 } else {
435 ALOGE("Invalid num rate :%d\n", num_rate);
436 ret = WIFI_ERROR_INVALID_ARGS;
437 goto exit;
438 }
439 }
440
441 exit:
442 return ret;
443 }
444
convertToExternalIfaceMlstatStructure(u8 ** data,u32 * offset,u8 ** outbuf,u32 * data_rem_len,u8 num_links,u32 * outbuf_rem_len)445 int convertToExternalIfaceMlstatStructure(u8 **data, u32 *offset, u8 **outbuf,
446 u32 *data_rem_len, u8 num_links, u32 *outbuf_rem_len)
447 {
448 int ret = 0, i = 0;
449 u32 all_peers_per_link_size = 0, fixed_link_stat_size = 0;
450 u8 *data_ptr = NULL;
451 u8 num_peers = 0;
452 wifi_link_stat *links_ptr = NULL;
453
454 for (i = 0; i < num_links; i++) {
455 data_ptr = ((*data) + (*offset));
456 if (!data_ptr || !(*data_rem_len)) {
457 ALOGE("no variable links data!! num_links %d, data_rem_len = %d, offset = %d\n",
458 num_links, *data_rem_len, *offset);
459 ret = WIFI_ERROR_OUT_OF_MEMORY;
460 goto exit;
461 }
462
463 fixed_link_stat_size = offsetof(wifi_link_stat, peer_info);
464 if (*data_rem_len < fixed_link_stat_size) {
465 ALOGE("no fixed wifi_link_stat data!!, data_rem_len %d, fixed link stat data %d\n",
466 *data_rem_len, fixed_link_stat_size);
467 ret = WIFI_ERROR_INVALID_ARGS;
468 goto exit;
469 }
470
471 if (*outbuf_rem_len < fixed_link_stat_size) {
472 ALOGE("No space to copy fixed link stats of index[%d]!, rem_len %d, req_len %d\n",
473 i, *outbuf_rem_len, fixed_link_stat_size);
474 ret = WIFI_ERROR_OUT_OF_MEMORY;
475 goto exit;
476 }
477
478 memcpy(*outbuf, data_ptr, fixed_link_stat_size);
479 *outbuf_rem_len -= fixed_link_stat_size;
480 *data_rem_len -= fixed_link_stat_size;
481 *outbuf += fixed_link_stat_size;
482 *offset += fixed_link_stat_size;
483
484 links_ptr = (wifi_link_stat *)data_ptr;
485 if (!links_ptr) {
486 ALOGE("no link_stat data!!");
487 ret = WIFI_ERROR_INVALID_ARGS;
488 goto exit;
489 }
490
491 if (!links_ptr->num_peers) {
492 ALOGI("no peers in unassoc case, skip processing peer stats\n");
493 ret = WIFI_SUCCESS;
494 continue;
495 }
496
497 if (links_ptr->num_peers != NUM_PEER_AP) {
498 ALOGE("Invalid num peer :%d\n", links_ptr->num_peers);
499 ret = WIFI_ERROR_INVALID_ARGS;
500 goto exit;
501 }
502
503 num_peers = links_ptr->num_peers;
504 all_peers_per_link_size = num_peers * offsetof(wifi_peer_info, rate_stats);
505 if ((num_peers == NUM_PEER_AP) && (*data_rem_len >= all_peers_per_link_size)) {
506 ret = convertToExternalIfaceLinkstatStructure(data, offset, outbuf,
507 data_rem_len, num_peers, links_ptr, outbuf_rem_len);
508 if (ret != WIFI_SUCCESS) {
509 ALOGE(("Failed to convert it to iface link stats\n"));
510 goto exit;
511 }
512 } else {
513 ALOGI("num_peers %d, Required data not found: expected len %d, data_rem_len %d\n",
514 num_peers, all_peers_per_link_size, *data_rem_len);
515 continue;
516 }
517 }
518 exit:
519 return ret;
520 }
521 };
522
wifi_get_link_stats(wifi_request_id id,wifi_interface_handle iface,wifi_stats_result_handler handler)523 wifi_error wifi_get_link_stats(wifi_request_id id,
524 wifi_interface_handle iface, wifi_stats_result_handler handler)
525 {
526 GetLinkStatsCommand command(iface, handler);
527 return (wifi_error) command.requestResponse();
528 }
529
wifi_set_link_stats(wifi_interface_handle,wifi_link_layer_params)530 wifi_error wifi_set_link_stats(
531 wifi_interface_handle /* iface */, wifi_link_layer_params /* params */)
532 {
533 /* Return success here since bcom HAL does not need set link stats. */
534 return WIFI_SUCCESS;
535 }
536
wifi_clear_link_stats(wifi_interface_handle,u32,u32 *,u8,u8 *)537 wifi_error wifi_clear_link_stats(
538 wifi_interface_handle /* iface */, u32 /* stats_clear_req_mask */,
539 u32 * /* stats_clear_rsp_mask */, u8 /* stop_req */, u8 * /* stop_rsp */)
540 {
541 /* Return success here since bcom HAL does not support clear link stats. */
542 return WIFI_SUCCESS;
543 }
544