xref: /aosp_15_r20/system/chre/pal/util/wifi_scan_cache.c (revision 84e339476a462649f82315436d70fd732297a399)
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "chre/pal/util/wifi_scan_cache.h"
18 
19 #include <inttypes.h>
20 
21 #include "chre/util/macros.h"
22 
23 /************************************************
24  *  Constants
25  ***********************************************/
26 
27 // Constants used in chreWifiScanCacheInitialAgeMsValue() and
28 // chreWifiScanCacheFinalizeAgeMs()
29 // These values are selected because msec = nsec / 1000000 and
30 // 1000000 = 64 * 15625 = (1 << 6) * 15625
31 #define CHRE_WIFI_SCAN_CACHE_AGE_MS_SHIFT (6)
32 #define CHRE_WIFI_SCAN_CACHE_AGE_MS_DIVISOR (15625)
33 
34 /************************************************
35  *  Prototypes
36  ***********************************************/
37 
38 struct chreWifiScanCacheState {
39   //! true if the scan cache has started, i.e. chreWifiScanCacheScanEventBegin
40   //! was invoked and has not yet ended.
41   bool started;
42 
43   //! true if the current scan cache is a result of a CHRE active scan request.
44   bool scanRequestedByChre;
45 
46   //! The number of chreWifiScanResults dropped due to OOM.
47   uint16_t numWifiScanResultsDropped;
48 
49   //! Stores the WiFi cache elements
50   struct chreWifiScanEvent event;
51   struct chreWifiScanResult resultList[CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY];
52 
53   //! The number of chreWifiScanEvent data pending release via
54   //! chreWifiScanCacheReleaseScanEvent().
55   uint8_t numWifiEventsPendingRelease;
56 
57   bool scanMonitoringEnabled;
58 
59   uint32_t scannedFreqList[CHRE_WIFI_FREQUENCY_LIST_MAX_LEN];
60 
61   uint64_t scanStartTimeNs;
62 };
63 
64 /************************************************
65  *  Global variables
66  ***********************************************/
67 static const struct chrePalSystemApi *gSystemApi = NULL;
68 static const struct chrePalWifiCallbacks *gCallbacks = NULL;
69 
70 static struct chreWifiScanCacheState gWifiCacheState;
71 
72 //! true if scan monitoring is enabled via
73 //! chreWifiScanCacheConfigureScanMonitor().
74 static bool gScanMonitoringEnabled;
75 
76 static const uint64_t kOneMillisecondInNanoseconds = UINT64_C(1000000);
77 
78 /************************************************
79  *  Private functions
80  ***********************************************/
chreWifiScanCacheIsInitialized(void)81 static bool chreWifiScanCacheIsInitialized(void) {
82   return (gSystemApi != NULL && gCallbacks != NULL);
83 }
84 
areAllScanEventsReleased(void)85 static bool areAllScanEventsReleased(void) {
86   return gWifiCacheState.numWifiEventsPendingRelease == 0;
87 }
88 
isFrequencyListValid(const uint32_t * frequencyList,uint16_t frequencyListLen)89 static bool isFrequencyListValid(const uint32_t *frequencyList,
90                                  uint16_t frequencyListLen) {
91   return (frequencyListLen == 0) || (frequencyList != NULL);
92 }
93 
paramsMatchScanCache(const struct chreWifiScanParams * params)94 static bool paramsMatchScanCache(const struct chreWifiScanParams *params) {
95   uint64_t timeNs = gWifiCacheState.event.referenceTime;
96   bool scan_within_age =
97       gWifiCacheState.started ||
98       (timeNs >= gSystemApi->getCurrentTime() -
99                      (params->maxScanAgeMs * kOneMillisecondInNanoseconds));
100 
101   // Perform a conservative check for the params and scan cache.
102   // TODO(b/174510035): Consider optimizing for the case for channelSet ==
103   // CHRE_WIFI_CHANNEL_SET_ALL.
104   bool params_non_dfs =
105       (params->scanType == CHRE_WIFI_SCAN_TYPE_ACTIVE) ||
106       ((params->scanType == CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE) &&
107        (params->channelSet == CHRE_WIFI_CHANNEL_SET_NON_DFS));
108   bool cache_non_dfs =
109       (gWifiCacheState.event.scanType == CHRE_WIFI_SCAN_TYPE_ACTIVE) ||
110       (gWifiCacheState.event.scanType == CHRE_WIFI_SCAN_TYPE_PASSIVE);
111 
112   bool cache_all_freq = (gWifiCacheState.event.scannedFreqListLen == 0);
113   bool cache_all_ssid = (gWifiCacheState.event.ssidSetSize == 0);
114 
115   return scan_within_age && (params_non_dfs || !cache_non_dfs) &&
116          cache_all_freq && cache_all_ssid;
117 }
118 
isWifiScanCacheBusy(bool logOnBusy)119 static bool isWifiScanCacheBusy(bool logOnBusy) {
120   bool busy = true;
121   if (gWifiCacheState.started) {
122     if (logOnBusy) {
123       gSystemApi->log(CHRE_LOG_ERROR, "Scan cache already started");
124     }
125   } else if (!areAllScanEventsReleased()) {
126     if (logOnBusy) {
127       gSystemApi->log(CHRE_LOG_ERROR, "Scan cache events pending release");
128     }
129   } else {
130     busy = false;
131   }
132 
133   return busy;
134 }
135 
chreWifiScanCacheDispatchAll(void)136 static void chreWifiScanCacheDispatchAll(void) {
137   gSystemApi->log(CHRE_LOG_DEBUG, "Dispatching %" PRIu8 " events",
138                   gWifiCacheState.event.resultTotal);
139   if (gWifiCacheState.event.resultTotal == 0) {
140     gWifiCacheState.event.eventIndex = 0;
141     gWifiCacheState.event.resultCount = 0;
142     gWifiCacheState.event.results = NULL;
143     gCallbacks->scanEventCallback(&gWifiCacheState.event);
144   } else {
145     uint8_t eventIndex = 0;
146     for (uint16_t i = 0; i < gWifiCacheState.event.resultTotal;
147          i += CHRE_PAL_WIFI_SCAN_CACHE_MAX_RESULT_COUNT) {
148       gWifiCacheState.event.resultCount =
149           MIN(CHRE_PAL_WIFI_SCAN_CACHE_MAX_RESULT_COUNT,
150               (uint8_t)(gWifiCacheState.event.resultTotal - i));
151       gWifiCacheState.event.eventIndex = eventIndex++;
152       gWifiCacheState.event.results = &gWifiCacheState.resultList[i];
153 
154       // TODO(b/174511061): The current approach only works for situations where
155       // the event is released immediately. Add a way to handle this scenario
156       // (e.g. an array of chreWifiScanEvent's).
157       gWifiCacheState.numWifiEventsPendingRelease++;
158       gCallbacks->scanEventCallback(&gWifiCacheState.event);
159       if (gWifiCacheState.numWifiEventsPendingRelease != 0) {
160         gSystemApi->log(CHRE_LOG_ERROR, "Scan event not released immediately");
161       }
162     }
163   }
164 }
165 
isWifiScanResultInCache(const struct chreWifiScanResult * result,size_t * index)166 static bool isWifiScanResultInCache(const struct chreWifiScanResult *result,
167                                     size_t *index) {
168   for (uint8_t i = 0; i < gWifiCacheState.event.resultTotal; i++) {
169     const struct chreWifiScanResult *cacheResult =
170         &gWifiCacheState.resultList[i];
171     // Filtering based on BSSID + SSID + frequency based on Linux cfg80211.
172     // https://github.com/torvalds/linux/blob/master/net/wireless/scan.c
173     if ((result->primaryChannel == cacheResult->primaryChannel) &&
174         (memcmp(result->bssid, cacheResult->bssid, CHRE_WIFI_BSSID_LEN) == 0) &&
175         (result->ssidLen == cacheResult->ssidLen) &&
176         (memcmp(result->ssid, cacheResult->ssid, result->ssidLen) == 0)) {
177       *index = i;
178       return true;
179     }
180   }
181 
182   return false;
183 }
184 
isLowerRssiScanResultInCache(const struct chreWifiScanResult * result,size_t * index)185 static bool isLowerRssiScanResultInCache(
186     const struct chreWifiScanResult *result, size_t *index) {
187   int8_t lowestRssi = result->rssi;
188   bool foundWeakerResult = false;
189   for (uint8_t i = 0; i < gWifiCacheState.event.resultTotal; i++) {
190     const struct chreWifiScanResult *cacheResult =
191         &gWifiCacheState.resultList[i];
192     // Filter based on RSSI to determine weakest result in cache.
193     if (cacheResult->rssi < lowestRssi) {
194       lowestRssi = cacheResult->rssi;
195       *index = i;
196       foundWeakerResult = true;
197     }
198   }
199 
200   return foundWeakerResult;
201 }
202 
chreWifiScanCacheInitialAgeMsValue()203 static uint32_t chreWifiScanCacheInitialAgeMsValue() {
204   // ageMs will be finalized via chreWifiScanCacheFinalizeAgeMs() once the scan
205   // finishes, because it is relative to the scan end time that we can't know
206   // yet. Before the end of the scan, populate ageMs with the time since the
207   // start of the scan.
208   //
209   // We avoid 64-bit integer division by:
210   //  - Only considering the delta between this result and the start of the
211   //    scan, which constrains the range of expected values to what should be
212   //    only a few seconds
213   //  - Instead of directly dividing by 1000000, we first divide by 64 (right
214   //    shift by 6), then truncate to 32 bits, then later we'll do integer
215   //    division by 15625 to get milliseconds
216   //    - This works because x/1000000 = x/(64 * 15625) = (x/64)/15625
217   //    - The largest delta we can fit here is 2^32/15625 ms = 274877 ms or
218   //      about 4.5 minutes
219   uint64_t timeSinceScanStartNs =
220       (gSystemApi->getCurrentTime() - gWifiCacheState.scanStartTimeNs);
221   return (timeSinceScanStartNs >> CHRE_WIFI_SCAN_CACHE_AGE_MS_SHIFT);
222 }
223 
chreWifiScanCacheFinalizeAgeMs()224 static void chreWifiScanCacheFinalizeAgeMs() {
225   // Convert ageMs from the chreWifiScanCacheInitialAgeMsValue() to its final,
226   // correct value using the formula derived from these steps:
227   //  ageMs = (referenceTimeNs - absoluteScanResultTimeNs) / 1000000
228   //        = (referenceTimeNs - (scanStartTimeNs + scanOffsetNs)) / 1000000
229   //        = ((referenceTimeNs - scanStartTimeNs) - scanOffsetNs) / 1000000
230   //        = (scanDuration / 64 - scanOffsetNs / 64) / 15625
231   //  ageMs = (scanDurationShifted - currentAgeMsValue) / 15625
232   uint64_t referenceTimeNs = gWifiCacheState.event.referenceTime;
233   uint64_t scanStartTimeNs = gWifiCacheState.scanStartTimeNs;
234   uint32_t scanDurationShifted =
235       (referenceTimeNs - scanStartTimeNs) >> CHRE_WIFI_SCAN_CACHE_AGE_MS_SHIFT;
236   if (referenceTimeNs < scanStartTimeNs) {
237     gSystemApi->log(CHRE_LOG_ERROR, "Invalid scan timestamp, clamping");
238     // Use a smaller number to avoid very large ageMs values
239     scanDurationShifted = 78125000;  // 5 seconds --> 5*10e9 / 64
240   }
241 
242   for (uint16_t i = 0; i < gWifiCacheState.event.resultTotal; i++) {
243     if (scanDurationShifted < gWifiCacheState.resultList[i].ageMs) {
244       gSystemApi->log(CHRE_LOG_ERROR,
245                       "Invalid result timestamp %" PRIu32 " vs. %" PRIu32,
246                       gWifiCacheState.resultList[i].ageMs, scanDurationShifted);
247       gWifiCacheState.resultList[i].ageMs = 0;
248     } else {
249       gWifiCacheState.resultList[i].ageMs =
250           (scanDurationShifted - gWifiCacheState.resultList[i].ageMs) /
251           CHRE_WIFI_SCAN_CACHE_AGE_MS_DIVISOR;
252     }
253   }
254 }
255 
256 /************************************************
257  *  Public functions
258  ***********************************************/
chreWifiScanCacheInit(const struct chrePalSystemApi * systemApi,const struct chrePalWifiCallbacks * callbacks)259 bool chreWifiScanCacheInit(const struct chrePalSystemApi *systemApi,
260                            const struct chrePalWifiCallbacks *callbacks) {
261   if (systemApi == NULL || callbacks == NULL) {
262     return false;
263   }
264 
265   gSystemApi = systemApi;
266   gCallbacks = callbacks;
267   memset(&gWifiCacheState, 0, sizeof(gWifiCacheState));
268   gScanMonitoringEnabled = false;
269 
270   return true;
271 }
272 
chreWifiScanCacheDeinit(void)273 void chreWifiScanCacheDeinit(void) {
274   gSystemApi = NULL;
275   gCallbacks = NULL;
276 }
277 
chreWifiScanCacheScanEventBegin(enum chreWifiScanType scanType,uint8_t ssidSetSize,const uint32_t * scannedFreqList,uint16_t scannedFreqListLength,uint8_t radioChainPref,bool scanRequestedByChre)278 bool chreWifiScanCacheScanEventBegin(enum chreWifiScanType scanType,
279                                      uint8_t ssidSetSize,
280                                      const uint32_t *scannedFreqList,
281                                      uint16_t scannedFreqListLength,
282                                      uint8_t radioChainPref,
283                                      bool scanRequestedByChre) {
284   bool success = false;
285   if (chreWifiScanCacheIsInitialized()) {
286     enum chreError error = CHRE_ERROR_NONE;
287     if (!isFrequencyListValid(scannedFreqList, scannedFreqListLength)) {
288       gSystemApi->log(CHRE_LOG_ERROR, "Invalid frequency argument");
289       error = CHRE_ERROR_INVALID_ARGUMENT;
290     } else if (isWifiScanCacheBusy(true /* logOnBusy */)) {
291       error = CHRE_ERROR_BUSY;
292     } else {
293       success = true;
294       memset(&gWifiCacheState, 0, sizeof(gWifiCacheState));
295 
296       gWifiCacheState.event.version = CHRE_WIFI_SCAN_EVENT_VERSION;
297       gWifiCacheState.event.scanType = scanType;
298       gWifiCacheState.event.ssidSetSize = ssidSetSize;
299 
300       scannedFreqListLength =
301           MIN(scannedFreqListLength, CHRE_WIFI_FREQUENCY_LIST_MAX_LEN);
302       if (scannedFreqList != NULL) {
303         memcpy(gWifiCacheState.scannedFreqList, scannedFreqList,
304                scannedFreqListLength * sizeof(uint32_t));
305       }
306       gWifiCacheState.event.scannedFreqListLen = scannedFreqListLength;
307       gWifiCacheState.event.radioChainPref = radioChainPref;
308 
309       gWifiCacheState.scanRequestedByChre = scanRequestedByChre;
310       gWifiCacheState.started = true;
311       gWifiCacheState.scanStartTimeNs = gSystemApi->getCurrentTime();
312     }
313 
314     if (scanRequestedByChre && !success) {
315       gCallbacks->scanResponseCallback(false /* pending */, error);
316     }
317   }
318 
319   return success;
320 }
321 
chreWifiScanCacheScanEventAdd(const struct chreWifiScanResult * result)322 void chreWifiScanCacheScanEventAdd(const struct chreWifiScanResult *result) {
323   if (!gWifiCacheState.started) {
324     gSystemApi->log(CHRE_LOG_ERROR, "Cannot add to cache before starting it");
325     return;
326   }
327 
328   size_t index;
329   if (!isWifiScanResultInCache(result, &index)) {
330     if (gWifiCacheState.event.resultTotal >=
331         CHRE_PAL_WIFI_SCAN_CACHE_CAPACITY) {
332       gWifiCacheState.numWifiScanResultsDropped++;
333       // Determine weakest result in cache to replace with the new result.
334       if (!isLowerRssiScanResultInCache(result, &index)) {
335         return;
336       }
337     } else {
338       // Result was not already cached, add new entry to the end of the cache
339       index = gWifiCacheState.event.resultTotal;
340       gWifiCacheState.event.resultTotal++;
341     }
342   }
343 
344   memcpy(&gWifiCacheState.resultList[index], result,
345          sizeof(const struct chreWifiScanResult));
346 
347   gWifiCacheState.resultList[index].ageMs =
348       chreWifiScanCacheInitialAgeMsValue();
349 }
350 
chreWifiScanCacheScanEventEnd(enum chreError errorCode)351 void chreWifiScanCacheScanEventEnd(enum chreError errorCode) {
352   if (gWifiCacheState.started) {
353     if (gWifiCacheState.numWifiScanResultsDropped > 0) {
354       gSystemApi->log(CHRE_LOG_WARN,
355                       "Dropped total of %" PRIu32 " access points",
356                       gWifiCacheState.numWifiScanResultsDropped);
357     }
358     if (gWifiCacheState.scanRequestedByChre) {
359       gCallbacks->scanResponseCallback(
360           errorCode == CHRE_ERROR_NONE /* pending */, errorCode);
361     }
362 
363     if (errorCode == CHRE_ERROR_NONE &&
364         (gWifiCacheState.scanRequestedByChre || gScanMonitoringEnabled)) {
365       gWifiCacheState.event.referenceTime = gSystemApi->getCurrentTime();
366       gWifiCacheState.event.scannedFreqList = gWifiCacheState.scannedFreqList;
367       chreWifiScanCacheFinalizeAgeMs();
368       chreWifiScanCacheDispatchAll();
369     }
370 
371     gWifiCacheState.started = false;
372     gWifiCacheState.scanRequestedByChre = false;
373   }
374 }
375 
chreWifiScanCacheDispatchFromCache(const struct chreWifiScanParams * params)376 bool chreWifiScanCacheDispatchFromCache(
377     const struct chreWifiScanParams *params) {
378   if (!chreWifiScanCacheIsInitialized()) {
379     return false;
380   }
381 
382   if (paramsMatchScanCache(params)) {
383     if (!isWifiScanCacheBusy(false /* logOnBusy */)) {
384       // Satisfied by cache
385       gCallbacks->scanResponseCallback(true /* pending */, CHRE_ERROR_NONE);
386       chreWifiScanCacheDispatchAll();
387       return true;
388     } else if (gWifiCacheState.started) {
389       // Will be satisfied by cache once the scan completes
390       gSystemApi->log(CHRE_LOG_INFO, "Using in-progress scan for CHRE request");
391       gWifiCacheState.scanRequestedByChre = true;
392       return true;
393     } else {
394       // Assumed: busy because !areAllScanEventsReleased()
395       // TODO(b/174511061): the current code assumes scan events are released
396       // synchronously, so this should never happen
397       gSystemApi->log(CHRE_LOG_ERROR,
398                       "Unexpected scan request while delivering results");
399       return false;
400     }
401   } else {
402     // Cache contains results from incompatible scan parameters (either too old
403     // or different scan type), so a new scan is needed
404     return false;
405   }
406 }
407 
chreWifiScanCacheReleaseScanEvent(struct chreWifiScanEvent * event)408 void chreWifiScanCacheReleaseScanEvent(struct chreWifiScanEvent *event) {
409   if (!chreWifiScanCacheIsInitialized()) {
410     return;
411   }
412 
413   if (event != &gWifiCacheState.event) {
414     gSystemApi->log(CHRE_LOG_ERROR, "Invalid event pointer %p", event);
415   } else if (gWifiCacheState.numWifiEventsPendingRelease > 0) {
416     gWifiCacheState.numWifiEventsPendingRelease--;
417   }
418 }
419 
chreWifiScanCacheConfigureScanMonitor(bool enable)420 void chreWifiScanCacheConfigureScanMonitor(bool enable) {
421   if (!chreWifiScanCacheIsInitialized()) {
422     return;
423   }
424 
425   gScanMonitoringEnabled = enable;
426 }
427