/* * 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 #include // ----------------------------------------------------------------------------- // 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; }