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