mirror of
https://github.com/google/pebble
synced 2026-10-16 16:24:12 +02:00
216 lines
7.0 KiB
C
216 lines
7.0 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define FILE_LOG_COLOR LOG_COLOR_BLUE
|
|
|
|
#include "bluetooth/gap_le_scan.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "comm/bt_lock.h"
|
|
#include "gap_le_scan.h"
|
|
#include "kernel/events.h"
|
|
#include "system/hexdump.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "util/circular_buffer.h"
|
|
#include "util/likely.h"
|
|
|
|
#include <btutil/bt_device.h>
|
|
|
|
#include <string.h>
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Static Variables -- MUST be protected with bt_lock/unlock!
|
|
|
|
//! The current scanning state of the controller.
|
|
static bool s_is_scanning;
|
|
|
|
//! The backing array for the circular buffer of reports to be processed.
|
|
static uint8_t *s_reports_buffer;
|
|
|
|
//! The circular buffer that tracks reports to be processed.
|
|
//! [MT] Currently, there is only one potential client that reads from the
|
|
//! buffer (the app). In the future, I can imagine the kernel also wants to
|
|
//! the scan at the same time. When that happens, we need to keep a cursor for
|
|
//! each client.
|
|
static CircularBuffer s_circular_buffer;
|
|
|
|
//! Flag that is set true when the cancelling of the timer was attempted, but
|
|
//! did not succeed. If true, the timer callback will not finalize the pending
|
|
//! report because it is not the report for which the timer was set initially.
|
|
//! @see gap_le_scan_get_dropped_reports_count
|
|
static uint32_t s_dropped_reports;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
bool gap_le_start_scan(void) {
|
|
bool success = false;
|
|
bt_lock();
|
|
{
|
|
if (!s_is_scanning) {
|
|
s_dropped_reports = 0;
|
|
|
|
success = bt_driver_start_le_scan(
|
|
true /* active_scan */, false /* use_white_list_filter */,
|
|
true /* filter_dups */, 10240, 10240);
|
|
|
|
if (success) {
|
|
// Allocate report buffers if advertising started successfully
|
|
const size_t buffer_size = GAP_LE_SCAN_REPORTS_BUFFER_SIZE;
|
|
s_reports_buffer = (uint8_t *) kernel_malloc_check(buffer_size);
|
|
circular_buffer_init(&s_circular_buffer, s_reports_buffer, buffer_size);
|
|
s_is_scanning = true;
|
|
}
|
|
}
|
|
}
|
|
bt_unlock();
|
|
return success;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
bool gap_le_stop_scan(void) {
|
|
bool success = false;
|
|
bt_lock();
|
|
{
|
|
if (s_is_scanning) {
|
|
|
|
success = bt_driver_stop_le_scan();
|
|
kernel_free(s_reports_buffer);
|
|
s_reports_buffer = NULL;
|
|
s_is_scanning = false;
|
|
|
|
if (s_dropped_reports) {
|
|
PBL_LOG(LOG_LEVEL_INFO, "LE Scan -- Dropped reports: %" PRIu32, s_dropped_reports);
|
|
}
|
|
}
|
|
}
|
|
bt_unlock();
|
|
return success;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
bool gap_le_is_scanning(void) {
|
|
bt_lock();
|
|
const bool is_scanning = s_is_scanning;
|
|
bt_unlock();
|
|
return is_scanning;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
//! Copies over the pending report to the circular buffer and free the pending
|
|
//! "slot". In case there is no space left, the pending report will be dropped.
|
|
//! and a counter will be incremented
|
|
void bt_driver_cb_le_scan_handle_report(const GAPLERawAdReport *report_buffer, int length) {
|
|
const bool written = circular_buffer_write(
|
|
&s_circular_buffer, (uint8_t *)report_buffer, length);
|
|
|
|
if (!written) {
|
|
++s_dropped_reports;
|
|
} else { // notify clients there's a new event available
|
|
PebbleEvent e = {
|
|
.type = PEBBLE_BLE_SCAN_EVENT,
|
|
};
|
|
event_put(&e);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
bool gap_le_consume_scan_results(uint8_t *buffer, uint16_t *size_in_out) {
|
|
// The number of bytes left to read:
|
|
uint16_t read_space;
|
|
// The space left in the output buffer:
|
|
uint16_t write_space = *size_in_out;
|
|
bt_lock();
|
|
{
|
|
if (UNLIKELY(!s_is_scanning)) {
|
|
// Return, the buffers are deallocated by now already...
|
|
*size_in_out = 0;
|
|
bt_unlock();
|
|
return false;
|
|
}
|
|
|
|
// We can't just copy over up to the maximum buffer size, because we could
|
|
// end up with half reports.
|
|
|
|
read_space = circular_buffer_get_read_space_remaining(&s_circular_buffer);
|
|
|
|
// While there are reports to read and there is enough space for at least
|
|
// the GAPLERawAdReport header:
|
|
while (read_space && write_space >= sizeof(GAPLERawAdReport)) {
|
|
// First copy the header. We know for sure this will fit into buffer,
|
|
// because it was tested in the while() condition:
|
|
circular_buffer_copy(&s_circular_buffer,
|
|
buffer, sizeof(GAPLERawAdReport));
|
|
const GAPLERawAdReport *report = (const GAPLERawAdReport *) buffer;
|
|
|
|
// Now use the copied header to figure out how big the report actually is:
|
|
const uint32_t payload_len = report->payload.ad_data_length +
|
|
report->payload.scan_resp_data_length;
|
|
const uint32_t report_len = sizeof(GAPLERawAdReport) + payload_len;
|
|
|
|
// There should always be at least enough bytes to read in the circular
|
|
// read the length of report, otherwise there's an internal inconsistency.
|
|
PBL_ASSERTN(read_space >= report_len);
|
|
|
|
if (report_len <= write_space) {
|
|
// Mark the bytes of the header as "consumed", as it's already copied:
|
|
circular_buffer_consume(&s_circular_buffer, sizeof(GAPLERawAdReport));
|
|
buffer += sizeof(GAPLERawAdReport);
|
|
|
|
// Now copy the payload:
|
|
circular_buffer_copy(&s_circular_buffer, buffer, payload_len);
|
|
circular_buffer_consume(&s_circular_buffer, payload_len);
|
|
buffer += payload_len;
|
|
|
|
// Update the counters:
|
|
write_space -= report_len;
|
|
read_space -= report_len;
|
|
} else {
|
|
// No more space in out buffer.
|
|
// The header might have been copied, but this has not been counted
|
|
// towards the "bytes copied" that is reported back to the client.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Out: number of bytes copied:
|
|
*size_in_out -= write_space;
|
|
}
|
|
bt_unlock();
|
|
return (read_space != 0);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void gap_le_scan_init(void) {
|
|
bt_lock();
|
|
s_is_scanning = false;
|
|
bt_unlock();
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void gap_le_scan_deinit(void) {
|
|
bt_lock();
|
|
if (s_is_scanning) {
|
|
gap_le_stop_scan();
|
|
s_is_scanning = false;
|
|
}
|
|
bt_unlock();
|
|
}
|
|
|
|
// For UNIT Tests
|
|
uint32_t gap_le_scan_get_dropped_reports_count(void) {
|
|
return s_dropped_reports;
|
|
}
|