diff --git a/src/bluetooth-fw/da1468x/.gitignore b/src/bluetooth-fw/da1468x/.gitignore new file mode 100644 index 00000000..310f9d1c --- /dev/null +++ b/src/bluetooth-fw/da1468x/.gitignore @@ -0,0 +1 @@ +!openocd.cfg diff --git a/src/bluetooth-fw/da1468x/common/dialog_utils.c b/src/bluetooth-fw/da1468x/common/dialog_utils.c new file mode 100644 index 00000000..3660c718 --- /dev/null +++ b/src/bluetooth-fw/da1468x/common/dialog_utils.c @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#include "dialog_utils.h" + +// Dialog SDK: +#include "ble_common.h" + +#include +#include +#include + +bool dialog_utils_dialog_is_addr_type_random(addr_type_t addr_type) { + return (addr_type == PRIVATE_ADDRESS); +} + +addr_type_t dialog_utils_local_addr_type_to_dialog(const BTDeviceInternal *address) { + return (address->is_random_address) ? PRIVATE_ADDRESS : PUBLIC_ADDRESS; +} + +void dialog_utils_bd_address_to_bt_device(const bd_address_t *addr, BTDeviceInternal *device_out) { + *device_out = (BTDeviceInternal) {}; + device_out->is_random_address = dialog_utils_dialog_is_addr_type_random(addr->addr_type); + memcpy(&device_out->address, &addr->addr, sizeof(device_out->address)); +} + +void dialog_utils_bt_device_to_bd_address(const BTDeviceInternal *device, bd_address_t *addr_out) { + addr_out->addr_type = dialog_utils_local_addr_type_to_dialog(device); + memcpy(addr_out->addr, &device->address, sizeof(addr_out->addr)); +} + +HciStatusCode ble_error_to_hci_status_error(ble_error_t e) { + switch (e) { + case BLE_STATUS_OK: + return HciStatusCode_Success; + default: + return (e + HciStatusCode_VS_Base); + } +} diff --git a/src/bluetooth-fw/da1468x/common/hc_endpoint_bonding_sync.c b/src/bluetooth-fw/da1468x/common/hc_endpoint_bonding_sync.c new file mode 100644 index 00000000..94694b69 --- /dev/null +++ b/src/bluetooth-fw/da1468x/common/hc_endpoint_bonding_sync.c @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#include "bonding_sync_impl.h" +#include "hc_protocol/hc_endpoint_bonding_sync.h" +#include "hc_protocol/hc_protocol.h" + +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include + +#include + +void hc_endpoint_bonding_sync_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_BondingSync_AddBonding: + bonding_sync_handle_hc_add((const BleBonding *)msg->payload); + break; + case HcMessageID_BondingSync_RemoveBonding: + bonding_sync_handle_hc_remove((const BleBonding *)msg->payload); + break; + + default: + PBL_LOG(LOG_LEVEL_ERROR, "Unknown command ID: %"PRIu8, msg->command_id); + break; + } +} + +static void prv_send_cmd(HcMessageID_BondingSync cmd_id, const BleBonding *bonding) { + hc_protocol_enqueue_with_payload(HcEndpointID_BondingSync, cmd_id, + (const uint8_t *)bonding, sizeof(*bonding)); +} + +void hc_endpoint_bonding_sync_add(const BleBonding *bonding) { + prv_send_cmd(HcMessageID_BondingSync_AddBonding, bonding); +} + +void hc_endpoint_bonding_sync_remove(const BleBonding *bonding) { + prv_send_cmd(HcMessageID_BondingSync_RemoveBonding, bonding); +} diff --git a/src/bluetooth-fw/da1468x/common/hc_protocol.c b/src/bluetooth-fw/da1468x/common/hc_protocol.c new file mode 100644 index 00000000..add8016c --- /dev/null +++ b/src/bluetooth-fw/da1468x/common/hc_protocol.c @@ -0,0 +1,506 @@ +/* + * 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. + */ + +#include "core_dump.h" +#include "hc_protocol/hc_protocol.h" +#include "host_transport.h" + +#include "kernel/pbl_malloc.h" +#include "os/mutex.h" +#include "os/tick.h" +#include "system/logging.h" +#include "system/passert.h" + +#include "util/list.h" + +#include "FreeRTOS.h" +#include "semphr.h" +#include "task.h" + +#include +#include + +typedef enum HcProtocolState { + HcProtoclStateNotInitialized = 0, + HcProtoclStateIsInitialized, + HcProtoclStateDeinitializing, +} HcProtocolState; + +#define TIMEOUT_TICKS (configTICK_RATE_HZ / 2) +#define NUM_CONSEC_FAILURES 5 + +extern const HcProtocolMessageHandler g_hc_protocol_endpoints_table[HcEndpointIDCount]; + +typedef struct { + ListNode node; + SemaphoreHandle_t semaphore; + const HcProtocolMessage *request; + HcProtocolMessage *response; +} HcExpectation; + +static PebbleRecursiveMutex *s_hc_lock; +static HcProtocolState s_hc_state; +static HcExpectation *s_expectations_head; +static uint8_t s_hc_next_transaction_id; +static SemaphoreHandle_t s_retry_semph; + +static volatile uint8_t s_outstanding_enqueues; + +static uint8_t s_num_consec_enqueue_failures; +#if !BT_CONTROLLER_BUILD +static uint8_t s_num_consec_request_failures; +#endif + +static void prv_lock(void) { + mutex_lock_recursive(s_hc_lock); +} + +static void prv_unlock(void) { + mutex_unlock_recursive(s_hc_lock); +} + +static void prv_update_hc_state(HcProtocolState state) { + prv_lock(); + s_hc_state = state; + prv_unlock(); +} + +//! WARNING: Will log if function is called and state is not initialized. +static bool prv_check_initialized(void) { + bool is_initialized; + HcProtocolState state; + prv_lock(); + { + state = s_hc_state; + is_initialized = (state == HcProtoclStateIsInitialized); + } + prv_unlock(); + if (!is_initialized) { + PBL_LOG(LOG_LEVEL_WARNING, "prv_check_initialized called when hc_protocol is not initialized, " + "state: %d", state); + } + return is_initialized; +} + +static TickType_t prv_remaining_ticks(TickType_t timeout_ticks, TickType_t start_ticks) { + const TickType_t now = xTaskGetTickCount(); + const TickType_t elapsed = (now - start_ticks); + if (timeout_ticks > elapsed) { + return timeout_ticks - elapsed; + } + return 0; +} + +static bool prv_still_processing_enqueues(void) { + bool done; + prv_lock(); + { + done = (s_outstanding_enqueues != 0); + } + prv_unlock(); + return done; +} + +// Only to be called by prv_enqueue(). This function keeps track of how many +// enqueues are outstanding. If an enqueue is about to start but the stack is +// no longer up, it does NOT bump the job count as we expect prv_enqueue() to +// bail immediately +static bool prv_update_enqueue_count(bool start) { + bool initialized; + prv_lock(); + { + initialized = prv_check_initialized(); + if (initialized || !start) { + s_outstanding_enqueues += (start) ? 1 : -1; + } + } + prv_unlock(); + return initialized; +} + +static bool prv_enqueue(HcProtocolMessage *message) { + PBL_ASSERTN(message->message_length >= sizeof(HcProtocolMessage)); + + if (!prv_update_enqueue_count(true)) { + return false; + } + + bool success = true; + + // Retry for 500ms + TickType_t start_ticks = xTaskGetTickCount(); + + while (1) { + HostTransportEnqueueStatus status = + host_transport_tx_enqueue((const uint8_t *)message, message->message_length); + + if (status != HostTransportEnqueueStatus_RetryLater) { + success = (status == HostTransportEnqueueStatus_Success); + goto done; + } + + TickType_t remaining_ticks = prv_remaining_ticks(TIMEOUT_TICKS, start_ticks); + + bool is_timeout; + if (remaining_ticks == 0) { + is_timeout = true; + } else { + is_timeout = (xSemaphoreTake(s_retry_semph, remaining_ticks) == pdFALSE); + } + + bool initialized = prv_check_initialized(); + if (is_timeout || !initialized) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to enqueue HC request (endpoint:%d, command:%"PRIu8")", + message->endpoint_id, message->command_id); + s_num_consec_enqueue_failures++; + if (initialized && (s_num_consec_enqueue_failures >= NUM_CONSEC_FAILURES)) { + core_dump_and_reset_or_reboot(); + } + success = false; + goto done; + } + } + s_num_consec_enqueue_failures = 0; + +done: + prv_update_enqueue_count(false /* stopping */); + return success; +} + +static void prv_assign_next_transaction_id(HcProtocolMessage *message) { + prv_lock(); + message->transaction_id.sn = s_hc_next_transaction_id; + const uint8_t max_sn = ((1 << HC_PROTOCOL_SN_BIT_WIDTH) - 1); + ++s_hc_next_transaction_id; + s_hc_next_transaction_id %= max_sn; + prv_unlock(); + + message->transaction_id.is_response = false; +} + +bool hc_protocol_enqueue(HcProtocolMessage *message) { + if (!prv_check_initialized()) { + return false; + } + + prv_assign_next_transaction_id(message); + return prv_enqueue(message); +} + +static HcProtocolMessage *prv_create_message(HcEndpointID endpoint_id, HcCommandID command_id, + const uint8_t *request_payload, + size_t request_payload_length) { + size_t message_length = sizeof(HcProtocolMessage) + request_payload_length; + HcProtocolMessage *message = (HcProtocolMessage *)kernel_zalloc_check(message_length); + message->message_length = message_length; + message->endpoint_id = endpoint_id; + message->command_id = command_id; + if (request_payload_length) { + memcpy(message->payload, request_payload, request_payload_length); + } + return message; +} + +bool hc_protocol_enqueue_with_payload(HcEndpointID endpoint_id, HcCommandID command_id, + const uint8_t *request_payload, + size_t request_payload_length) { + if (!prv_check_initialized()) { + return false; + } + + HcProtocolMessage *message = prv_create_message(endpoint_id, command_id, request_payload, + request_payload_length); + const bool rv = hc_protocol_enqueue(message); + kernel_free(message); + return rv; +} + +#if !BT_CONTROLLER_BUILD +static HcExpectation *prv_set_expectation_for_request(const HcProtocolMessage *request_message) { + SemaphoreHandle_t semaphore = xSemaphoreCreateBinary(); + PBL_ASSERTN(semaphore); + + HcExpectation *expectation = kernel_malloc_check(sizeof(HcExpectation)); + *expectation = (HcExpectation) { + .semaphore = semaphore, + .request = request_message, + .response = NULL, + }; + prv_lock(); + s_expectations_head = (HcExpectation *) list_prepend((ListNode *)s_expectations_head, + &expectation->node); + prv_unlock(); + return expectation; +} + +static void prv_cleanup_expectation(HcExpectation *expectation) { + prv_lock(); + list_remove((ListNode *)expectation, (ListNode **)&s_expectations_head, NULL); + prv_unlock(); + vSemaphoreDelete(expectation->semaphore); + kernel_free(expectation); +} + +static HcProtocolMessage *prv_expect(HcExpectation *expectation) { + HcProtocolMessage *response = NULL; + // Save for debugging + const HcCommandID cmd_id = expectation->request->command_id; + const HcEndpointID end_id = expectation->request->endpoint_id; + + TickType_t time_ticks = milliseconds_to_ticks(HC_PROTOCOL_DEFAULT_RESPONSE_TIMEOUT_MS); + if (end_id == HcEndpointID_Ctl) { + time_ticks = milliseconds_to_ticks(HC_PROTOCOL_DEFAULT_CTL_ENDPOINT_RESPONSE_TIMEOUT_MS); + } + + xSemaphoreTake(expectation->semaphore, time_ticks); + prv_lock(); + { + // If we've timed out, expectation->response is NULL: + response = expectation->response; + // Set request to NULL, so it can't be matched any longer (important in timeout scenario) + expectation->request = NULL; + } + prv_unlock(); + + if (response) { + s_num_consec_request_failures = 0; + } else { + s_num_consec_request_failures++; + PBL_LOG(LOG_LEVEL_ERROR, "HC request timed out (endpoint:%d, command:%"PRIu8")", + end_id, cmd_id); + if (prv_check_initialized() && (s_num_consec_request_failures >= NUM_CONSEC_FAILURES)) { + core_dump_and_reset_or_reboot(); + } + } + + return response; +} + +HcProtocolMessage *hc_protocol_enqueue_with_payload_and_expect(HcEndpointID endpoint_id, + HcCommandID command_id, + const uint8_t *request_payload, + size_t request_payload_length) { + if (!prv_check_initialized()) { + return false; + } + + HcProtocolMessage *request = prv_create_message(endpoint_id, command_id, request_payload, + request_payload_length); + // `response` will be NULL if it failed. + HcProtocolMessage *response = hc_protocol_enqueue_and_expect(request); + kernel_free(request); + return response; +} + +HcProtocolMessage *hc_protocol_enqueue_and_expect(HcProtocolMessage *request_message) { + if (!prv_check_initialized()) { + return false; + } + + // Don't allow because we'd deadlock otherwise: + PBL_ASSERTN(!host_transport_is_current_task_host_transport_task()); + + prv_assign_next_transaction_id(request_message); + HcExpectation *expectation = prv_set_expectation_for_request(request_message); + + if (!prv_enqueue(request_message)) { + prv_cleanup_expectation(expectation); + return NULL; + } + + HcProtocolMessage *response_message = prv_expect(expectation); + prv_cleanup_expectation(expectation); + + return response_message; +} +#endif + +bool hc_protocol_enqueue_response(const HcProtocolMessage *to_request, + const uint8_t *response_payload, size_t response_payload_length) { + if (!prv_check_initialized()) { + return false; + } + + PBL_ASSERTN(!to_request->transaction_id.is_response); + size_t message_length = sizeof(HcProtocolMessage) + response_payload_length; + HcProtocolMessage *response = kernel_malloc_check(message_length); + response->message_length = message_length; + response->endpoint_id = to_request->endpoint_id; + response->command_id = to_request->command_id; + response->transaction_id.is_response = true; + response->transaction_id.sn = to_request->transaction_id.sn; + if (response_payload_length) { + memcpy(response->payload, response_payload, response_payload_length); + } + bool rv = prv_enqueue(response); + kernel_free(response); + return rv; +} + +static HcProtocolMessageHandler prv_get_handler_for_endpoint_id(HcEndpointID endpoint_id) { + if (endpoint_id >= HcEndpointIDCount) { + return NULL; + } + return g_hc_protocol_endpoints_table[endpoint_id]; +} + +static void prv_dispatch_message_to_static_handler(HcProtocolMessage *message, bool *should_free) { + const HcProtocolMessageHandler handler = prv_get_handler_for_endpoint_id(message->endpoint_id); + if (!handler) { + PBL_LOG(LOG_LEVEL_ERROR, "No handler for endpoint ID %u", message->endpoint_id); + return; + } + + hc_protocol_cb_dispatch_handler(handler, message, should_free); +} + +static bool prv_expectation_for_message_list_filter_cb(ListNode *found_node, void *data) { + HcExpectation *expectation = (HcExpectation *)found_node; + const HcProtocolMessage *message = (const HcProtocolMessage *)data; + if (!expectation->request) { + // Already being handled or timed out. + return false; + } + if (!message->transaction_id.is_response) { + return false; + } + return (message->transaction_id.sn == expectation->request->transaction_id.sn); +} + +static HcExpectation *prv_expectation_for_message(HcProtocolMessage *message) { + return (HcExpectation *)list_find((ListNode *)s_expectations_head, + prv_expectation_for_message_list_filter_cb, message); +} + +static bool prv_try_handle_expectation(HcProtocolMessage *message, bool *should_free) { + prv_lock(); + HcExpectation *expectation = prv_expectation_for_message(message); + if (!expectation) { + prv_unlock(); + return false; + } + + // Make a heap copy if needed, or transfer ownership if the message is already heap-allocated: + HcProtocolMessage *response_heap_copy; + if (*should_free) { + *should_free = false; + response_heap_copy = message; + } else { + response_heap_copy = (HcProtocolMessage *) kernel_malloc_check(message->message_length); + memcpy(response_heap_copy, message, message->message_length); + } + + expectation->response = response_heap_copy; + xSemaphoreGive(expectation->semaphore); + prv_unlock(); + return true; +} + +static HcProtocolMessage *prv_get_message(size_t length, bool *should_free) { + uint8_t *rx_data = NULL; + *should_free = host_transport_rx_read(&rx_data, length); + return (HcProtocolMessage *) rx_data; +} + +void hc_protocol_process_receive_buffer(void) { + if (!prv_check_initialized()) { + return; + } + + size_t rx_length = host_transport_rx_get_length(); + while (rx_length) { + if (rx_length < sizeof(HcProtocolMessage)) { + // Header not received completely yet + return; + } + + bool should_free = false; + HcProtocolMessage *header = prv_get_message(sizeof(HcProtocolMessage), &should_free); + const size_t message_length = header->message_length; + if (should_free) { + kernel_free(header); + } + + if (rx_length < message_length) { + // Message not received completely yet + return; + } + + HcProtocolMessage *message = prv_get_message(message_length, &should_free); + if (!prv_try_handle_expectation(message, &should_free)) { + prv_dispatch_message_to_static_handler(message, &should_free); + } + + host_transport_rx_consume(message_length); + if (should_free) { + kernel_free(message); + } + + rx_length -= message_length; + } +} + +void hc_protocol_buffer_gained_space(void) { + PBL_ASSERTN(s_retry_semph); + xSemaphoreGive(s_retry_semph); +} + +//! Should be called only once at boot. +void hc_protocol_boot(void) { + if (s_hc_lock) { + return; + } + s_hc_lock = mutex_create_recursive(); +} + +//! Should be called when stack is brought up. +void hc_protocol_init(void) { + s_outstanding_enqueues = 0; + s_hc_next_transaction_id = 0; + s_retry_semph = xSemaphoreCreateBinary(); + prv_update_hc_state(HcProtoclStateIsInitialized); +} + +//! Should be called when stack is torn down. +void hc_protocol_deinit(void) { + prv_update_hc_state(HcProtoclStateDeinitializing); + + while (prv_still_processing_enqueues()) { + // Give the semaphore in case a task is waiting on it + xSemaphoreGive(s_retry_semph); + // Give the task some time to process it + vTaskDelay(2); + } + + // At this point it should no longer be possible for someone to use the retry + // semaphore so delete it + SemaphoreHandle_t s_retry_semph_tmp = s_retry_semph; + s_retry_semph = NULL; + vSemaphoreDelete(s_retry_semph_tmp); + + prv_lock(); + HcExpectation *expectation = s_expectations_head; + while (expectation) { + if (!expectation->request) { + // Semaphore already given or timed out, so skip it. + continue; + } + // Just give, the expectation + semaphore should get cleaned up automatically now. + xSemaphoreGive(expectation->semaphore); + expectation = (HcExpectation *) list_get_next(&expectation->node); + } + prv_unlock(); +} diff --git a/src/bluetooth-fw/da1468x/controller/board/board_robert.c b/src/bluetooth-fw/da1468x/controller/board/board_robert.c new file mode 100644 index 00000000..6e9e6ef1 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/board_robert.c @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#include "board.h" + +static const BoardConfigHostSPI s_host_spi = { + .mcu_int = { + .port = HW_GPIO_PORT_2, + .pin = HW_GPIO_PIN_3, + .function = HW_GPIO_FUNC_GPIO, + }, + + .spi = { + .peripheral = HW_SPI1, + .cs = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_1, + .function = HW_GPIO_FUNC_SPI_EN, + }, + .cs_2 = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_7, + .function = HW_GPIO_FUNC_GPIO, + }, + .clk = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_0, + .function = HW_GPIO_FUNC_SPI_CLK, + }, + .miso_do = { + .port = HW_GPIO_PORT_1, + .pin = HW_GPIO_PIN_3, + .function = HW_GPIO_FUNC_SPI_DO, + }, + .mosi_di = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_4, + .function = HW_GPIO_FUNC_SPI_DI, + }, + } +}; + +// These are spare GPIOs on both the SILK and ROBERT bigboards which are broken out to a header +// Check out debug_gpio.h for more info on how to leverage them for debug +static BoardConfigGpioDebug s_debug_gpios = { + .num_debug_gpios = 4, + .debug_gpio = { + [0] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_0, + }, + [1] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_1, + }, + [2] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_2, + }, + [3] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_3, + }, + } +}; + + +static const BoardConfigBTFEM s_bt_fem = { + .rx = { + .port = HW_GPIO_PORT_1, + .pin = HW_GPIO_PIN_6, + }, + .tx = { + .port = HW_GPIO_PORT_1, + .pin = HW_GPIO_PIN_7, + }, +}; + +const BoardConfigHostSPI * const HOST_SPI = &s_host_spi; +BoardConfigGpioDebug * const DEBUG_GPIOS = &s_debug_gpios; +const BoardConfigBTFEM * const BT_FEM = &s_bt_fem; diff --git a/src/bluetooth-fw/da1468x/controller/board/board_silk.c b/src/bluetooth-fw/da1468x/controller/board/board_silk.c new file mode 100644 index 00000000..cf853486 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/board_silk.c @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#include "board.h" + +static const BoardConfigHostSPI s_host_spi = { + .mcu_int = { + .port = HW_GPIO_PORT_2, + .pin = HW_GPIO_PIN_3, + .function = HW_GPIO_FUNC_GPIO, + }, + + .spi = { + .peripheral = HW_SPI1, + .cs = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_1, + .function = HW_GPIO_FUNC_SPI_EN, + }, + .cs_2 = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_7, + .function = HW_GPIO_FUNC_GPIO, + }, + .clk = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_0, + .function = HW_GPIO_FUNC_SPI_CLK, + }, + .miso_do = { + .port = HW_GPIO_PORT_1, + .pin = HW_GPIO_PIN_3, + .function = HW_GPIO_FUNC_SPI_DO, + }, + .mosi_di = { + .port = HW_GPIO_PORT_0, + .pin = HW_GPIO_PIN_4, + .function = HW_GPIO_FUNC_SPI_DI, + }, + } +}; + +// These are spare GPIOs on both the SILK and ROBERT bigboards which are broken out to a header +// Check out debug_gpio.h for more info on how to leverage them for debug +static BoardConfigGpioDebug s_debug_gpios = { + .num_debug_gpios = 4, + .debug_gpio = { + [0] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_0, + }, + [1] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_1, + }, + [2] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_2, + }, + [3] = { + .port = HW_GPIO_PORT_3, + .pin = HW_GPIO_PIN_3, + }, + } +}; + +const BoardConfigHostSPI * const HOST_SPI = &s_host_spi; +BoardConfigGpioDebug * const DEBUG_GPIOS = &s_debug_gpios; diff --git a/src/bluetooth-fw/da1468x/controller/board/include/board.h b/src/bluetooth-fw/da1468x/controller/board/include/board.h new file mode 100644 index 00000000..e401f60f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/include/board.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#pragma once + +#include "hw_gpio.h" +#include "hw_spi.h" + +#include + +typedef struct GPIOPortPin { + HW_GPIO_PORT port; + HW_GPIO_PIN pin; + HW_GPIO_FUNC function; +} GPIOPortPin; + +typedef struct { + // BT Controller to MCU INT line: + GPIOPortPin mcu_int; + + // BT Controller SPI slave: + struct { + HW_SPI_ID *peripheral; + GPIOPortPin cs; + GPIOPortPin cs_2; + GPIOPortPin clk; + GPIOPortPin miso_do; + GPIOPortPin mosi_di; + } spi; +} BoardConfigHostSPI; + +typedef struct DebugGPIOInfo { + HW_GPIO_PORT port; + HW_GPIO_PIN pin; + bool is_active; +} DebugGPIOInfo; + +typedef struct BoardConfigGpioDebug { + int num_debug_gpios; + DebugGPIOInfo debug_gpio[]; +} BoardConfigGpioDebug; + +typedef struct { + // BT FEM + GPIOPortPin rx; + GPIOPortPin tx; +} BoardConfigBTFEM; + +extern const BoardConfigHostSPI * const HOST_SPI; +extern BoardConfigGpioDebug * const DEBUG_GPIOS; +extern const BoardConfigBTFEM * const BT_FEM; + +#include "board_definitions.h" diff --git a/src/bluetooth-fw/da1468x/controller/board/include/board_definitions.h b/src/bluetooth-fw/da1468x/controller/board/include/board_definitions.h new file mode 100644 index 00000000..39aa7ec4 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/include/board_definitions.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#pragma once +#if PLATFORM_SILK +#include "board_silk.h" +#elif PLATFORM_CALCULUS || PLATFORM_ROBERT +#include "board_robert.h" +#else +#error "Unknown board definition" +#endif diff --git a/src/bluetooth-fw/da1468x/controller/board/include/board_robert.h b/src/bluetooth-fw/da1468x/controller/board/include/board_robert.h new file mode 100644 index 00000000..5508b038 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/include/board_robert.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +// Note TX DMA channel is assumed to be RX DMA channel + 1, see SPI_SLAVE_TO_EXT_MASTER +#define HOST_SPI_RX_DMA_CHANNEL (HW_DMA_CHANNEL_0) +#define HOST_SPI_TX_DMA_CHANNEL (HW_DMA_CHANNEL_1) diff --git a/src/bluetooth-fw/da1468x/controller/board/include/board_silk.h b/src/bluetooth-fw/da1468x/controller/board/include/board_silk.h new file mode 100644 index 00000000..5508b038 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/include/board_silk.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +// Note TX DMA channel is assumed to be RX DMA channel + 1, see SPI_SLAVE_TO_EXT_MASTER +#define HOST_SPI_RX_DMA_CHANNEL (HW_DMA_CHANNEL_0) +#define HOST_SPI_TX_DMA_CHANNEL (HW_DMA_CHANNEL_1) diff --git a/src/bluetooth-fw/da1468x/controller/board/wscript_build b/src/bluetooth-fw/da1468x/controller/board/wscript_build new file mode 100644 index 00000000..5fcef60f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/board/wscript_build @@ -0,0 +1,27 @@ +def _build_board(source_file, name, config_h, idx): + bld.objects( + name=name, + source=[source_file], + use=['dialog_sdk_includes'], + export_includes=['include'], + includes=['include'], + inject_include_files=[config_h], + idx=idx, + ) + +board = bld.env.BOARD +if board in ('silk_evt', 'silk_bb', 'silk_bb2', 'silk'): + _build_board('board_silk.c', 'dialog_board_main', + '../main/config/custom_config_main.h', 3000) + _build_board('board_silk.c', 'dialog_board_boot', + '../boot/config/custom_config_boot.h', 4000) +elif board in ('robert_bb', 'robert_bb2', 'robert_evt', 'cutts_bb'): + _build_board('board_robert.c', 'dialog_board_main', + '../main/config/custom_config_main.h', 3000) + _build_board('board_robert.c', 'dialog_board_boot', + '../boot/config/custom_config_boot.h', 4000) +else: + print bld.env + bld.fatal('Unknown board {}'.format(board)) + +# vim:filetype=python diff --git a/src/bluetooth-fw/da1468x/controller/boot/include/debug_print.h b/src/bluetooth-fw/da1468x/controller/boot/include/debug_print.h new file mode 100644 index 00000000..14fe281a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/include/debug_print.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +void debug_print_str(const char *str); +void debug_print_hex(int val); +void debug_print_str_and_int(const char *str, int value); diff --git a/src/bluetooth-fw/da1468x/controller/boot/include/host_transport_impl.h b/src/bluetooth-fw/da1468x/controller/boot/include/host_transport_impl.h new file mode 100644 index 00000000..ea852cc4 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/include/host_transport_impl.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +// Initializes Dialog part as a SPI Slave and puts a firmware load in motion. +// See host_transport.c for implementation details +void host_transport_begin(void); diff --git a/src/bluetooth-fw/da1468x/controller/boot/ldscripts/mem.ld.h b/src/bluetooth-fw/da1468x/controller/boot/ldscripts/mem.ld.h new file mode 100644 index 00000000..2667d41f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/ldscripts/mem.ld.h @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#include "da1468x_mem_map.h" + +/* Linker script to place sections and symbol values. Should be used together + * with other linker script that defines memory regions CODE_AND_RAM + * It references following symbols, which must be defined in code: + * Reset_Handler : Entry of reset handler + * + * It defines following symbols, which code can use without definition: + * __zero_table_start__ + * __zero_table_end__ + * __etext + * __data_start__ + * __data_end__ + * __bss_start__ + * __bss_end__ + * __StackLimit + * __StackTop + * __stack + */ + +MEMORY +{ + CODE_AND_RAM (rwx): ORIGIN = DATA_RAM_BASE_ADDRESS, LENGTH = 10 * 1024 +} + +ENTRY(Reset_Handler) + +SECTIONS +{ + .text : + { + KEEP(*(.isr_vector)) + /* make sure that IVT doesn't cross 0xC0 */ + . = 0xC0; + + KEEP(*(.patch_table)) + KEEP(*(.default_patch_code_handler_section)) + + *(.text*) + + *(.rodata*) + + KEEP(*(.eh_frame*)) + } > CODE_AND_RAM + + /* To clear multiple BSS sections, + * uncomment .zero.table section and, + * define __STARTUP_CLEAR_BSS_MULTIPLE in startup_ARMCMx.S */ + .zero.table : + { + . = ALIGN(4); + __zero_table_start__ = .; + LONG (__bss_start__) + LONG (__bss_end__ - __bss_start__) + __zero_table_end__ = .; + } > CODE_AND_RAM + + __etext = .; + + /* The initialised data section is stored immediately + at the end of the text section */ + .data : AT (__etext) + { + __data_start__ = .; + *(vtable) + *(.data*) + + . = ALIGN(4); + /* init_array/fini_array moved to flash, align preserved */ + + KEEP(*(.jcr*)) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > CODE_AND_RAM + + /* GNU build id: This is a hash of parts of the binary that uniquely + * identifies the binary. This hash gets inserted by the linker; + * we're passing the flag --build-id=sha1 to do this. + * The variable DIALOG_BUILD_ID is provided, so that the values can be used + * in the firmware code. */ + .note.gnu.build-id : { + PROVIDE(DIALOG_BUILD_ID = .); + KEEP(*(.note.gnu.build-id)) + } > CODE_AND_RAM + + .bss : + { + . = ALIGN(4); + __bss_start__ = .; + *(.bss*) + *(COMMON) + *(retention_mem_zi) + . = ALIGN(4); + __bss_end__ = .; + } > CODE_AND_RAM + + /* .stack_dummy section doesn't contains any symbols. It is only a + * a placeholder for where the ISR stack lives. */ + .stack_dummy (COPY): + { + __StackBottom = .; + *(.stack*) + } > CODE_AND_RAM + + /* Set stack top to end of RAM, and stack limit move down by + * size of stack_dummy section */ + __StackTop = ORIGIN(CODE_AND_RAM) + LENGTH(CODE_AND_RAM); + __StackLimit = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); +} diff --git a/src/bluetooth-fw/da1468x/controller/boot/src/debug_print.c b/src/bluetooth-fw/da1468x/controller/boot/src/debug_print.c new file mode 100644 index 00000000..9ddaeed3 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/src/debug_print.c @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#include "util/string.h" + +extern int _write(int file, char *ptr, int len); + +void debug_print_str(char *str) { + _write(0, str, strlen(str)); +} + +void debug_print_hex(int val) { + char buffer[12] = { }; + itoa(val, buffer, sizeof(buffer)); + debug_print_str(buffer); +} + +void debug_print_str_and_int(char *str, int value) { + debug_print_str(str); + debug_print_hex(value); + debug_print_str("\n"); +} diff --git a/src/bluetooth-fw/da1468x/controller/boot/src/default_registers.c b/src/bluetooth-fw/da1468x/controller/boot/src/default_registers.c new file mode 100644 index 00000000..1a9ce731 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/src/default_registers.c @@ -0,0 +1,486 @@ +/* + * 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. + */ + +#include "sdk_defs.h" + +#include + +// If Dialog ever updates the number of registers or the default values of the registers, one can: +// 1. Set `DEFAULT_REGISTER_DEBUG` to 1 +// 2. Update/Add/Remove the register values using `REG_SETF` +// 3. Flash and boot the dialog chip. +// 4. Attach GDB to ble chip and type `p/x ALL_REGISTERS` +// 5. Copy, reformat, and paste output to the squashed register values. + +#define DEFAULT_REGISTER_DEBUG 0 + +#if DEFAULT_REGISTER_DEBUG +typedef struct AllRegisters { + // DCDC + uint16_t DCDC_CTRL_0_REG; + uint16_t DCDC_CTRL_1_REG; + uint16_t DCDC_CTRL_2_REG; + uint16_t DCDC_V14_0_REG; + uint16_t DCDC_V14_1_REG; + uint16_t DCDC_V18_0_REG; + uint16_t DCDC_V18_1_REG; + uint16_t DCDC_VDD_0_REG; + uint16_t DCDC_VDD_1_REG; + uint16_t DCDC_V18P_0_REG; + uint16_t DCDC_V18P_1_REG; + uint16_t DCDC_RET_0_REG; + uint16_t DCDC_RET_1_REG; + uint16_t DCDC_TRIM_REG; + uint16_t DCDC_TEST_0_REG; + uint16_t DCDC_TEST_1_REG; + uint16_t DCDC_IRQ_CLEAR_REG; + uint16_t DCDC_IRQ_MASK_REG; + + // CRG_TOP + uint16_t BANDGAP_REG; + uint16_t BOD_STATUS_REG; + uint16_t FORCE_SLEEP_REG; + uint16_t LDOS_DISABLE_REG; + uint16_t AON_SPARE_REG; + + // QSPIC + uint32_t QSPIC_CTRLMODE_REG; + uint32_t QSPIC_BURSTCMDA_REG; + uint32_t QSPIC_BURSTCMDB_REG; + uint32_t QSPIC_WRITEDATA_REG; + uint32_t QSPIC_DUMMYDATA_REG; + uint32_t QSPIC_ERASECTRL_REG; + uint32_t QSPIC_ERASECMDA_REG; + uint32_t QSPIC_ERASECMDB_REG; + uint32_t QSPIC_BURSTBRK_REG; + uint32_t QSPIC_STATUSCMD_REG; + uint32_t QSPIC_CHCKERASE_REG; + uint32_t QSPIC_GP_REG; + uint32_t QSPIC_UCODE_START; + + // GPREG + uint16_t PLL_SYS_CTRL1_REG; + uint16_t PLL_SYS_CTRL2_REG; + uint16_t PLL_SYS_CTRL3_REG; + uint16_t PLL_SYS_TEST_REG; + + // CACHE + uint32_t CACHE_CTRL1_REG; + uint32_t CACHE_LNSIZECFG_REG; + uint32_t CACHE_ASSOCCFG_REG; + uint32_t CACHE_CTRL2_REG; + uint32_t CACHE_CTRL3_REG; + uint32_t CACHE_MRM_HITS_REG; + uint32_t CACHE_MRM_MISSES_REG; + uint32_t CACHE_MRM_CTRL_REG; + uint32_t CACHE_MRM_TINT_REG; + uint32_t CACHE_MRM_THRES_REG; + uint32_t SWD_RESET_REG; +} AllRegisters; + +static AllRegisters ALL_REGISTERS; + +#endif // DEFAULT_REGISTER_DEBUG + + +void default_registers_restore(void) { + DCDC->DCDC_CTRL_0_REG = 0x2f24; + DCDC->DCDC_CTRL_1_REG = 0x5410; + DCDC->DCDC_CTRL_2_REG = 0x882d; + DCDC->DCDC_V14_0_REG = 0xa1a4; + DCDC->DCDC_V14_1_REG = 0xd890; + DCDC->DCDC_V18_0_REG = 0xe3e4; + DCDC->DCDC_V18_1_REG = 0xbc90; + DCDC->DCDC_VDD_0_REG = 0xc304; + DCDC->DCDC_VDD_1_REG = 0xec90; + DCDC->DCDC_V18P_0_REG = 0xe3e4; + DCDC->DCDC_V18P_1_REG = 0xbc90; + DCDC->DCDC_RET_0_REG = 0xaaa6; + DCDC->DCDC_RET_1_REG = 0xaa46; + DCDC->DCDC_TRIM_REG = 0x0; + DCDC->DCDC_TEST_0_REG = 0x0; + DCDC->DCDC_TEST_1_REG = 0x0; + DCDC->DCDC_IRQ_CLEAR_REG = 0x0; + DCDC->DCDC_IRQ_MASK_REG = 0x0; + CRG_TOP->BANDGAP_REG = 0x0; + CRG_TOP->BOD_STATUS_REG = 0x7; + CRG_TOP->FORCE_SLEEP_REG = 0x1; + CRG_TOP->LDOS_DISABLE_REG = 0x0; + CRG_TOP->AON_SPARE_REG = 0x0; + QSPIC->QSPIC_CTRLBUS_REG = 0x0; + QSPIC->QSPIC_CTRLMODE_REG = 0x0; + QSPIC->QSPIC_BURSTCMDA_REG = 0x0; + QSPIC->QSPIC_BURSTCMDB_REG = 0x0; + QSPIC->QSPIC_WRITEDATA_REG = 0x0; + QSPIC->QSPIC_DUMMYDATA_REG = 0x0; + QSPIC->QSPIC_ERASECTRL_REG = 0x0; + QSPIC->QSPIC_ERASECMDA_REG = 0x0; + QSPIC->QSPIC_ERASECMDB_REG = 0x0; + QSPIC->QSPIC_BURSTBRK_REG = 0x0; + QSPIC->QSPIC_STATUSCMD_REG = 0x0; + QSPIC->QSPIC_CHCKERASE_REG = 0x0; + QSPIC->QSPIC_GP_REG = 0x0; + QSPIC->QSPIC_UCODE_START = 0x0; + GPREG->PLL_SYS_CTRL1_REG = 0x100; + GPREG->PLL_SYS_CTRL2_REG = 0x2006; + GPREG->PLL_SYS_CTRL3_REG = 0x3c09; + GPREG->PLL_SYS_TEST_REG = 0x70; + CACHE->CACHE_CTRL1_REG = 0x0; + CACHE->CACHE_LNSIZECFG_REG = 0x0; + CACHE->CACHE_ASSOCCFG_REG = 0x2; + CACHE->CACHE_CTRL2_REG = 0x0; + CACHE->CACHE_CTRL3_REG = 0x22; + CACHE->CACHE_MRM_HITS_REG = 0x0; + CACHE->CACHE_MRM_MISSES_REG = 0x0; + CACHE->CACHE_MRM_CTRL_REG = 0x0; + CACHE->CACHE_MRM_TINT_REG = 0x0; + CACHE->CACHE_MRM_THRES_REG = 0x0; + CACHE->SWD_RESET_REG = 0x0; + +#if DEFAULT_REGISTER_DEBUG + // DCDC + REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_FAST_STARTUP, 0x0); + REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_BROWNOUT_LV_MODE, 0x1); + REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_IDLE_CLK_DIV, 0x1); + REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_PRIORITY, 0xE4); + REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_FW_ENABLE, 0x1); + REG_SETF(DCDC, DCDC_CTRL_0_REG, DCDC_MODE, 0); + + REG_SETF(DCDC, DCDC_CTRL_1_REG, DCDC_STARTUP_DELAY, 0xA); + REG_SETF(DCDC, DCDC_CTRL_1_REG, DCDC_GLOBAL_MAX_IDLE_TIME, 0x20); + REG_SETF(DCDC, DCDC_CTRL_1_REG, DCDC_TIMEOUT, 0x10); + + REG_SETF(DCDC, DCDC_CTRL_2_REG, DCDC_TIMEOUT_IRQ_TRIG, 0x8); + REG_SETF(DCDC, DCDC_CTRL_2_REG, DCDC_TIMEOUT_IRQ_RES, 0x8); + REG_SETF(DCDC, DCDC_CTRL_2_REG, DCDC_TUNE, 0x0); + REG_SETF(DCDC, DCDC_CTRL_2_REG, DCDC_LSSUP_TRIM, 0x5); + REG_SETF(DCDC, DCDC_CTRL_2_REG, DCDC_HSGND_TRIM, 0x5); + + REG_SETF(DCDC, DCDC_V14_0_REG, DCDC_V14_FAST_RAMPING, 0x1); + REG_SETF(DCDC, DCDC_V14_0_REG, DCDC_V14_VOLTAGE, 0x8); + REG_SETF(DCDC, DCDC_V14_0_REG, DCDC_V14_CUR_LIM_MAX_HV, 0xD); + REG_SETF(DCDC, DCDC_V14_0_REG, DCDC_V14_CUR_LIM_MIN, 0x4); + + REG_SETF(DCDC, DCDC_V14_1_REG, DCDC_V14_ENABLE_HV, 0x1); + REG_SETF(DCDC, DCDC_V14_1_REG, DCDC_V14_ENABLE_LV, 0x1); + REG_SETF(DCDC, DCDC_V14_1_REG, DCDC_V14_CUR_LIM_MAX_LV, 0x6); + REG_SETF(DCDC, DCDC_V14_1_REG, DCDC_V14_IDLE_HYST, 0x4); + REG_SETF(DCDC, DCDC_V14_1_REG, DCDC_V14_IDLE_MIN, 0x10); + + REG_SETF(DCDC, DCDC_V18_0_REG, DCDC_V18_FAST_RAMPING, 0x1); + REG_SETF(DCDC, DCDC_V18_0_REG, DCDC_V18_VOLTAGE, 0x18); + REG_SETF(DCDC, DCDC_V18_0_REG, DCDC_V18_CUR_LIM_MAX_HV, 0x1F); + REG_SETF(DCDC, DCDC_V18_0_REG, DCDC_V18_CUR_LIM_MIN, 0x4); + + REG_SETF(DCDC, DCDC_V18_1_REG, DCDC_V18_ENABLE_HV, 0x1); + REG_SETF(DCDC, DCDC_V18_1_REG, DCDC_V18_ENABLE_LV, 0x0); + REG_SETF(DCDC, DCDC_V18_1_REG, DCDC_V18_CUR_LIM_MAX_LV, 0xF); + REG_SETF(DCDC, DCDC_V18_1_REG, DCDC_V18_IDLE_HYST, 0x4); + REG_SETF(DCDC, DCDC_V18_1_REG, DCDC_V18_IDLE_MIN, 0x10); + + REG_SETF(DCDC, DCDC_VDD_0_REG, DCDC_VDD_FAST_RAMPING, 0x1); + REG_SETF(DCDC, DCDC_VDD_0_REG, DCDC_VDD_VOLTAGE, 0x10); + REG_SETF(DCDC, DCDC_VDD_0_REG, DCDC_VDD_CUR_LIM_MAX_HV, 0x18); + REG_SETF(DCDC, DCDC_VDD_0_REG, DCDC_VDD_CUR_LIM_MIN, 0x4); + + REG_SETF(DCDC, DCDC_VDD_1_REG, DCDC_VDD_ENABLE_HV, 0x1); + REG_SETF(DCDC, DCDC_VDD_1_REG, DCDC_VDD_ENABLE_LV, 0x1); + REG_SETF(DCDC, DCDC_VDD_1_REG, DCDC_VDD_CUR_LIM_MAX_LV, 0xB); + REG_SETF(DCDC, DCDC_VDD_1_REG, DCDC_VDD_IDLE_HYST, 0x4); + REG_SETF(DCDC, DCDC_VDD_1_REG, DCDC_VDD_IDLE_MIN, 0x10); + + REG_SETF(DCDC, DCDC_V18P_0_REG, DCDC_V18P_FAST_RAMPING, 0x1); + REG_SETF(DCDC, DCDC_V18P_0_REG, DCDC_V18P_VOLTAGE, 0x18); + REG_SETF(DCDC, DCDC_V18P_0_REG, DCDC_V18P_CUR_LIM_MAX_HV, 0x1F); + REG_SETF(DCDC, DCDC_V18P_0_REG, DCDC_V18P_CUR_LIM_MIN, 0x4); + + REG_SETF(DCDC, DCDC_V18P_1_REG, DCDC_V18P_ENABLE_HV, 0x1); + REG_SETF(DCDC, DCDC_V18P_1_REG, DCDC_V18P_ENABLE_LV, 0x0); + REG_SETF(DCDC, DCDC_V18P_1_REG, DCDC_V18P_CUR_LIM_MAX_LV, 0xF); + REG_SETF(DCDC, DCDC_V18P_1_REG, DCDC_V18P_IDLE_HYST, 0x4); + REG_SETF(DCDC, DCDC_V18P_1_REG, DCDC_V18P_IDLE_MIN, 0x10); + + REG_SETF(DCDC, DCDC_RET_0_REG, DCDC_V18P_RET_CYCLES, 0x5); + REG_SETF(DCDC, DCDC_RET_0_REG, DCDC_V18P_CUR_LIM_RET, 0xA); + REG_SETF(DCDC, DCDC_RET_0_REG, DCDC_VDD_RET_CYCLES, 0x5); + REG_SETF(DCDC, DCDC_RET_0_REG, DCDC_VDD_CUR_LIM_RET, 0x6); + + REG_SETF(DCDC, DCDC_RET_1_REG, DCDC_V18_RET_CYCLES, 0x5); + REG_SETF(DCDC, DCDC_RET_1_REG, DCDC_V18_CUR_LIM_RET, 0xA); + REG_SETF(DCDC, DCDC_RET_1_REG, DCDC_V14_RET_CYCLES, 0x2); + REG_SETF(DCDC, DCDC_RET_1_REG, DCDC_V14_CUR_LIM_RET, 0x6); + + REG_SETF(DCDC, DCDC_TRIM_REG, DCDC_P_COMP_MAN_TRIM, 0x0); + REG_SETF(DCDC, DCDC_TRIM_REG, DCDC_P_COMP_TRIM, 0x0); + REG_SETF(DCDC, DCDC_TRIM_REG, DCDC_N_COMP_MAN_TRIM, 0x0); + REG_SETF(DCDC, DCDC_TRIM_REG, DCDC_N_COMP_TRIM, 0x0); + + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_COMP_CLK, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_CURRENT, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_OUTPUT_MONITOR, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_ANA_TEST, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_IDLE, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_V18P, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_VDD, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_V18, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_V14, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_FW, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_NSW, 0x0); + REG_SETF(DCDC, DCDC_TEST_0_REG, DCDC_FORCE_PSW, 0x0); + + REG_SETF(DCDC, DCDC_TEST_1_REG, DCDC_COMP_CLK, 0x0); + REG_SETF(DCDC, DCDC_TEST_1_REG, DCDC_TEST_CURRENT, 0x0); + REG_SETF(DCDC, DCDC_TEST_1_REG, DCDC_TEST_REG, 0x0); + + REG_SETF(DCDC, DCDC_IRQ_CLEAR_REG, DCDC_BROWN_OUT_IRQ_CLEAR, 0x0); + REG_SETF(DCDC, DCDC_IRQ_CLEAR_REG, DCDC_V18P_TIMEOUT_IRQ_CLEAR, 0x0); + REG_SETF(DCDC, DCDC_IRQ_CLEAR_REG, DCDC_VDD_TIMEOUT_IRQ_CLEAR, 0x0); + REG_SETF(DCDC, DCDC_IRQ_CLEAR_REG, DCDC_V18_TIMEOUT_IRQ_CLEAR, 0x0); + REG_SETF(DCDC, DCDC_IRQ_CLEAR_REG, DCDC_V14_TIMEOUT_IRQ_CLEAR, 0x0); + + REG_SETF(DCDC, DCDC_IRQ_MASK_REG, DCDC_BROWN_OUT_IRQ_MASK, 0x0); + REG_SETF(DCDC, DCDC_IRQ_MASK_REG, DCDC_V18P_TIMEOUT_IRQ_MASK, 0x0); + REG_SETF(DCDC, DCDC_IRQ_MASK_REG, DCDC_VDD_TIMEOUT_IRQ_MASK, 0x0); + REG_SETF(DCDC, DCDC_IRQ_MASK_REG, DCDC_V18_TIMEOUT_IRQ_MASK, 0x0); + REG_SETF(DCDC, DCDC_IRQ_MASK_REG, DCDC_V14_TIMEOUT_IRQ_MASK, 0x0); + + + // CRG_TOP + REG_SETF(CRG_TOP, BANDGAP_REG, BYPASS_COLD_BOOT_DISABLE, 0x0); + REG_SETF(CRG_TOP, BANDGAP_REG, LDO_SLEEP_TRIM, 0x0); + REG_SETF(CRG_TOP, BANDGAP_REG, BGR_ITRIM, 0x0); + REG_SETF(CRG_TOP, BANDGAP_REG, BGR_TRIM, 0x0); + + REG_SETF(CRG_TOP, BOD_STATUS_REG, BOD_VBAT_LOW, 0x0); + REG_SETF(CRG_TOP, BOD_STATUS_REG, BOD_V33_LOW, 0x0); + REG_SETF(CRG_TOP, BOD_STATUS_REG, BOD_1V8_FLASH_LOW, 0x0); + REG_SETF(CRG_TOP, BOD_STATUS_REG, BOD_1V8_PA_LOW, 0x0); + REG_SETF(CRG_TOP, BOD_STATUS_REG, BOD_VDD_LOW, 0x0); + + REG_SETF(CRG_TOP, FORCE_SLEEP_REG, FORCE_BLE_SLEEP, 0x0); // GUESS + REG_SETF(CRG_TOP, FORCE_SLEEP_REG, FORCE_FTDF_SLEEP, 0x0); // GUESS + + REG_SETF(CRG_TOP, LDOS_DISABLE_REG, LDOS_DISABLE, 0x0); // GUESS + + REG_SETF(CRG_TOP, AON_SPARE_REG, OSC16_HOLD_AMP_REG, 0x0); // GUESS + REG_SETF(CRG_TOP, AON_SPARE_REG, OSC16_SH_DISABLE, 0x0); // GUESS + REG_SETF(CRG_TOP, AON_SPARE_REG, EN_BATSYS_RET, 0x0); // GUESS + REG_SETF(CRG_TOP, AON_SPARE_REG, EN_BUSSYS_RET, 0x0); // GUESS + + + // QSPIC + REG_SETF(QSPIC, QSPIC_CTRLBUS_REG, QSPIC_DIS_CS, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLBUS_REG, QSPIC_EN_CS, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLBUS_REG, QSPIC_SET_QUAD, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLBUS_REG, QSPIC_SET_DUAL, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLBUS_REG, QSPIC_SET_SINGLE, 0x0); + + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_USE_32BA, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_FORCENSEQ_EN, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_PCLK_MD, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_RPIPE_EN, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_RXD_NEG, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_HRDY_MD, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_IO3_DAT, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_IO2_DAT, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_IO3_OEN, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_IO2_OEN, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_CLK_MD, 0x0); + REG_SETF(QSPIC, QSPIC_CTRLMODE_REG, QSPIC_AUTO_MD, 0x0); + + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_DMY_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_EXT_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_ADR_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_INST_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_EXT_BYTE, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_INST_WB, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDA_REG, QSPIC_INST, 0x0); + + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_DMY_FORCE, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_CS_HIGH_MIN, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_WRAP_SIZE, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_WRAP_LEN, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_WRAP_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_INST_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_DMY_NUM, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_EXT_HF_DS, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_EXT_BYTE_EN, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTCMDB_REG, QSPIC_DAT_RX_MD, 0x0); + + REG_SETF(QSPIC, QSPIC_WRITEDATA_REG, QSPIC_WRITEDATA, 0x0); + + REG_SETF(QSPIC, QSPIC_DUMMYDATA_REG, QSPIC_DUMMYDATA, 0x0); + + REG_SETF(QSPIC, QSPIC_ERASECTRL_REG, QSPIC_ERASE_EN, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECTRL_REG, QSPIC_ERS_ADDR, 0x0); + + REG_SETF(QSPIC, QSPIC_ERASECMDA_REG, QSPIC_RES_INST, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDA_REG, QSPIC_SUS_INST, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDA_REG, QSPIC_WEN_INST, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDA_REG, QSPIC_ERS_INST, 0x0); + + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_RESSUS_DLY, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_ERSRES_HLD, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_ERS_CS_HI, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_EAD_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_RES_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_SUS_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_WEN_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_ERASECMDB_REG, QSPIC_ERS_TX_MD, 0x0); + + REG_SETF(QSPIC, QSPIC_BURSTBRK_REG, QSPIC_SEC_HF_DS, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTBRK_REG, QSPIC_BRK_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTBRK_REG, QSPIC_BRK_SZ, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTBRK_REG, QSPIC_BRK_EN, 0x0); + REG_SETF(QSPIC, QSPIC_BURSTBRK_REG, QSPIC_BRK_WRD, 0x0); + + REG_SETF(QSPIC, QSPIC_STATUSCMD_REG, QSPIC_STSDLY_SEL, 0x0); + REG_SETF(QSPIC, QSPIC_STATUSCMD_REG, QSPIC_RESSTS_DLY, 0x0); + REG_SETF(QSPIC, QSPIC_STATUSCMD_REG, QSPIC_BUSY_VAL, 0x0); + REG_SETF(QSPIC, QSPIC_STATUSCMD_REG, QSPIC_RSTAT_RX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_STATUSCMD_REG, QSPIC_RSTAT_TX_MD, 0x0); + REG_SETF(QSPIC, QSPIC_STATUSCMD_REG, QSPIC_RSTAT_INST, 0x0); + + REG_SETF(QSPIC, QSPIC_CHCKERASE_REG, QSPIC_CHCKERASE, 0x0); + + REG_SETF(QSPIC, QSPIC_GP_REG, QSPIC_PADS_SLEW, 0x0); + REG_SETF(QSPIC, QSPIC_GP_REG, QSPIC_PADS_DRV, 0x0); + + REG_SETF(QSPIC, QSPIC_UCODE_START, QSPIC_UCODE_X, 0x0); + + + // GPREG + REG_SETF(GPREG, PLL_SYS_CTRL1_REG, PLL_R_DIV, 0x1); + REG_SETF(GPREG, PLL_SYS_CTRL1_REG, LDO_PLL_VREF_HOLD, 0x0); + REG_SETF(GPREG, PLL_SYS_CTRL1_REG, LDO_PLL_ENABLE, 0x0); + REG_SETF(GPREG, PLL_SYS_CTRL1_REG, PLL_EN, 0x0); + + REG_SETF(GPREG, PLL_SYS_CTRL2_REG, PLL_SEL_MIN_CUR_INT, 0x0); + REG_SETF(GPREG, PLL_SYS_CTRL2_REG, PLL_DEL_SEL, 0x2); + REG_SETF(GPREG, PLL_SYS_CTRL2_REG, PLL_N_DIV, 0x6); + + REG_SETF(GPREG, PLL_SYS_CTRL3_REG, PLL_RECALIB, 0x0); // GUESS + REG_SETF(GPREG, PLL_SYS_CTRL3_REG, PLL_START_DEL, 0xF); + REG_SETF(GPREG, PLL_SYS_CTRL3_REG, PLL_ICP_SEL, 0x9); + + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_LOCK_DET_RES_CNT, 0x0); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_SEL_R_DIV_TEST, 0x0); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_SEL_N_DIV_TEST, 0x0); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_CHANGE, 0x0); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_OPEN_LOOP, 0x0); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_TEST_VCTR, 0x0); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_MIN_CURRENT, 0x38); + REG_SETF(GPREG, PLL_SYS_TEST_REG, PLL_DIS_LOOPFILT, 0x0); + + + // CACHE + REG_SETF(CACHE, CACHE_CTRL1_REG, CACHE_RES1, 0x0); + REG_SETF(CACHE, CACHE_CTRL1_REG, CACHE_FLUSH, 0x0); + + REG_SETF(CACHE, CACHE_LNSIZECFG_REG, CACHE_LINE, 0x0); + + REG_SETF(CACHE, CACHE_ASSOCCFG_REG, CACHE_ASSOC, 0x2); + + REG_SETF(CACHE, CACHE_CTRL2_REG, ENABLE_ALSO_QSPIFLASH_CACHED, 0x0); + REG_SETF(CACHE, CACHE_CTRL2_REG, ENABLE_ALSO_OTP_CACHED, 0x0); + REG_SETF(CACHE, CACHE_CTRL2_REG, CACHE_CGEN, 0x0); + REG_SETF(CACHE, CACHE_CTRL2_REG, CACHE_WEN, 0x0); + REG_SETF(CACHE, CACHE_CTRL2_REG, CACHE_LEN, 0x0); + + REG_SETF(CACHE, CACHE_CTRL3_REG, CACHE_CONTROLLER_RESET, 0x0); + REG_SETF(CACHE, CACHE_CTRL3_REG, CACHE_RAM_SIZE_RESET_VALUE, 0x2); + REG_SETF(CACHE, CACHE_CTRL3_REG, CACHE_LINE_SIZE_RESET_VALUE, 0x0); + REG_SETF(CACHE, CACHE_CTRL3_REG, CACHE_ASSOCIATIVITY_RESET_VALUE, 0x2); + + REG_SETF(CACHE, CACHE_MRM_HITS_REG, MRM_HITS, 0x0); + + REG_SETF(CACHE, CACHE_MRM_MISSES_REG, MRM_MISSES, 0x0); + + REG_SETF(CACHE, CACHE_MRM_CTRL_REG, MRM_IRQ_THRES_STATUS, 0x0); + REG_SETF(CACHE, CACHE_MRM_CTRL_REG, MRM_IRQ_TINT_STATUS, 0x0); + REG_SETF(CACHE, CACHE_MRM_CTRL_REG, MRM_IRQ_MASK, 0x0); + REG_SETF(CACHE, CACHE_MRM_CTRL_REG, MRM_START, 0x0); + + REG_SETF(CACHE, CACHE_MRM_TINT_REG, MRM_TINT, 0x0); + + REG_SETF(CACHE, CACHE_MRM_THRES_REG, MRM_THRES, 0x0); + + REG_SETF(CACHE, SWD_RESET_REG, SWD_HW_RESET_REQ, 0x0); + + // DCDC + ALL_REGISTERS.DCDC_CTRL_0_REG = DCDC->DCDC_CTRL_0_REG; + ALL_REGISTERS.DCDC_CTRL_1_REG = DCDC->DCDC_CTRL_1_REG; + ALL_REGISTERS.DCDC_CTRL_2_REG = DCDC->DCDC_CTRL_2_REG; + ALL_REGISTERS.DCDC_V14_0_REG = DCDC->DCDC_V14_0_REG; + ALL_REGISTERS.DCDC_V14_1_REG = DCDC->DCDC_V14_1_REG; + ALL_REGISTERS.DCDC_V18_0_REG = DCDC->DCDC_V18_0_REG; + ALL_REGISTERS.DCDC_V18_1_REG = DCDC->DCDC_V18_1_REG; + ALL_REGISTERS.DCDC_VDD_0_REG = DCDC->DCDC_VDD_0_REG; + ALL_REGISTERS.DCDC_VDD_1_REG = DCDC->DCDC_VDD_1_REG; + ALL_REGISTERS.DCDC_V18P_0_REG = DCDC->DCDC_V18P_0_REG; + ALL_REGISTERS.DCDC_V18P_1_REG = DCDC->DCDC_V18P_1_REG; + ALL_REGISTERS.DCDC_RET_0_REG = DCDC->DCDC_RET_0_REG; + ALL_REGISTERS.DCDC_RET_1_REG = DCDC->DCDC_RET_1_REG; + ALL_REGISTERS.DCDC_TRIM_REG = DCDC->DCDC_TRIM_REG; + ALL_REGISTERS.DCDC_TEST_0_REG = DCDC->DCDC_TEST_0_REG; + ALL_REGISTERS.DCDC_TEST_1_REG = DCDC->DCDC_TEST_1_REG; + + ALL_REGISTERS.DCDC_IRQ_CLEAR_REG = DCDC->DCDC_IRQ_CLEAR_REG; + ALL_REGISTERS.DCDC_IRQ_MASK_REG = DCDC->DCDC_IRQ_MASK_REG; + + // CRG_TOP + ALL_REGISTERS.BANDGAP_REG = CRG_TOP->BANDGAP_REG; + ALL_REGISTERS.BOD_STATUS_REG = CRG_TOP->BOD_STATUS_REG; + ALL_REGISTERS.FORCE_SLEEP_REG = CRG_TOP->FORCE_SLEEP_REG; + ALL_REGISTERS.LDOS_DISABLE_REG = CRG_TOP->LDOS_DISABLE_REG; + ALL_REGISTERS.AON_SPARE_REG = CRG_TOP->AON_SPARE_REG; + + // QSPIC + ALL_REGISTERS.QSPIC_CTRLMODE_REG = QSPIC->QSPIC_CTRLMODE_REG; + ALL_REGISTERS.QSPIC_BURSTCMDA_REG = QSPIC->QSPIC_BURSTCMDA_REG; + ALL_REGISTERS.QSPIC_BURSTCMDB_REG = QSPIC->QSPIC_BURSTCMDB_REG; + ALL_REGISTERS.QSPIC_WRITEDATA_REG = QSPIC->QSPIC_WRITEDATA_REG; + ALL_REGISTERS.QSPIC_DUMMYDATA_REG = QSPIC->QSPIC_DUMMYDATA_REG; + ALL_REGISTERS.QSPIC_ERASECTRL_REG = QSPIC->QSPIC_ERASECTRL_REG; + ALL_REGISTERS.QSPIC_ERASECMDA_REG = QSPIC->QSPIC_ERASECMDA_REG; + ALL_REGISTERS.QSPIC_ERASECMDB_REG = QSPIC->QSPIC_ERASECMDB_REG; + ALL_REGISTERS.QSPIC_BURSTBRK_REG = QSPIC->QSPIC_BURSTBRK_REG; + ALL_REGISTERS.QSPIC_STATUSCMD_REG = QSPIC->QSPIC_STATUSCMD_REG; + ALL_REGISTERS.QSPIC_CHCKERASE_REG = QSPIC->QSPIC_CHCKERASE_REG; + ALL_REGISTERS.QSPIC_GP_REG = QSPIC->QSPIC_GP_REG; + ALL_REGISTERS.QSPIC_UCODE_START = QSPIC->QSPIC_UCODE_START; + + // GPREG + ALL_REGISTERS.PLL_SYS_CTRL1_REG = GPREG->PLL_SYS_CTRL1_REG; + ALL_REGISTERS.PLL_SYS_CTRL2_REG = GPREG->PLL_SYS_CTRL2_REG; + ALL_REGISTERS.PLL_SYS_CTRL3_REG = GPREG->PLL_SYS_CTRL3_REG; + ALL_REGISTERS.PLL_SYS_TEST_REG = GPREG->PLL_SYS_TEST_REG; + + // CACHE + ALL_REGISTERS.CACHE_CTRL1_REG = CACHE->CACHE_CTRL1_REG; + ALL_REGISTERS.CACHE_LNSIZECFG_REG = CACHE->CACHE_LNSIZECFG_REG; + ALL_REGISTERS.CACHE_ASSOCCFG_REG = CACHE->CACHE_ASSOCCFG_REG; + ALL_REGISTERS.CACHE_CTRL2_REG = CACHE->CACHE_CTRL2_REG; + ALL_REGISTERS.CACHE_CTRL3_REG = CACHE->CACHE_CTRL3_REG; + ALL_REGISTERS.CACHE_MRM_HITS_REG = CACHE->CACHE_MRM_HITS_REG; + ALL_REGISTERS.CACHE_MRM_MISSES_REG = CACHE->CACHE_MRM_MISSES_REG; + ALL_REGISTERS.CACHE_MRM_CTRL_REG = CACHE->CACHE_MRM_CTRL_REG; + ALL_REGISTERS.CACHE_MRM_TINT_REG = CACHE->CACHE_MRM_TINT_REG; + ALL_REGISTERS.CACHE_MRM_THRES_REG = CACHE->CACHE_MRM_THRES_REG; + ALL_REGISTERS.SWD_RESET_REG = CACHE->SWD_RESET_REG; + +#endif // DEFAULT_REGISTER_DEBUG +} diff --git a/src/bluetooth-fw/da1468x/controller/boot/src/fault_handling.c b/src/bluetooth-fw/da1468x/controller/boot/src/fault_handling.c new file mode 100644 index 00000000..7b27925f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/src/fault_handling.c @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#include "debug_print.h" +#include "hw_watchdog.h" +#include "sdk_defs.h" + +#include +#include +#include + +static void prv_handle_fault(void) { +#if NO_WATCHDOG + while (1) {}; +#else + NVIC_SystemReset(); +#endif +} + +void HardFault_HandlerC(uint32_t *fault_args) { + debug_print_str("Hard fault"); + prv_handle_fault(); +} + +void UsageFault_HandlerC(uint32_t *fault_args) { + debug_print_str("Usage fault"); + prv_handle_fault(); +} + +void BusFault_HandlerC(uint32_t *fault_args) { + debug_print_str("Bus fault"); + prv_handle_fault(); +} + +void MemManag_HandlerC(uint32_t *fault_args) { + debug_print_str("MemManag fault"); + prv_handle_fault(); +} + +void NMI_HandlerC(uint32_t *fault_args) { + debug_print_str("NMI Handler"); + prv_handle_fault(); +} diff --git a/src/bluetooth-fw/da1468x/controller/boot/src/host_transport.c b/src/bluetooth-fw/da1468x/controller/boot/src/host_transport.c new file mode 100644 index 00000000..618eef9a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/src/host_transport.c @@ -0,0 +1,207 @@ +/* + * 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. + */ + +#include +#include + +#include "da1468x_mem_map.h" + +#include "debug_print.h" +#include "hw_gpio.h" +#include "hw_spi.h" + +#include "board.h" +#include "host_transport.h" +#include "util/crc32.h" + +// Dialog SPI bootloader implementation +// +// See following doc for protocol specification: +// https://docs.google.com/document/d/1PrnTsDhBZYsrlxa9-6OzdoEvtE50uVkSTufqEdQ2yWw/ + +// The arm vector table has a default size of 0x40 bytes. The rest of the space +// is variable depending on the number of IRQn interrupt handlers implemented +// by the platform. For the dialog part 32 IRQs are provided. There is also a +// patch area used by the dialog BT ROM which must be loaded and comes directly +// after the vector table, it's 128 bytes +#define IVT_TABLE_SIZE (0x40 + 32 * 4 + 0x80) +static uint8_t s_vector_table[IVT_TABLE_SIZE]; + +static volatile bool s_expected_bytes_received = false; +static void prv_expect_byte_spi_int_cb(void *user_data, uint16_t transferred) { + s_expected_bytes_received = true; + debug_print_str_and_int("Bytes TX/RXed: ", transferred); +} + +// Interesting observation: The INT fires on writes once the data has been drained to the FIFO, +// not when it actually gets drained. +static void prv_write_or_read_bytes(void *byte_buffer, int num_bytes, bool do_write) { + s_expected_bytes_received = false; + if (do_write) { + hw_spi_write_buf(HOST_SPI->spi.peripheral, + byte_buffer, num_bytes, prv_expect_byte_spi_int_cb, NULL); + } else { + hw_spi_read_buf(HOST_SPI->spi.peripheral, + byte_buffer, num_bytes, prv_expect_byte_spi_int_cb, NULL); + } + + hw_gpio_set_active(HOST_SPI->mcu_int.port, HOST_SPI->mcu_int.pin); + // Probably not necessary but add a little delay so its easier to catch INT + // on logic analyzer + for (volatile int delay = 0; delay < 50; delay++) { } + + while (!s_expected_bytes_received) { }; + hw_gpio_set_inactive(HOST_SPI->mcu_int.port, HOST_SPI->mcu_int.pin); +} + +static void prv_handle_hi_command(void) { + debug_print_str("HI CMD\n"); + uint8_t response[] = { 'H', 'E', 'R', 'E' }; + prv_write_or_read_bytes(response, sizeof(response), true); +} + +static void prv_send_crc_of_mem_region(void *data_start, size_t len) { + uint32_t crc = CRC32_INIT; + crc = crc32(crc, data_start, len); + + prv_write_or_read_bytes((uint8_t *)&crc, sizeof(crc), true); + debug_print_str_and_int("Computed CRC: ", (int)crc); +} + +static void prv_handle_load_data_command(void) { + debug_print_str("LD CMD\n"); + + struct __attribute__((packed)) { + uint32_t copy_address; + uint32_t length; + } send_data_cmd_payload = { }; + + prv_write_or_read_bytes(&send_data_cmd_payload, sizeof(send_data_cmd_payload), false); + + debug_print_str_and_int(" Address:", send_data_cmd_payload.copy_address); + debug_print_str_and_int(" Length:", send_data_cmd_payload.length); + + prv_write_or_read_bytes((uint8_t*)send_data_cmd_payload.copy_address, + send_data_cmd_payload.length, false); + + prv_send_crc_of_mem_region( + (void *)send_data_cmd_payload.copy_address, send_data_cmd_payload.length); +} + +static void prv_handle_vector_table_update_command(void) { + debug_print_str("VT CMD\n"); + + // reset vector table + memset(s_vector_table, 0x0, sizeof(s_vector_table)); + + uint8_t number_of_entries = 0; + prv_write_or_read_bytes(&number_of_entries, sizeof(number_of_entries), false); + + size_t vector_table_copy_size = number_of_entries * 4; + prv_write_or_read_bytes(s_vector_table, vector_table_copy_size, false); + + prv_send_crc_of_mem_region(&s_vector_table[0], vector_table_copy_size); +} + +static void prv_handle_reboot_command(void) { + debug_print_str("RT CMD\n"); + const uint32_t vt_start_addr = DATA_RAM_BASE_ADDRESS; + + // We are about to overwrite the vector table. Disable interrupts while + // this is taking place + __disable_irq(); + + // Interrupt Clear Enable Register: + NVIC->ICER[0] = ~0; + // Interrupt Clear Pending Register: + NVIC->ICPR[0] = ~0; + + for (size_t i = 0; i < sizeof(s_vector_table); i++) { + *(uint8_t *)(vt_start_addr + i) = s_vector_table[i]; + } + + NVIC_SystemReset(); + __builtin_unreachable(); +} + +static void prv_bootloader_loop(void) { + debug_print_str("Beginning Bootloader Loop\n"); + while (1) { + uint8_t cmd[2] = { }; + prv_write_or_read_bytes(cmd, sizeof(cmd), false); + + if (cmd[0] == 'H' && cmd[1] == 'I') { + prv_handle_hi_command(); + } else if (cmd[0] == 'L' && cmd[1] == 'D') { + prv_handle_load_data_command(); + } else if (cmd[0] == 'V' && cmd[1] == 'T') { + prv_handle_vector_table_update_command(); + } else if (cmd[0] == 'R' && cmd[1] == 'T') { + prv_handle_reboot_command(); + } else { + debug_print_str("Unknown CMD:"); + debug_print_str_and_int(" Byte 0:", cmd[0]); + debug_print_str_and_int(" Byte 1:", cmd[1]); + } + } +} + +static void prv_configure_pins_for_spi_transfer(void) { + hw_gpio_set_pin_function(HOST_SPI->spi.cs.port, HOST_SPI->spi.cs.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.cs.function); + hw_gpio_set_pin_function(HOST_SPI->spi.cs_2.port, HOST_SPI->spi.cs_2.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.cs_2.function); + hw_gpio_set_pin_function(HOST_SPI->spi.clk.port, HOST_SPI->spi.clk.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.clk.function); + hw_gpio_set_pin_function(HOST_SPI->spi.mosi_di.port, HOST_SPI->spi.mosi_di.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.mosi_di.function); + hw_gpio_set_pin_function(HOST_SPI->spi.miso_do.port, HOST_SPI->spi.miso_do.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.miso_do.function); + + hw_gpio_configure_pin(HOST_SPI->mcu_int.port, HOST_SPI->mcu_int.pin, + HW_GPIO_MODE_OUTPUT, HOST_SPI->mcu_int.function, false); +} + +static void prv_configure_spi_peripheral(void) { + spi_config config = { + .cs_pad = { 0, 0 }, + .word_mode = HW_SPI_WORD_8BIT, + .smn_role = HW_SPI_MODE_SLAVE, + .phase_mode = HW_SPI_PHA_MODE_0, + .polarity_mode = HW_SPI_POL_LOW, + .mint_mode = HW_SPI_MINT_DISABLE, // we are not using this feature + .xtal_freq = 0, + .fifo_mode = HW_SPI_FIFO_RX_TX, + .disabled = 0, +#ifdef HW_SPI_DMA_SUPPORT + .use_dma = 1, + .rx_dma_channel = HOST_SPI_RX_DMA_CHANNEL, + .tx_dma_channel = HOST_SPI_TX_DMA_CHANNEL, +#endif + }; + + hw_spi_init(HOST_SPI->spi.peripheral, &config); +} + +void host_transport_begin(void) { + prv_configure_pins_for_spi_transfer(); + + prv_configure_spi_peripheral(); + + prv_bootloader_loop(); + + __builtin_unreachable(); +} diff --git a/src/bluetooth-fw/da1468x/controller/boot/src/passert.c b/src/bluetooth-fw/da1468x/controller/boot/src/passert.c new file mode 100644 index 00000000..3e187e92 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/src/passert.c @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#include + +#include "debug_print.h" + +#include "sdk_defs.h" + +#include + +//! Implementation for the bootloader. +//! Because ASSERT_ERROR() and ASSERT_WARNING() macros in sdk_defs.h use this function, +//! we need an implementation for the bootloader as well. +NORETURN passert_failed_no_message(void) { + debug_print_str_and_int("ASRT", (int)__builtin_return_address(0)); +#if NO_WATCHDOG + while (1) {}; +#else + NVIC_SystemReset(); +#endif + __builtin_unreachable(); +} diff --git a/src/bluetooth-fw/da1468x/controller/boot/wscript b/src/bluetooth-fw/da1468x/controller/boot/wscript new file mode 100644 index 00000000..8d2a39de --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/boot/wscript @@ -0,0 +1,79 @@ +import sys + +import waftools.objcopy + +from resources.types.resource_definition import ResourceDefinition +from resources.types.resource_object import ResourceObject + +CUSTOM_CONFIG_H_PATH = 'config/custom_config_boot.h' + + +def build(bld): + bld.env.append_value('DEFINES', ['BLE_BOOTLOADER=1']) + + sys.path.append(bld.path.find_node('../..').abspath()) + from dialog_waf import get_sdk_node, collect_sdk_sources, generate_mem_ld + + mem_ld_node = generate_mem_ld(bld, CUSTOM_CONFIG_H_PATH) + + # Collect source files: + source_dirs = ['src', '../common/src'] + sources = sum([bld.path.ant_glob('%s/**/*.c' % d) + for d in source_dirs], []) + sdk_sources = [ + 'sdk/bsp/startup/vector_table.S', + 'sdk/bsp/startup/startup_ARMCM0.S', + 'sdk/bsp/startup/system_ARMCM0.c', + 'sdk/bsp/startup/config.c', + 'sdk/bsp/peripherals/src/hw_cpm.c', + 'sdk/bsp/peripherals/src/hw_dma.c', + 'sdk/bsp/peripherals/src/hw_gpio.c', + 'sdk/bsp/peripherals/src/hw_spi.c', + 'sdk/bsp/peripherals/src/hw_uart.c', + 'sdk/bsp/peripherals/src/hw_watchdog.c', + + # Used by system_ARMCM0.c: + 'sdk/bsp/peripherals/src/hw_otpc.c', + 'sdk/bsp/peripherals/src/sys_tcs.c', + ] + sources.extend(collect_sdk_sources(bld, sdk_sources)) + + linkflags = ['-Wl,-Map,bt_da14681_boot.map', + '-Wl,--build-id=sha1'] + + # Includes: + includes = ['include', '../common/include', '../../include', 'config'] + elf_node = bld.path.get_bld().make_node('bt_da14681_boot.elf') + bld.program(features='c asm cprogram', + source=sources, + includes=includes, + target=elf_node, + lib=['gcc'], + linkflags=linkflags, + ldscript=mem_ld_node, + inject_include_files=[CUSTOM_CONFIG_H_PATH], + use=['dialog_sdk_includes', 'dialog_board_boot', + 'pblibc-cm0', 'libutil_includes', 'libutil-cm0']) + + bld.add_manual_dependency(elf_node, mem_ld_node) + + bin_node = elf_node.change_ext('.bin') + bld(rule=waftools.objcopy.objcopy_bin, source=elf_node, target=bin_node) + + def create_bt_patch_resource(task): + bin_data = task.inputs[0].read('rb') + reso = ResourceObject(ResourceDefinition('raw', 'BT_BOOT_IMAGE', None, + storage=task.generator.storage), bin_data) + reso.dump(task.outputs[0]) + + reso_node = elf_node.change_ext('.bin.reso') + bld(rule=create_bt_patch_resource, source=bin_node, target=reso_node, + storage=bld.get_bluetooth_fw_storage()) + + bld.DYNAMIC_RESOURCES.append(reso_node) + +def configure(ctx): + pass + + +# vim:filetype=python diff --git a/src/bluetooth-fw/da1468x/controller/main/config/platform_devices.h b/src/bluetooth-fw/da1468x/controller/main/config/platform_devices.h new file mode 100644 index 00000000..194640c0 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/config/platform_devices.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "board.h" + +SPI_BUS(SPI1) +SPI_SLAVE_TO_EXT_MASTER(SPI1, PEBBLE_HOST, CONFIG_SPI_IGNORE_CS, + CONFIG_SPI_WORD_MODE, CONFIG_SPI_POL_MODE, + CONFIG_SPI_PHASE_MODE, CONFIG_SPI_DMA_CHANNEL); +SPI_BUS_END + +#if dg_configGPADC_ADAPTER + +/* + * Define sources connected to GPADC + */ + +GPADC_SOURCE(TEMP_SENSOR, HW_GPADC_CLOCK_INTERNAL, HW_GPADC_INPUT_MODE_SINGLE_ENDED, + HW_GPADC_INPUT_SE_TEMPSENS, 5, false, HW_GPADC_OVERSAMPLING_1_SAMPLE, + HW_GPADC_INPUT_VOLTAGE_UP_TO_1V2) + +#endif /* dg_configGPADC_ADAPTER */ diff --git a/src/bluetooth-fw/da1468x/controller/main/include/.gitignore b/src/bluetooth-fw/da1468x/controller/main/include/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/src/bluetooth-fw/da1468x/controller/main/include/advert.h b/src/bluetooth-fw/da1468x/controller/main/include/advert.h new file mode 100644 index 00000000..edc94ff9 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/advert.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#pragma once + +#include "advert_state.h" + +// Dialog SDK: +#include "ble_common.h" + +#include +#include + +typedef struct ble_evt_gap_adv_completed ble_evt_gap_adv_completed_t; +typedef struct BLEAdData BLEAdData; + +ble_error_t advert_set_interval(uint16_t min_slots, uint16_t max_slots); + +//! @return The current state, returned for debugging/logging purposes. +//! It's possible this is not AdvertState_Running, in case advertising was paused or is still +//! stopping. +AdvertState advert_enable(void); + +void advert_set_data(const BLEAdData *ad_data); + +void advert_disable(void); + +void advert_handle_completed(const ble_evt_gap_adv_completed_t *evt); + +void advert_init(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/aes.h b/src/bluetooth-fw/da1468x/controller/main/include/aes.h new file mode 100644 index 00000000..e14a51dd --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/aes.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#define AES_128_BLOCK_SIZE (16) +#define AES_128_KEY_SIZE (16) + +bool aes_128_encrypt_block(const uint8_t key[AES_128_KEY_SIZE], + const uint8_t plain_text_block[AES_128_BLOCK_SIZE], + uint8_t cipher_text_block_out[AES_128_BLOCK_SIZE]); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/ble_task.h b/src/bluetooth-fw/da1468x/controller/main/include/ble_task.h new file mode 100644 index 00000000..9b220a61 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/ble_task.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +void ble_task_assert_is_executing_on_ble_task(void); + +//! Start the Dialog BLE stack with the given config and block until it's up and running. +void ble_task_init(const BTDriverConfig *config); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/chip_id.h b/src/bluetooth-fw/da1468x/controller/main/include/chip_id.h new file mode 100644 index 00000000..a6bdcb34 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/chip_id.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#pragma once + +#include "dialog_chip_id.h" +#include "util/attributes.h" +#include + +bool dialog_chip_id_copy(DialogChipID *chip_id_out); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/connection.h b/src/bluetooth-fw/da1468x/controller/main/include/connection.h new file mode 100644 index 00000000..4f229650 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/connection.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#pragma once + +#include "gatt_wrapper_types.h" + +#include +#include + +typedef struct Connection Connection; +typedef struct BTDeviceInternal BTDeviceInternal; +typedef struct BTDeviceAddress BTDeviceAddress; +typedef struct BleConnectionParams BleConnectionParams; +typedef struct PPoGATTWorkAroundState PPoGATTWorkAroundState; +typedef void (*ConnectionForEachCallback)(Connection *connection, void *data); + +// Call once on boot before doing anything with Bluetooth +void connection_module_init(void); + +// Call every time a BT connection is created/destroyed. These ops should +// be driven from events received by the ble_task +//! @param initial_addr The remote address at the time of connection establishment. See notes +//! with the initial_addr field in the Connection struct. +//! @param local_addr The local/own address that was used to advertise at the time of connection +//! establishment. +Connection *connection_create(uint16_t conn_idx, const BTDeviceInternal *initial_addr, + const BTDeviceAddress *local_addr, const BleConnectionParams *params); +void connection_destroy(Connection *connection); + +// Returns true if the pointer given is in our list of connections +bool connection_is_valid(Connection *connection); + +// Enqueues a Gatt Operation to the list of outstanding Gatt Operations in the Connection object. +void connection_enqueue_gatt_op(Connection *connection, uintptr_t context_ref, + GattRespDest resp_dest, GattOpType op_type); +// Dequeues a Gatt Operation from the list of outstanding Gatt Operations in the Connection object. +// Returns true if the object was successfully dequeued and false if there are no operations known +// to be in progress +bool connection_dequeue_gatt_op(Connection *connection, uintptr_t *context_ref, + GattRespDest *resp_dest, GattOpType expected_op_type); +// Pops the most recent Gatt Operation appended to the list. +bool connection_pop_gatt_op(Connection *connection); + +// +// Retrieve Connections +// + +Connection *connection_by_idx(uint16_t conn_idx); +Connection *connection_by_idx_check(uint16_t conn_idx); +Connection *connection_by_address(const BTDeviceInternal *addr_buf); +Connection *connection_by_address_check(const BTDeviceInternal *addr_out); + +// @note: internal lock will be held for the duration of this call +void connection_for_each(ConnectionForEachCallback cb, void *data); + +// +// Getters +// + +uint16_t connection_get_idx(Connection *connection); +//! If valid, gets the updated_addr of the connection, or otherwise the initial_addr. +void connection_get_address(const Connection *connection, BTDeviceInternal *addr_buf); +//! Gets the local/own address that was used to advertise at the time of connection establishment. +void connection_get_local_address(Connection *connection, BTDeviceAddress *addr_buf); +void connection_get_address_by_idx_check(uint16_t conn_idx, BTDeviceInternal *addr_out); +void connection_get_conn_params(const Connection *connection, + BleConnectionParams *params_out); +bool connection_is_subscribed_to_gatt_mtu_notifications(const Connection *connection); +bool connection_is_gateway(Connection *connection); +bool connection_is_subscribed_to_connection_status_notifications(const Connection *connection); +bool connection_is_subscribed_to_conn_param_notifications(const Connection *connection); +bool connection_should_pin_address(const Connection *connection); +bool connection_should_auto_accept_re_pairing(const Connection *connection); +bool connection_is_reversed_ppogatt_enabled(const Connection *connection); +uint8_t connection_get_last_pairing_result(uint16_t conn_idx); +PPoGATTWorkAroundState *connection_get_ppogatt_wa_state(Connection *connection); + +// +// Setters - Sets the requested value provided connection_is_valid(connection) returns true +// + +void connection_set_gateway(Connection *connection, bool is_gateway); +void connection_set_subscribed_to_connection_status_notifications( + Connection *connection, bool is_subscribed); +void connection_set_subscribed_to_gatt_mtu_notifications( + Connection *connection, bool is_subscribed); +void connection_set_subscribed_to_conn_param_notifications( + Connection *connection, bool is_subscribed); +void connection_set_conn_params(Connection *connection, const BleConnectionParams *params); + +void connection_update_address(Connection *connection, const BTDeviceInternal *updated_addr); +void connection_set_should_pin_address(Connection *connection, bool should_pin_address); +void connection_set_should_auto_accept_re_pairing(Connection *connection, + bool should_auto_accept_re_pairing); +void connection_set_reversed_ppogatt_enabled(Connection *connection, + bool is_reversed_ppogatt_enabled); +void connection_set_last_pairing_result(uint16_t conn_idx, uint8_t result); +void connection_set_ppogatt_wa_state(Connection *connection, PPoGATTWorkAroundState *state); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/connection_private.h b/src/bluetooth-fw/da1468x/controller/main/include/connection_private.h new file mode 100644 index 00000000..366c3a99 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/connection_private.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#pragma once + +#include "connection.h" + +#include +#include +#include + +#include + +typedef struct GattOperation GattOperation; + +typedef enum { + ConnectionFlag_IsSubscribedToConnectionStatusNotifications = 0, + ConnectionFlag_IsSubscribedToGattMtuNotifications, + ConnectionFlag_IsSubscribedToConnParamNotifications, + ConnectionFlag_ShouldPinAddress, + //! @note The flag in the Connection struct is only relevant during the pairing process. + //! Once bonded, the value gets stored in the bonding list (storage.c / device_t). + ConnectionFlag_ShouldAutoAcceptRePairing, + //! @note The flag in the Connection struct is only relevant during the pairing process. + //! Once bonded, the value gets stored in the bonding list (storage.c / device_t). + ConnectionFlag_IsReversedPPoGATTEnabled, + ConnectionFlagCount, +} ConnectionFlag; + +typedef struct Connection { + ListNode node; + + uint16_t conn_idx; + + //! Remote address at the time the connection was established. + //! This can be the actual connection address OR the resolved address. + //! The former is the case for unbonded connections (including yet-to-be-bonded connections). + //! The latter is the case for bonded reconnections: if we are bonded (have an IRK) and the + //! underlying stack was able to resolve the address before passing the connection establishment + //! event to ble_task. + BTDeviceInternal initial_addr; + + //! Updated remote address. In case the address got resolved some time after connecting, + //! for example after pairing happened, the resolved address will be stored in this field. + //! The initial address will stay stored in initial_addr and the resolved address will be set in + //! this field. For bonded reconnections, this field will not be used (remain all zeroes). + bool has_updated_addr; + BTDeviceInternal updated_addr; + + //! Local address at the time the connection was created. + BTDeviceAddress local_addr; + + GattOperation *gatt_op_list; + + bool is_gateway; + + //! @see pebble_pairing_service.c + uint32_t flags; + BleConnectionParams conn_params; + uint8_t last_pairing_result; + + //! @see ppogatt_emulated_server_wa.c + PPoGATTWorkAroundState *ppogatt_wa_state; +} Connection; + +_Static_assert((sizeof(((Connection *)0)->flags) * 8) >= ConnectionFlagCount, + "Bitfield 'flags' full!"); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/debug_gpio.h b/src/bluetooth-fw/da1468x/controller/main/include/debug_gpio.h new file mode 100644 index 00000000..0720c286 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/debug_gpio.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include + +//! This is a debug utlility that makes it easy to toggle gpios to debug paths which are very +//! sensitive to timing. +//! To use it simply: +//! 1) Call debug_gpio_init() before you want to track things +//! 2) Call debug_gpio_toggle() to flip the gpio state + +//! Initializs DEBUG_GPIOs defined in board config and drives them all low +void debug_gpio_init(void); + +//! Toggles the debug_gpio from it's current state +void debug_gpio_toggle(int debug_gpio_num); + +//! Drives the state of the specified debug gpio +void debug_gpio_set_active(int debug_gpio, bool is_active); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/debug_reboot_reason.h b/src/bluetooth-fw/da1468x/controller/main/include/debug_reboot_reason.h new file mode 100644 index 00000000..77567d33 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/debug_reboot_reason.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#pragma once + +void debug_reboot_reason_print(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/debugger.h b/src/bluetooth-fw/da1468x/controller/main/include/debugger.h new file mode 100644 index 00000000..a135141f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/debugger.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +#if NO_WATCHDOG +void debugger_await(void); +#endif diff --git a/src/bluetooth-fw/da1468x/controller/main/include/dialog_analytics/analytics.h b/src/bluetooth-fw/da1468x/controller/main/include/dialog_analytics/analytics.h new file mode 100644 index 00000000..e166c30a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/dialog_analytics/analytics.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_endpoint_analytics.h" + +#include + +//! Called once during initialization. +void analytics_init(void); + +//! Called every time the Analytics module should reset its state and set all node values to 0. +//! Typically called after all analytics have been flushed out to the Main FW (every hour or so). +void analytics_reset_nodes(void); + +//! Set a scalar metric +//! @param metric The metric to set +//! @param val The new value +void analytics_set(DialogAnalyticsMetric metric, uint32_t val); + +//! Increment a metric by 1 +//! @param metric The metric to increment +void analytics_inc(DialogAnalyticsMetric metric); + +//! Increment a metric +//! @param metric The metric to increment +//! @param amount The amount to increment by +void analytics_add(DialogAnalyticsMetric metric, uint32_t amount); + +//! Starts a stopwatch that integrates a "rate of things" over time. +//! @param metric The metric of the stopwatch to start +void analytics_stopwatch_start(DialogAnalyticsMetric metric); + +//! Starts a stopwatch that integrates a "rate of things" over time. +//! @param metric The metric for which to start the stopwatch. +//! @param count_per_second The rate in number of things per second to count. +//! For example, if you want to measure "bytes transferred" over time and know the transfer speed +//! is 1024 bytes per second, then you would pass in 1024 as count_per_second. +void analytics_stopwatch_start_at_rate(DialogAnalyticsMetric metric, uint32_t count_per_second); + +//! Stops a stopwatch +//! @param metric The metric of the stopwatch +void analytics_stopwatch_stop(DialogAnalyticsMetric metric); + +// +// Consumer API +// + +typedef void (*AnalyticsEachCallback)(DialogAnalyticsMetric metric, uint32_t value, void *context); + +void analytics_each(AnalyticsEachCallback cb, void *context); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/die.h b/src/bluetooth-fw/da1468x/controller/main/include/die.h new file mode 100644 index 00000000..c345774e --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/die.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +NORETURN reset_due_to_software_failure(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/dis_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/dis_impl.h new file mode 100644 index 00000000..1aca3102 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/dis_impl.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +typedef struct DisInfo DisInfo; + +void device_information_service_init(const DisInfo *dis_info); +bool device_information_service_register(uint16_t start_hdl); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/gap_le_device_name_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/gap_le_device_name_impl.h new file mode 100644 index 00000000..971ae2c6 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/gap_le_device_name_impl.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +#include "connection.h" +#include "hc_protocol/hc_protocol.h" + +void gap_le_device_name_handle_set(const char *name); + +void gap_le_device_name_handle_request(const BTDeviceInternal *addr); +void gap_le_device_name_handle_request_all(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/gatt_client_discovery.h b/src/bluetooth-fw/da1468x/controller/main/include/gatt_client_discovery.h new file mode 100644 index 00000000..f7c2f05a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/gatt_client_discovery.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#pragma once + +#include + +typedef struct ble_evt_gattc_browse_svc_t ble_evt_gattc_browse_svc_t; +typedef struct ble_evt_gattc_browse_completed_t ble_evt_gattc_browse_completed_t; +typedef struct ble_evt_gattc_indication_t ble_evt_gattc_indication_t; + +void gatt_client_discovery_process_service(const ble_evt_gattc_browse_svc_t *service); +void gatt_client_discovery_handle_complete(const ble_evt_gattc_browse_completed_t *complete_event); + +bool gatt_client_discovery_filter_service_changed(const ble_evt_gattc_indication_t *evt); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/gatt_local_services.h b/src/bluetooth-fw/da1468x/controller/main/include/gatt_local_services.h new file mode 100644 index 00000000..3dc7b5d4 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/gatt_local_services.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#pragma once + +#define DEVICE_INFORMATION_SERVICE_EXPECTED_ATT_STARTING_HANDLE (12) +#define HRM_SERVICE_EXPECTED_ATT_STARTING_HANDLE (35) +#define HRM_SERVICE_EXPECTED_ATT_ENDING_HANDLE (42) +#define PEBBLE_PAIRING_SERVICE_EXPECTED_ATT_STARTING_HANDLE (23) +#define PEBBLE_PPOGATT_SERVICE_EXPECTED_ATT_STARTING_HANDLE (0xE000) + +typedef struct BTDriverConfig BTDriverConfig; + +void gatt_local_services_init(const BTDriverConfig *config); +void gatt_local_services_register(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/gatt_wrapper.h b/src/bluetooth-fw/da1468x/controller/main/include/gatt_wrapper.h new file mode 100644 index 00000000..8f83ef76 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/gatt_wrapper.h @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#pragma once + +#include "ble_gattc.h" + +#include + +#include + +typedef enum GattReqSource { + GattReqSourceHost, + GattReqSourceController, +} GattReqSource; + +// Gatt wrappers that call the underlying Dialog Gatt API's. +// Wrapped because it also keeps track of the context_ref in a `Connection` object +ble_error_t gatt_wrapper_read(uint16_t conn_idx, uint16_t handle, uintptr_t context_ref, + GattReqSource source); + +ble_error_t gatt_wrapper_read_by_uuid(uint16_t conn_idx, uint16_t start_h, uint16_t end_h, + const att_uuid_t *uuid, uintptr_t context_ref, + GattReqSource source); + +ble_error_t gatt_wrapper_write(uint16_t conn_idx, uint16_t handle, uint16_t length, + const uint8_t *value, uintptr_t context_ref, GattReqSource source); + +ble_error_t gatt_wrapper_write_no_resp(uint16_t conn_idx, uint16_t handle, uint16_t length, + const uint8_t *value); + +// Gatt wrapper callback -- for use when calling gatt_wrapper_read|write from the controller. +// Pass the callback fn pointer in context_ref. +typedef void (*gatt_wrapper_read_cb)(const ble_evt_gattc_read_completed_t *evt); +typedef void (*gatt_wrapper_write_cb)(const ble_evt_gattc_write_completed_t *evt); + +// +// Event Handlers (from ble_task) +// +void gatt_wrapper_handle_read_completed(const ble_evt_gattc_read_completed_t *evt); + +void gatt_wrapper_handle_write_completed(const ble_evt_gattc_write_completed_t *evt); + +void gatt_wrapper_handle_notification(const ble_evt_gattc_notification_t *evt); + +void gatt_wrapper_handle_indication(const ble_evt_gattc_indication_t *evt); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/gatt_wrapper_types.h b/src/bluetooth-fw/da1468x/controller/main/include/gatt_wrapper_types.h new file mode 100644 index 00000000..c615cc4e --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/gatt_wrapper_types.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include + +typedef enum GattOpType { + GattOpType_Read, + GattOpType_Write +} GattOpType; + +typedef enum GattRespDest { + GattRespDestNone, + GattRespDestHost, + GattRespDestController, +} GattRespDest; + +typedef struct GattOperation { + ListNode node; + uintptr_t object_ref; + GattRespDest resp_dest; + GattOpType op_type; +} GattOperation; diff --git a/src/bluetooth-fw/da1468x/controller/main/include/hc_endpoint_gap_service_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/hc_endpoint_gap_service_impl.h new file mode 100644 index 00000000..1ce68338 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/hc_endpoint_gap_service_impl.h @@ -0,0 +1,22 @@ +/* + * 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. + */ + +#pragma once + +#include "ble_gattc.h" + +void hc_endpoint_gap_service_gattc_read_complete(const ble_evt_gattc_read_completed_t *evt); +void hc_endpoint_gap_service_gattc_discover_char(const ble_evt_gattc_discover_char_t *evt); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/hci_rom_passthrough.h b/src/bluetooth-fw/da1468x/controller/main/include/hci_rom_passthrough.h new file mode 100644 index 00000000..ec869e5b --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/hci_rom_passthrough.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include "ad_ble.h" + +#include +#include + +//! Injects an HCI command into Dialog's ROM even if the chip is not configured +//! with BLE_STACK_PASSTHROUGH_MODE & BLE_EXTERNAL_HOST. This should be used +//! conservatively as it effectively bypasses the dialog SDK stack. It does however +//! allow one to add support for things not yet exported as an SDK API (i.e BLE +//! Direct Test Modes) +bool hci_rom_passthrough_send_cmd( + uint16_t ogf, uint16_t ocf, const uint8_t *param_buf, uint8_t param_length); + +//! Commands issued with hci_rom_passthrough_send_cmd() will bubble up +//! HCI event(s). This callback is invoked from the app task to handle these events +void hci_rom_passthrough_handle_evt(hci_evt_msg_t *msg); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/host_transport_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/host_transport_impl.h new file mode 100644 index 00000000..d3e3c0f6 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/host_transport_impl.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +void host_transport_init_periph(void); +void host_transport_init(void); + +typedef enum { + SCSPinFunction_Wakeup_GPIO, + SCSPinFunction_SPI_CS, +} SCSPinFunction; + +// Used by core dump. +void host_transport_configure_spi_scs_pin(SCSPinFunction function); +void host_transport_set_mcu_int(bool is_ready_to_transact); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/hrm_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/hrm_impl.h new file mode 100644 index 00000000..76159905 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/hrm_impl.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include + +void hrm_service_init(bool is_hrm_supported_and_enabled); + +void hrm_service_register(uint16_t start_hdl); + +void hrm_service_handle_measurement(const BleHrmServiceMeasurement *measurement, + const BTDeviceInternal *permitted_devices, + size_t num_permitted_devices); + +void hrm_service_handle_enable(bool enable); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/kernel/pbl_malloc.h b/src/bluetooth-fw/da1468x/controller/main/include/kernel/pbl_malloc.h new file mode 100644 index 00000000..ced76b9a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/kernel/pbl_malloc.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +// Defined with prefix of `kernel` to allow for code to be shared +// between normal firmware the bluetooth firmware. +void kernel_heap_init(void); +void *kernel_malloc(size_t bytes); +void *kernel_zalloc(size_t bytes); +void *kernel_malloc_check(size_t bytes); +void *kernel_zalloc_check(size_t bytes); +void kernel_free(void *ptr); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/kernel/util/freertos_utils.h b/src/bluetooth-fw/da1468x/controller/main/include/kernel/util/freertos_utils.h new file mode 100644 index 00000000..3e5fcd9a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/kernel/util/freertos_utils.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "portable.h" + +static inline TickType_t milliseconds_to_ticks(uint32_t milliseconds) { + return ((uint64_t)milliseconds * configTICK_RATE_HZ) / 1000; +} + +static inline uint32_t ticks_to_milliseconds(TickType_t ticks) { + return ((uint64_t)ticks * 1000) / configTICK_RATE_HZ; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/include/local_addr_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/local_addr_impl.h new file mode 100644 index 00000000..369b3911 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/local_addr_impl.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#include + +typedef struct BTDeviceAddress BTDeviceAddress; + +//! @see bt_driver_set_local_address +void local_addr_set(bool allow_cycling, const BTDeviceAddress *pinned_address); + +void local_addr_handle_update(const BTDeviceAddress *updated_address); + +void local_addr_handle_adverts_stopped(void); + +void local_addr_handle_disconnection(void); + +void local_addr_init(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/mcu.h b/src/bluetooth-fw/da1468x/controller/main/include/mcu.h new file mode 100644 index 00000000..c137dd4f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/mcu.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// Stub for the header. +// This will include the right MCU header and ARM core header: +#include "sdk_defs.h" + +#undef CMSIS_COMPATIBLE diff --git a/src/bluetooth-fw/da1468x/controller/main/include/pebble_pairing_service_impl.h b/src/bluetooth-fw/da1468x/controller/main/include/pebble_pairing_service_impl.h new file mode 100644 index 00000000..d43c0f78 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/pebble_pairing_service_impl.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include + +typedef struct Connection Connection; + +void pebble_pairing_service_init(void); + +//! Registers the Pebble Pairing Service with the ROM stack. +//! This needs to happen every time the ROM stack modifies the device configuration, see +//! notes in ble_gap.h. +void pebble_pairing_service_register(uint16_t start_hdl); + +void pebble_pairing_service_handle_status_change(Connection *connection, uint16_t conn_idx); + +void pebble_pairing_service_handle_gatt_mtu_change(Connection *connection, uint16_t conn_idx); + +void pebble_pairing_service_handle_conn_params_change(Connection *connection, uint16_t conn_idx); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/power.h b/src/bluetooth-fw/da1468x/controller/main/include/power.h new file mode 100644 index 00000000..d200efb1 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/power.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +void power_init(void); + +void power_enter_hibernation(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/ppogatt_emulated_server_wa.h b/src/bluetooth-fw/da1468x/controller/main/include/ppogatt_emulated_server_wa.h new file mode 100644 index 00000000..5731c159 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/ppogatt_emulated_server_wa.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +typedef struct Connection Connection; +typedef struct HcProtocolMessage HcProtocolMessage; +typedef struct PPoGATTWorkAroundState PPoGATTWorkAroundState; + +void ppogatt_service_init(void); +void ppogatt_service_register(uint16_t start_hdl); + +bool ppogatt_emulated_server_handle_msg(uint16_t conn_idx, Connection *conn, + const HcProtocolMessage *msg); +void ppogatt_inject_emulated_ppogatt_service_if_needed(uint16_t conn_idx); + +void ppogatt_enable_emulated_server_wa(void); +void ppogatt_emulated_notify_phone_ppogatt_server_found(Connection *conn); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/pra_generate.h b/src/bluetooth-fw/da1468x/controller/main/include/pra_generate.h new file mode 100644 index 00000000..2bb940de --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/pra_generate.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include + +#include + +typedef struct BTDeviceAddress BTDeviceAddress; + +//! Generates private resolvable address with a random prand. +void pra_generate(BTDeviceAddress *address_out); + +//! Generates private resolvable address with a given prand and IRK. +void pra_generate_with_prand_and_irk(BTDeviceAddress *address_out, + uint32_t prand, const SMIdentityResolvingKey *irk); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/reboot_reason.h b/src/bluetooth-fw/da1468x/controller/main/include/reboot_reason.h new file mode 100644 index 00000000..e16e8fec --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/reboot_reason.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +// NOTE: We include the reboot reason in analytics and the tools we use to analyze the analytics are +// dependent on the position and ordering of these enumerated values. To keep the analysis tools +// simpler, it is best to keep these enums in the same order and add new ones to the end. +typedef enum { + RebootReasonCode_Unknown = 0, + RebootReasonCode_Shutdown, + RebootReasonCode_Watchdog, + RebootReasonCode_Assert, + RebootReasonCode_StackOverflow, + RebootReasonCode_HardFault, + RebootReasonCode_BreakpointButNoDebuggerAttached, + RebootReasonCode_DialogPlatformReset, + RebootReasonCode_CoreDumpReentered, + RebootReasonCode_CoreDumpRequested, + RebootReasonCode_RomError, + RebootReasonCode_NMI, +} RebootReasonCode; + +typedef struct { + uint32_t cookie; + RebootReasonCode code; + union { + uint16_t data16; + uint8_t data8[2]; + }; + uint32_t extra; +} RebootReason; + +void reboot_reason_set(RebootReason *reason); + +bool reboot_reason_get(RebootReason *reason); + +void reboot_reason_clear(void); + +uint32_t reboot_reason_get_crash_lr(void); + +RebootReasonCode reboot_reason_get_last_reboot_reason(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/service_changed.h b/src/bluetooth-fw/da1468x/controller/main/include/service_changed.h new file mode 100644 index 00000000..cefa266a --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/service_changed.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#include + +void service_changed_send_indication_to_all(uint16_t start_handle, uint16_t end_handle); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/system/hexdump.h b/src/bluetooth-fw/da1468x/controller/main/include/system/hexdump.h new file mode 100644 index 00000000..c26eceb0 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/system/hexdump.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +void hexdump_log_src(int level, const uint8_t *data, size_t length); + +#define PBL_HEXDUMP(level, data, length) \ + hexdump_log_src(level, data, length); diff --git a/src/bluetooth-fw/da1468x/controller/main/include/system/logging.h b/src/bluetooth-fw/da1468x/controller/main/include/system/logging.h new file mode 100644 index 00000000..18bc2909 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/system/logging.h @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +// Log domains + +#define LOG_DOMAIN_MISC 1 + +#ifndef LOG_DOMAIN_GATT_DEBUG +#define LOG_DOMAIN_GATT_DEBUG 0 +#endif + +void pbl_log_init(void); +void pbl_log_hashed(const uint32_t packed_loghash, ...); +void pbl_log(uint8_t log_level, const char *src_filename, int src_line_number, + const char *fmt, ...) FORMAT_PRINTF(4, 5); + +void pbl_log_set_level(uint8_t level); +uint8_t pbl_log_get_level(void); + +// Log defines/stubs + +#define LOG_LEVEL_ALWAYS 0 +#define LOG_LEVEL_ERROR 1 +#define LOG_LEVEL_WARNING 50 +#define LOG_LEVEL_INFO 100 +#define LOG_LEVEL_DEBUG 200 +#define LOG_LEVEL_DEBUG_VERBOSE 255 + +#ifdef PBL_LOGS_HASHED +#define PBL_SHOULD_LOG(level) (true) +#else +#define PBL_SHOULD_LOG(level) (level <= LOG_LEVEL_ALWAYS) +#endif + +#include "logging/log_hashing.h" + +#define LOG_COLOR_BLACK "BLACK" // Not so useful in general +#define LOG_COLOR_RED "RED" +#define LOG_COLOR_GREEN "GREEN" +#define LOG_COLOR_YELLOW "YELLOW" +#define LOG_COLOR_BLUE "BLUE" +#define LOG_COLOR_MAGENTA "MAGENTA" +#define LOG_COLOR_CYAN "CYAN" +#define LOG_COLOR_GREY "GREY" +// Reserved for bold. Use sparingly +#define LOG_COLOR_LIGHT_GREY "LIGHT_GREY" +#define LOG_COLOR_LIGHT_RED "LIGHT_RED" +#define LOG_COLOR_LIGHT_GREEN "LIGHT_GREEN" +#define LOG_COLOR_LIGHT_YELLOW "LIGHT_YELLOW" +#define LOG_COLOR_LIGHT_BLUE "LIGHT_BLUE" +#define LOG_COLOR_LIGHT_MAGENTA "LIGHT_MAGENTA" +#define LOG_COLOR_LIGHT_CYAN "LIGHT_CYAN" +#define LOG_COLOR_WHITE "WHITE" + +// Allow the default color for a src file to be set by defining FILE_LOG_COLOR +// Can be called directly with PBL_LOG_COLOR +#ifdef FILE_LOG_COLOR + #define DEFAULT_LOG_COLOR FILE_LOG_COLOR +#else + #define DEFAULT_LOG_COLOR LOG_COLOR_GREY +#endif + + +#define SPLIT_64_BIT_ARG(x) (uint32_t)((x >> 32) & 0xFFFFFFFF), (uint32_t)(x & 0xFFFFFFFF) + +#ifndef DEFAULT_LOG_DOMAIN + #define DEFAULT_LOG_DOMAIN LOG_DOMAIN_MISC +#endif // DEFAULT_LOG_DOMAIN + +#ifdef PBL_LOGS_HASHED + +#define PBL_LOG_D(domain, level, fmt, ...) \ + do { \ + if (PBL_SHOULD_LOG(level)) { \ + if (domain) { \ + NEW_LOG_HASH(pbl_log_hashed, level, DEFAULT_LOG_COLOR, fmt, ## __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define PBL_LOG_COLOR_D(domain, level, color, fmt, ...) \ + do { \ + if (PBL_SHOULD_LOG(level)) { \ + if (domain) { \ + NEW_LOG_HASH(pbl_log_hashed, level, color, fmt, ## __VA_ARGS__); \ + } \ + } \ + } while (0) + +#else // PBL_LOGS_HASHED + +#define PBL_LOG_D(domain, level, fmt, ...) \ + do { \ + if (PBL_SHOULD_LOG(level)) { \ + if (domain) { \ + pbl_log(level, __FILE_NAME__, __LINE__, fmt, ## __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define PBL_LOG_COLOR_D(domain, level, color, fmt, ...) \ + do { \ + if (PBL_SHOULD_LOG(level)) { \ + if (domain) { \ + pbl_log(level, __FILE_NAME__, __LINE__, fmt, ## __VA_ARGS__); \ + } \ + } \ + } while (0) + +#endif // PBL_LOGS_HASHED + +#define PBL_LOG(level, fmt, ...) \ + PBL_LOG_D(DEFAULT_LOG_DOMAIN, level, fmt, ## __VA_ARGS__) + +#define PBL_LOG_COLOR(level, color, fmt, ...) \ + PBL_LOG_COLOR_D(DEFAULT_LOG_DOMAIN, level, color, fmt, ## __VA_ARGS__) + +#define GATT_LOG_DEBUG(fmt, args...) PBL_LOG_D(LOG_DOMAIN_GATT_DEBUG, LOG_LEVEL_DEBUG, fmt, ## args) + diff --git a/src/bluetooth-fw/da1468x/controller/main/include/system/passert.h b/src/bluetooth-fw/da1468x/controller/main/include/system/passert.h new file mode 100644 index 00000000..b9457fb2 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/system/passert.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +NORETURN passert_failed_no_message(void); + +NORETURN passert_failed_no_message_with_lr(uint32_t lr); + +NORETURN passert_rom_error_no_message_with_errortype(uint32_t error_type_stat); + +#define PBL_ASSERTN(expr) \ + do { \ + if (UNLIKELY(!(expr))) { \ + passert_failed_no_message(); \ + } \ + } while (0) + + +#define PBL_ASSERTN_LR(expr, lr) \ + do { \ + if (UNLIKELY(!(expr))) { \ + passert_failed_no_message_with_lr(lr); \ + } \ + } while (0) + +#define WTF PBL_ASSERTN(0) diff --git a/src/bluetooth-fw/da1468x/controller/main/include/tasks.h b/src/bluetooth-fw/da1468x/controller/main/include/tasks.h new file mode 100644 index 00000000..5e770e88 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/include/tasks.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#pragma once + +#include "osal.h" + +/* + * Centralized Task Management + */ + +typedef enum { + DialogTask_SysInit = 0, + DialogTask_Ble, + DialogTask_HostTrans, + DialogTask_Logging, + DialogTask_Last, + DialogTask_Error = DialogTask_Last, + DialogTask_ISR = 0xF, +} DialogTask; + +extern OS_TASK DialogTaskList[DialogTask_Last]; + +/* + * Task Register + * Directly write to DialogTaskList[] for easy compatibility with OS_TASK_CREATE. + */ + +/* + * Task Unregister + */ +void task_unregister_task(OS_TASK task); +void task_unregister_dialogtask(DialogTask task); + +/* Given FreeRTOS task handle, return DialogTask enum. DialogTask_Error if not found. */ +DialogTask task_to_dialogtask(OS_TASK task); + +/* For logging: get the current task ID (will respond with DialogTask_ISR if appropriate) */ +DialogTask task_get_dialogtask(void); + +/* Dumps to the console, the amount of untouched stack space for each registered task */ +void tasks_dump_free_space(void); diff --git a/src/bluetooth-fw/da1468x/controller/main/ldscripts/mem.ld.h b/src/bluetooth-fw/da1468x/controller/main/ldscripts/mem.ld.h new file mode 100644 index 00000000..a92816cd --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/ldscripts/mem.ld.h @@ -0,0 +1,297 @@ +/* + * 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. + */ + +/* Linker script to place sections and symbol values. Should be used together + * with other linker script that defines memory regions ROM, RetRAM0 and RAM. + * It references following symbols, which must be defined in code: + * Reset_Handler : Entry of reset handler + * + * It defines following symbols, which code can use without definition: + * __zero_table_start__ + * __zero_table_end__ + * __etext + * __data_start__ + * __data_end__ + * __bss_start__ + * __bss_end__ + * __end__ + * end + * __StackLimit + * __StackTop + */ + +#include "da1468x_mem_map.h" + +#define VECTOR_TABLE_MAX_SIZE (0xC0 + 0x80) +#define VECTOR_TABLE_ADDRESS (DATA_RAM_BASE_ADDRESS) +#define HEAP_START_ADDRESS (VECTOR_TABLE_ADDRESS + VECTOR_TABLE_MAX_SIZE) +#define CODE_AND_RAM_BASE_ADDRESS (HEAP_START_ADDRESS + configTOTAL_HEAP_SIZE) + +#define INFOBLOB_SIZE (VECTOR_TABLE_MAX_SIZE + 8 /* size of fields in .ObjectBinInfo below */ ) + +MEMORY +{ + VT (rx): + ORIGIN = VECTOR_TABLE_ADDRESS, + LENGTH = VECTOR_TABLE_MAX_SIZE + INFOBLOB (r): + ORIGIN = CODE_AND_RAM_BASE_ADDRESS - INFOBLOB_SIZE, + LENGTH = INFOBLOB_SIZE + CODE_AND_RAM (rwx): + ORIGIN = CODE_AND_RAM_BASE_ADDRESS, + LENGTH = DATA_RAM_BASE_ADDRESS + DATA_RAM_SIZE - CODE_AND_RAM_BASE_ADDRESS + CACHE_RAM (rwx): + ORIGIN = CACHE_RAM_BASE_ADDRESS + LENGTH = CACHE_RAM_SIZE + + /* Allocate log strings here for the console. Not loaded to memory. */ + LOG_STRINGS (r) : + ORIGIN = 0xC0000000, + LENGTH = 512K + + /* Allocate Firmware Metadata here. Not loaded to memory. See below. */ + FW_VERSION (r) : + ORIGIN = 0xD0000000, + LENGTH = 1K +} + +ENTRY(Reset_Handler) + +SECTIONS +{ + .vt : + { + KEEP(*(.isr_vector)) + /* make sure that IVT doesn't cross 0xC0 */ + . = 0xC0; + KEEP(*(.patch_table)) + /* make sure that IVT is the correct size */ + . = VECTOR_TABLE_MAX_SIZE; + } > VT + /* The heap starts immediately after the patch table and runs to the start of .zero.table */ + __heap_start = .; + __HEAP_START_ADDR_CHECK = HEAP_START_ADDRESS; + ASSERT((__heap_start == __HEAP_START_ADDR_CHECK), "Heap start address is incorrect!") + + // Note: There is a struct in dialog_spi_bootloader.c that matches this section + .ObjectBinInfo : + { + LONG(CODE_AND_RAM_BASE_ADDRESS) + LONG(VECTOR_TABLE_MAX_SIZE) + // Note: if you add/remove something, don't forget to change INFOBLOB_SIZE above + } > INFOBLOB + + // When we build the binary with objcopy, we pack the vector & patch table here + .vt_stash_region (COPY) : + { + . = . + VECTOR_TABLE_MAX_SIZE; + } > INFOBLOB + + /* GNU build id: This is a hash of parts of the binary that uniquely + * identifies the binary. This hash gets inserted by the linker; + * we're passing the flag --build-id=sha1 to do this. + * The variable DIALOG_BUILD_ID is provided, so that the values can be used + * in the firmware code. */ + .note.gnu.build-id : { + PROVIDE(DIALOG_BUILD_ID = .); + KEEP(*(.note.gnu.build-id)) + } > CODE_AND_RAM + + /* To clear multiple BSS sections, + * uncomment .zero.table section and, + * define __STARTUP_CLEAR_BSS_MULTIPLE in startup_ARMCMx.S */ + .zero.table : + { + . = ALIGN(4); + __zero_table_start__ = .; + LONG (HEAP_START_ADDRESS) + LONG (configTOTAL_HEAP_SIZE) + LONG (__zero_initialized_start__) + LONG (__zero_initialized_end__ - __zero_initialized_start__) + LONG (__ble_vars_start__) + LONG (__ble_vars_end__ - __ble_vars_start__) + LONG (__cache_ram_zi_start__) + LONG (__cache_ram_zi_end__ - __cache_ram_zi_start__) + LONG (__log_buffer_start__) + LONG (__log_buffer_end - __log_buffer_start__) + __zero_table_end__ = .; + } > CODE_AND_RAM + + __etext = .; + + /* The initialised data section is stored immediately + at the end of the text section */ + .data : AT (__etext) + { + __data_start__ = .; + *(vtable) + *(.data*) + + . = ALIGN(4); + /* init_array/fini_array moved to flash, align preserved */ + + KEEP(*(.jcr*)) + . = ALIGN(4); + *(retention_mem_rw) + *(privileged_data_rw) + *(.retention) + . = ALIGN(4); + + /* All data end */ + __data_end__ = .; + + } > CODE_AND_RAM + + .text : + { + __text_start = .; + /* + * Dialog assigns their jump table to a special section (I'm not + * sure why). Need to add it since the linker will otherwise try + * to place it in the first available region (the vector table region). + */ + KEEP(*(jump_table_mem_area)) + KEEP(*(.default_patch_code_handler_section)) + + *(.text*) + . = ALIGN(4); + *(text_retained) + + *(.rodata*) + + KEEP(*(.eh_frame*)) + . = ALIGN(4); + } > CODE_AND_RAM = 0x00 + __text_end = .; + + .text_crc32 : + { + LONG(0xDEADBEEF) + } > CODE_AND_RAM + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > CODE_AND_RAM = 0x00 + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > CODE_AND_RAM = 0x00 + __exidx_end = .; + + __StackTop = __StackLimit + __STACK_SIZE; + PROVIDE(__stack = __StackTop); + + __zero_initialized_start__ = .; + + /* .stack_dummy section doesn't contains any symbols. It is only a + * a placeholder for where the ISR stack lives. It's put right after the + * .text section, so that when it overflows, it's easy to figure out because + * .text is not supposed to be changed. + */ + .stack_dummy (NOLOAD): + { + . = ALIGN(4); + __StackLimit = .; + . += __STACK_SIZE; + } > CODE_AND_RAM + + .bss (NOLOAD): + { + . = ALIGN(4); + *(.bss*) + *(COMMON) + . = ALIGN(4); + *(retention_mem_zi) + . = ALIGN(4); + } > CODE_AND_RAM + + . = ALIGN(4); /* zero initing startup-code assumes word-alignment */ + __zero_initialized_end__ = .; + + __debug_region_start__ = .; + .debug_region (NOLOAD): + { + KEEP(*(reboot_reason)) + } > CODE_AND_RAM + __debug_region_end__ = .; + + + /* Put the debug log buffer in it's own section. This allows us to dynamically grow it + * to use all available RAM (well, up to RETENTION_BLE). + */ + .log_buffer (NOLOAD) : + { + __log_buffer_start__ = .; + KEEP(*(.log_buffer)) + __log_buffer_end = .; + } > CODE_AND_RAM + + + /* BLE ROM (RivieraWaves) variables -- see BLE_VAR_ADDR + This region extends all the way to the end of SysRAM! + */ + RETENTION_BLE BLE_VAR_ADDR (NOLOAD) : + { + __ble_vars_start__ = .; + KEEP(*(ble_variables)) + } > CODE_AND_RAM + + . = ALIGN(4); /* zero initing startup-code assumes word-alignment */ + __ble_vars_end__ = .; + + .cache_ram (NOLOAD): + { + __cache_ram_zi_start__ = .; + *(ble_env_heap) + *(ble_msg_heap) + *(ble_db_heap) + . = ALIGN(4); + *(privileged_data_zi) + + . = ALIGN(4); /* zero initing startup-code assumes word-alignment */ + __cache_ram_zi_end__ = .; + } > CACHE_RAM + + /* Unloaded section containing our log strings. */ + .log_strings (INFO) : { + KEEP(*(.log_string.header)) + KEEP(*(.log_strings)) + } >LOG_STRINGS + + /* Unloaded section containing our Firmware Metadata. + * If there is ever enough RAM to keep this in the RAM image (at the very end of .text) + * this section can be safely removed without causing errors in the coredump/feelsbadman tools + */ + .fw_version (INFO) : { + KEEP(*(.pbl_fw_version)) + } >FW_VERSION + + + + + /* Symbols for the core_dump memory table */ + __vector_table_length = LENGTH(VT); + __heap_length = __zero_table_start__ - __heap_start; + __text_length = __text_end - __text_start; + __rwdata_length = __data_end__ - __data_start__; + __stack_bss_length = __zero_initialized_end__ - __zero_initialized_start__; + __debug_region_length = __debug_region_end__ - __debug_region_start__; + __ble_vars_length = __ble_vars_end__ - __ble_vars_start__; + __cache_ram_length = __cache_ram_zi_end__ - __cache_ram_zi_start__; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/advert.c b/src/bluetooth-fw/da1468x/controller/main/src/advert.c new file mode 100644 index 00000000..f2fed806 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/advert.c @@ -0,0 +1,306 @@ +/* + * 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. + */ + +#include "advert.h" + +#include "system/logging.h" +#include "system/passert.h" + +#include "host_transport.h" + +// Dialog SDK: +#include "ble_gap.h" + +#include "FreeRTOS.h" +#include "light_mutex.h" +#include "semphr.h" + +#include +#include + +#include +#include + +extern void local_addr_handle_adverts_stopped(void); + +// Accesses to these statics must be protected by prv_lock() calls +static AdvertState s_adv_state = AdvertState_Off; + +static AdvertState s_desired_host_adv_state = AdvertState_Off; +static struct { + bool needs_updating; + BLEAdData ad_data; + uint8_t data_buffer[2 * GAP_LE_AD_REPORT_DATA_MAX_LENGTH]; +} s_desired_ad_data; + +static PebbleRecursiveMutex *s_adv_mutex; + +static void prv_lock(void) { + mutex_lock_recursive(s_adv_mutex); +} + +static void prv_unlock(void) { + mutex_unlock_recursive(s_adv_mutex); +} + +static gap_disc_mode_t prv_dialog_discoverable_type_for_flag(uint8_t flags) { + if (flags & GAP_LE_AD_FLAGS_GEN_DISCOVERABLE_MASK) { + return GAP_DISC_MODE_GEN_DISCOVERABLE; + } else if (flags & GAP_LE_AD_FLAGS_LIM_DISCOVERABLE_MASK) { + return GAP_DISC_MODE_LIM_DISCOVERABLE; + } else { + return GAP_DISC_MODE_NON_DISCOVERABLE; + } +} + +ble_error_t advert_set_interval(uint16_t min_slots, uint16_t max_slots) { + // No need to lock / take here, this API just sets a couple vars in ble_mgr_dev_params, + // while holding the ble_mgr_dev_params lock. + ble_error_t rv = ble_gap_adv_intv_set(min_slots, max_slots); + if (rv != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Error: ble_gap_adv_intv_set - rv: %d", rv); + } + return rv; +} + +static ble_error_t prv_set_mode_and_data(void) { + const BLEAdData *ad_data = &s_desired_ad_data.ad_data; + // FIXME PBL-34428: We are chopping off the beginning of the advertisement data because that is + // where the flags are stored. We parse the flags and translate to a specific type of discovery + // mode required by the Dialog part. Do some error checking while we are at it. + const uint8_t *adv_bytes = ad_data->data; + size_t adv_bytes_len = ad_data->ad_data_length; + gap_disc_mode_t disc_mode = GAP_DISC_MODE_NON_DISCOVERABLE; + + // Make sure the whole packet data is at least 3 bytes. + // Make sure that the length of the first field is at least 2 (contains the type and flags byte) + // Make sure that the type is GAP_DATA_TYPE_FLAGS + if ((ad_data->ad_data_length >= 3) + && (*adv_bytes >= 2) + && *(adv_bytes + 1) == GAP_DATA_TYPE_FLAGS) { + // Start the iter at the flags byte + const uint8_t *iter = adv_bytes + 2; + disc_mode = prv_dialog_discoverable_type_for_flag(*iter); + + // Move iter past the flags + iter += 1; + + adv_bytes = iter; + adv_bytes_len -= 3; + } + + ble_error_t rv = ble_gap_adv_mode_set(disc_mode); + if (rv != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Error: ble_gap_adv_mode_set - rv: %d", rv); + // Not returning, probably better to try to advertise regardless of this error. + } + + rv = ble_gap_adv_data_set(adv_bytes_len, adv_bytes, + ad_data->scan_resp_data_length, + &ad_data->data[ad_data->ad_data_length]); + if (rv != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Error: ble_gap_adv_data_set - rv: %d", rv); + } + + s_desired_ad_data.needs_updating = false; + return rv; +} + +static ble_error_t prv_advert_enable(void) { + ble_error_t rv = BLE_ERROR_FAILED; + if (s_desired_ad_data.needs_updating) { + rv = prv_set_mode_and_data(); + if (rv != BLE_STATUS_OK) { + return rv; + } + } + + rv = ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED); + if (rv != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_adv_start: status = 0x%x", rv); + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "Adverts (re)started"); + } + return rv; +} + +static ble_error_t prv_advert_disable(void) { + ble_error_t rv = ble_gap_adv_stop(); + if (rv != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_adv_stop: status = 0x%x", rv); + } + return rv; +} + +static ble_error_t prv_try_to_enter_host_desired_state(void) { + ble_error_t rv = BLE_STATUS_OK; + prv_lock(); + { + // The only valid desired host states are Off & Running + switch (s_desired_host_adv_state) { + case AdvertState_Running: + case AdvertState_Off: + break; + default: + WTF; + } + + // Are we already in the state we desire + if (s_desired_host_adv_state == s_adv_state) { + rv = BLE_ERROR_ALREADY_DONE; + goto unlock; + } + + + switch (s_adv_state) { + case AdvertState_Pausing: + case AdvertState_Paused: + case AdvertState_Stopping: + // We are transitioning, these are handled by other routines + goto unlock; + default: + break; + } + + if (s_desired_host_adv_state == AdvertState_Running) { + // TODO: Does the dialog stack gracefully handle calling stop before adds + // are truly running. Is there an event we can wait on and have a Starting state? + s_adv_state = AdvertState_Running; + rv = prv_advert_enable(); + + } else if (s_desired_host_adv_state == AdvertState_Off) { + s_adv_state = AdvertState_Stopping; + rv = prv_advert_disable(); + } + } +unlock: + prv_unlock(); + return rv; +} + +AdvertState advert_enable(void) { + AdvertState rv; + prv_lock(); + { + s_desired_host_adv_state = AdvertState_Running; + prv_try_to_enter_host_desired_state(); + rv = s_adv_state; + } + prv_unlock(); + return rv; +} + +void advert_disable(void) { + prv_lock(); + { + s_desired_host_adv_state = AdvertState_Off; + prv_try_to_enter_host_desired_state(); + } + prv_unlock(); +} + +void advert_set_data(const BLEAdData *ad_data) { + prv_lock(); + { + memcpy(&s_desired_ad_data.ad_data, ad_data, + sizeof(BLEAdData) + ad_data->ad_data_length + ad_data->scan_resp_data_length); + s_desired_ad_data.needs_updating = true; + } + prv_unlock(); +} + +static void prv_resume_if_needed(void) { + if (s_desired_host_adv_state == AdvertState_Running) { + PBL_LOG(LOG_LEVEL_DEBUG, "Unpausing advertisements"); + } + s_adv_state = AdvertState_Off; + prv_try_to_enter_host_desired_state(); +} + +void advert_handle_completed(const ble_evt_gap_adv_completed_t *evt) { + prv_lock(); + { + PBL_LOG(LOG_LEVEL_DEBUG, "advert_handle_completed %"PRIu8, evt->status); + if (s_adv_state == AdvertState_Pausing) { + s_adv_state = AdvertState_Paused; + } else { + s_adv_state = AdvertState_Off; + } + + // Note: Ideally, the controller would inform us why the advertisement had stopped. For + // example, if we knew that the reason was due to a slave device connecting we would not + // restart advertisements. However, I don't see a robust way to conclude this based on the + // events the ROM stack emits. + prv_try_to_enter_host_desired_state(); + } + prv_unlock(); + + local_addr_handle_adverts_stopped(); + + prv_lock(); + { + // Make sure we never stay paused once local_addr has a chance to do whatever it needs to do + // while advertisements were paused. + if (s_adv_state == AdvertState_Paused) { + prv_resume_if_needed(); + } + } + prv_unlock(); +} + +//! @note This is here exclusively for local_addr.c! +//! If you also need to use advert_pause/resume, it's time to add a refcount. +bool advert_execute_cb_if_adverts_are_paused(void (*cb)(void *), void *ctx) { + bool cb_executed = false; + prv_lock(); + { + switch (s_adv_state) { + // States where we can immediately transition to paused + case AdvertState_Off: + case AdvertState_Paused: + s_adv_state = AdvertState_Paused; + break; + default: + break; + } + + if (s_adv_state != AdvertState_Paused) { + PBL_LOG(LOG_LEVEL_DEBUG, "Pausing advertisements, state = 0x%x", + s_adv_state); + if (s_adv_state == AdvertState_Running) { + prv_advert_disable(); + } + + s_adv_state = AdvertState_Pausing; + } else if (s_adv_state == AdvertState_Paused) { + // It's safe to call the cb + cb(ctx); + cb_executed = true; + prv_resume_if_needed(); + } + } + prv_unlock(); + return cb_executed; +} + +void advert_init(void) { + s_adv_state = AdvertState_Off; + s_desired_host_adv_state = AdvertState_Off; + s_desired_ad_data.needs_updating = false; + s_desired_ad_data.ad_data.ad_data_length = 0; + s_desired_ad_data.ad_data.scan_resp_data_length = 0; + s_adv_mutex = mutex_create_recursive(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/aes.c b/src/bluetooth-fw/da1468x/controller/main/src/aes.c new file mode 100644 index 00000000..77a8546b --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/aes.c @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#include "aes.h" + +#include "system/hexdump.h" +#include "system/logging.h" +#include "system/passert.h" + +// Dialog SDK: +#include "hw_aes_hash.h" +#include "sdk_defs.h" + +#include + +bool aes_128_encrypt_block(const uint8_t key[AES_128_KEY_SIZE], + const uint8_t plain_text_block[AES_128_BLOCK_SIZE], + uint8_t cipher_text_block_out[AES_128_BLOCK_SIZE]) { + + // Nothing else should currently be using the crypto block, just in case this changes: + PBL_ASSERTN(!REG_GETF(CRG_TOP, CLK_AMBA_REG, AES_CLK_ENABLE)); + + hw_aes_hash_setup aes_setup = { + .mode = HW_AES_ECB, // we're just encrypting one block, so just use "code book" + .aesDirection = HW_AES_ENCRYPT, + .aesKeySize = HW_AES_128, + .aesKeyExpand = true, + .aesKeys = (uintptr_t)(void *)key, + .aesWriteBackAll = false, + .moreDataToCome = false, + .sourceAddress = (uintptr_t)(void *)plain_text_block, + .destinationAddress = (uintptr_t)(void *)cipher_text_block_out, + .dataSize = AES_128_BLOCK_SIZE, + .enableInterrupt = false, + }; + hw_aes_hash_init(&aes_setup); + hw_aes_hash_start(); + hw_aes_hash_disable(true /* wait_until_inactive */); + + return true; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/ble_task.c b/src/bluetooth-fw/da1468x/controller/main/src/ble_task.c new file mode 100644 index 00000000..b93972e7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/ble_task.c @@ -0,0 +1,693 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "advert.h" +#include "dialog_analytics/analytics.h" +#include "bonding_flags.h" +#include "bonding_sync_impl.h" +#include "connection.h" +#include "debug_reboot_reason.h" +#include "dialog_utils.h" +#include "gatt_local_services.h" +#include "hci_rom_passthrough.h" +#include "host_transport.h" +#include "host_transport_impl.h" +#include "local_addr_impl.h" +#include "pebble_pairing_service_impl.h" +#include "system/hexdump.h" +#include "system/logging.h" +#include "system/passert.h" +#include "tasks.h" +#include "hc_protocol/hc_endpoint_analytics.h" +#include "hc_protocol/hc_endpoint_responsiveness.h" +#include "hc_protocol/hc_endpoint_gap_le_connect.h" +#include "hc_protocol/hc_endpoint_gap_service.h" +#include "hc_protocol/hc_endpoint_gatt.h" +#include "hc_protocol/hc_endpoint_pairing.h" +#include "gatt_client_discovery.h" +#include "gatt_wrapper.h" + +// Dialog SDK: +#include "ad_ble.h" +#include "ble_att.h" +#include "ble_common.h" +#include "ble_gap.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_mgr.h" +#include "ble_mgr_irb_common.h" +#include "ble_service.h" +#include "ble_uuid.h" +#include "osal.h" +#include "smp_common.h" +#include "storage.h" +#include "sys_watchdog.h" +#include "timers.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define mainBLE_PERIPHERAL_TASK_PRIORITY (tskIDLE_PRIORITY + 1) + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Defines to configure the different test scenarios (enable only one) + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#define ADV_MAX_INTVL_SLOTS (400) + +extern void test_hci_passthrough(void); +#define TEST_HCI_ROM_PASSTHROUGH (0) + +typedef struct { + SemaphoreHandle_t semph; + const BTDriverConfig *config; +} BleTaskInitInfo; + +/* + * Main code + */ + +typedef struct { + uint16_t conn_idx; //! The connection that has the demo server + + uint16_t write_hdl; + uint16_t write_cccd_hdl; + + uint16_t notify_hdl; + uint16_t notify_cccd_hdl; + + uint32_t expected_read_header; //! the payload index we are expecting to receiver from the server + uint32_t current_write_header; //! the current header we are writing to App +} demo_server_hdl_t; + +static int8_t s_ble_perip_wdog_id; + +static void prv_log_conn_params(const gap_conn_params_t *conn_params) { + PBL_LOG(LOG_LEVEL_DEBUG, "Current conn params: intvl min: %d max: %d latency: %d STO: %d", + (int)conn_params->interval_min, (int)conn_params->interval_max, + (int)conn_params->slave_latency, (int)conn_params->sup_timeout); +} + +static bool prv_get_device_and_irk_by_conn_idx(uint16_t conn_idx, + bool *is_resolved_out, + SMIdentityResolvingKey *irk_out) { + bool success = true; + storage_acquire(); + { + device_t *dev = find_device_by_conn_idx(conn_idx); + if (!dev) { + success = false; + goto release; + } + bool is_resolved = false; + if (dev && dev->irk) { + is_resolved = true; + if (irk_out) { + memcpy(irk_out, dev->irk->key, sizeof(*irk_out)); + } + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "Address not resolved"); + } + if (is_resolved_out) { + *is_resolved_out = is_resolved; + } + } +release: + storage_release(); + return success; +} + +static void handle_evt_gap_connected(ble_evt_gap_connected_t *evt) { + PBL_LOG(LOG_LEVEL_DEBUG, "Connected, idx=%d!", (int)evt->conn_idx); + prv_log_conn_params(&evt->conn_params); + + if (!evt->is_master) { + // To be compliant with BT Core Spec section 7.8.9, stop advertising when the chip gets + // connected to. (The host expectes advertisements to have stopped in this scenario (see + // gap_le_advert_handle_connect_as_slave)) Ideally we would handle this in our advert.c wrapper + // but we don't have enough state available to conclude this in advert_handle_completed() + advert_disable(); + } + + BTDeviceInternal addr; + dialog_utils_bd_address_to_bt_device(&evt->peer_address, &addr); + + HcGapLeConnectionData event = { + .connection_complete_event = { + .conn_params = { + .conn_interval_1_25ms = evt->conn_params.interval_min, + .slave_latency_events = evt->conn_params.slave_latency, + .supervision_timeout_10ms = evt->conn_params.sup_timeout, + }, + .peer_address = addr, + .status = HciStatusCode_Success, + .is_master = evt->is_master, + .handle = evt->conn_idx, + }, + }; + + // Store the current local address with the connection. It's possible the local address will + // cycle while being connected. If we're going to pair and the device wants to use address + // pinning, we need to make sure to pin the current address and not a newly generated one. + const BTDeviceAddress *own_addr = (const BTDeviceAddress *)evt->own_addr.addr; + const BleConnectionParams *params = &event.connection_complete_event.conn_params; + connection_create(evt->conn_idx, &addr, own_addr, params); + + ble_error_t rv = ble_gattc_get_mtu(evt->conn_idx, &event.mtu); + if (rv != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_WARNING, "ble_gattc_get_mtu failed with status: %d", rv); + } + + // If the address got resolved, copy the IRK into the event: + BleConnectionCompleteEvent *conn_evt = &event.connection_complete_event; + prv_get_device_and_irk_by_conn_idx(evt->conn_idx, &conn_evt->is_resolved, &conn_evt->irk); + + hc_endpoint_gap_le_connect_send_connection_complete(&event); +} + +static void prv_handle_conn_param_update_request_response( + const ble_evt_gap_conn_param_update_completed_t *event) { + // This callback getting invoked means that conn param request was acknowledged. Could be either + // successful or not. If a failure, we return the failure up to the host + if (event->status == BLE_STATUS_OK) { + // We'll be getting an conn_param_update very soon, no need to alert the host. + return; + } + + PBL_LOG(LOG_LEVEL_WARNING, "Connection parameter update request failed with status: %d", + event->status); + + Connection *conn = connection_by_idx(event->conn_idx); + if (conn == NULL) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection for idx=%d", event->conn_idx); + return; + } + + BTDeviceInternal addr; + connection_get_address(conn, &addr); + + hc_endpoint_responsiveness_notify_update(NULL, &addr, HciStatusCode_VS_Base + event->status); +} + +static void prv_handle_conn_param_update_request(const ble_evt_gap_conn_param_update_req_t *evt) { + gap_conn_params_t conn_params = evt->conn_params; + PBL_LOG(LOG_LEVEL_DEBUG, "Master requesting conn param change, Conn Idx: %d - (%d %d %d %d)", + evt->conn_idx, (int)conn_params.interval_min, (int)conn_params.interval_max, + (int)conn_params.slave_latency, (int)conn_params.sup_timeout); + + // accept the change + ble_gap_conn_param_update_reply(evt->conn_idx, true); +} + +static void prv_handle_conn_param_update(const ble_evt_gap_conn_param_updated_t *event) { + // This callback getting invoked means that conn param request was successful + // Note: If this cb is not invoked within 30s after issuing an update request + // we will be auto-disconnected (according to the ble_gap_conn_param_update + // API) + prv_log_conn_params(&event->conn_params); + + const uint16_t conn_idx = event->conn_idx; + Connection *conn = connection_by_idx(conn_idx); + if (conn == NULL) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection for idx=%d", conn_idx); + return; + } + + BTDeviceInternal addr; + connection_get_address(conn, &addr); + + BleConnectionParams params = { + .conn_interval_1_25ms = event->conn_params.interval_min, + .slave_latency_events = event->conn_params.slave_latency, + .supervision_timeout_10ms = event->conn_params.sup_timeout, + }; + connection_set_conn_params(conn, ¶ms); + pebble_pairing_service_handle_conn_params_change(conn, conn_idx); + hc_endpoint_responsiveness_notify_update(¶ms, &addr, HciStatusCode_Success); +} + +static void prv_handle_evt_gap_disconnected(const ble_evt_gap_disconnected_t *evt) { + PBL_LOG(LOG_LEVEL_INFO, "Disconnected: idx=%"PRIu16", reason=0x%"PRIx8, + evt->conn_idx, evt->reason); + local_addr_handle_disconnection(); + + Connection *conn = connection_by_idx_check(evt->conn_idx); + + BTDeviceInternal addr; + connection_get_address(conn, &addr); + + BleDisconnectionCompleteEvent event = { + .peer_address = addr, + .status = HciStatusCode_Success, + .reason = evt->reason, + .handle = evt->conn_idx, + }; + + hc_endpoint_gap_le_connect_send_disconnection_complete(&event); + + connection_destroy(connection_by_idx(evt->conn_idx)); +} + +static void prv_handle_evt_gap_sec_level_changed(const ble_evt_gap_sec_level_changed_t *evt) { + PBL_LOG(LOG_LEVEL_INFO, "Security level changed to: %u", evt->level); + Connection *conn = connection_by_idx(evt->conn_idx); + if (conn == NULL) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection for idx=%d", evt->conn_idx); + return; + } + + pebble_pairing_service_handle_status_change(conn, evt->conn_idx); + + BTDeviceInternal addr; + connection_get_address(conn, &addr); + + BleEncryptionChange event = { + .dev_address = addr.address, + .status = HciStatusCode_Success, + .encryption_enabled = (evt->level > GAP_SEC_LEVEL_1), + }; + + hc_endpoint_gap_le_connect_send_encryption_changed(&event); +} + +//! @return True iff there is an existing pairing for this connection with the +//! "BleBondingFlag_ShouldAutoAcceptRePairing" bit set AND the bit has been set again through the +//! Trigger Pairing characteristic. The second part of the condition may not be true in case a user +//! updated the phone to an Android version that does not have the bug (PBL-39369). +static bool prv_is_auto_accept_repairing_mode(Connection *conn, uint16_t conn_idx) { + bool should_auto_accept_re_pairing; + storage_acquire(); + { + const device_t *dev = find_device_by_conn_idx(conn_idx); + should_auto_accept_re_pairing = (dev && + (dev->flags & BleBondingFlag_ShouldAutoAcceptRePairing)); + } + storage_release(); + should_auto_accept_re_pairing &= connection_should_auto_accept_re_pairing(conn); + + return should_auto_accept_re_pairing; +} + +static void prv_handle_pairing_request(const ble_evt_gap_pair_req_t *evt) { + PBL_LOG(LOG_LEVEL_DEBUG, "Received pairing request."); + + const uint16_t conn_idx = evt->conn_idx; + Connection *conn = connection_by_idx(conn_idx); + if (conn == NULL) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection for idx=%d", conn_idx); + return; + } + + BTDeviceInternal device = {}; + connection_get_address(conn, &device); + + if (prv_is_auto_accept_repairing_mode(conn, conn_idx)) { + PBL_LOG(LOG_LEVEL_DEBUG, "Auto-accepting pairing request"); + extern void pair_reply(uint16_t conn_idx, bool is_confirmed); + pair_reply(conn_idx, true); + return; + } + + hc_endpoint_pairing_send_pairing_request(&device); +} + +static void prv_handle_address_resolved(const ble_evt_gap_address_resolved_t *evt) { + PBL_LOG(LOG_LEVEL_DEBUG, "IRK exchanged and address resolved/updated."); + // Update the IRK + Address (Dialog SDK swaps the connection address for the identity address, + // even if the pairing process failed mid-way): + BleAddressAndIRKChange e = {}; + if (!prv_get_device_and_irk_by_conn_idx(evt->conn_idx, &e.is_resolved, &e.irk)) { + // Disconnected in the mean time + return; + } + + e.is_address_updated = true; + dialog_utils_bd_address_to_bt_device(&evt->address, &e.device); + dialog_utils_bd_address_to_bt_device(&evt->resolved_address, &e.new_device); + + // Also update the new address in the local connection list: + Connection *connection = connection_by_idx_check(evt->conn_idx); + connection_update_address(connection, &e.new_device); + + hc_endpoint_gap_le_connect_send_address_and_irk_changed(&e); +} + +static void prv_handle_pairing_completed(const ble_evt_gap_pair_completed_t *evt) { + PBL_LOG(LOG_LEVEL_INFO, "Pairing completed. Bond=%u, MITM=%u, status=0x%"PRIx8, + evt->bond, evt->mitm, evt->status); + const uint16_t conn_idx = evt->conn_idx; + Connection *conn = connection_by_idx(conn_idx); + if (conn == NULL) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection for idx=%d", conn_idx); + return; + } + + if (prv_is_auto_accept_repairing_mode(conn, conn_idx)) { + // Don't sync the new pairing, we're dealing with an Android device that has to re-pair + // upon every reconnection. Don't sync to avoid re-writing the pairing info upon every + // reconnection. @see https://pebbletechnology.atlassian.net/browse/PBL-39369 + PBL_LOG(LOG_LEVEL_DEBUG, "Skipping syncing new pairing info..."); + } else { + const bool success = (evt->status == BLE_STATUS_OK); + if (success && evt->bond) { + // Sync the new bonding: + bonding_sync_handle_pairing_completed(conn, conn_idx); + } + BTDeviceInternal device = {}; + connection_get_address(conn, &device); + hc_endpoint_pairing_send_pairing_complete(&device, evt->status); + } + + // Convert to BT Spec error codes and update Pebble Pairing Service's Conn Status characteristic: + const uint8_t smp_status = SMP_GET_PAIR_FAIL_REASON(evt->status); + connection_set_last_pairing_result(conn_idx, smp_status); + pebble_pairing_service_handle_status_change(conn, conn_idx); +} + +static void prv_handle_gatt_mtu_changed(const ble_evt_gattc_mtu_changed_t *event) { + Connection *connection = connection_by_idx_check(event->conn_idx); + if (!connection) { + PBL_LOG(LOG_LEVEL_WARNING, "No connection for idx %d", event->conn_idx); + return; + } + + PBL_LOG(LOG_LEVEL_INFO, "MTU updated: %"PRIu16, event->mtu); + pebble_pairing_service_handle_gatt_mtu_change(connection, event->conn_idx); + hc_endpoint_gap_service_mtu_changed(connection, event->mtu); +} + +static void prv_configure_local_device_properties(const BTDriverConfig *config) { + ble_error_t e; + + e = ble_gap_appearance_set(BLE_GAP_APPEARANCE_GENERIC_WATCH, ATT_PERM_READ); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_appearance_set: %u", e); + } + +#if 0 // FIXME: PBL-36556 -- How to configure the identity address? + const BTDeviceAddress *addr = &config->identity_addr; + PBL_LOG(LOG_LEVEL_DEBUG, "Setting Local Device Addr to:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)addr, sizeof(*addr)); + own_address_t own_addr = { + .addr_type = PRIVATE_STATIC_ADDRESS + }; + memcpy(&own_addr.addr, addr, sizeof(own_addr.addr)); + + e = ble_gap_address_set(&own_addr, 0); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_address_set: %u", e); + } +#endif + + e = ble_gap_mtu_size_set(ATT_MAX_SUPPORTED_MTU); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_mtu_size_set: %u", e); + } + + e = ble_gap_role_set(GAP_PERIPHERAL_ROLE); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_role_set: %u", e); + } + + // FIXME: PBL-23399: Single-source this. + const gap_conn_params_t preferred_params = { + .interval_min = 6, // 7.5ms + .interval_max = 24, // 30ms + .slave_latency = 4, + .sup_timeout = 600, // 6000ms + }; + e = ble_gap_per_pref_conn_params_set(&preferred_params); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_per_pref_conn_params_set: %u", e); + } +} + +void ble_task_assert_is_executing_on_ble_task(void) { + PBL_ASSERTN(xTaskGetCurrentTaskHandle() == DialogTaskList[DialogTask_Ble]); +} + +static void prv_configure_irk(const BTDriverConfig *config) { + // Use the Identity Root directly as the local Identity Resolving Key: + PBL_LOG(LOG_LEVEL_DEBUG, "Setting local IRK:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (const uint8_t *)&config->root_keys[SMRootKeyTypeIdentity], + sizeof(config->root_keys[SMRootKeyTypeIdentity])); + + ble_dev_params_t *dev_params = ble_mgr_dev_params_acquire(); + memcpy(&dev_params->irk, &config->root_keys[SMRootKeyTypeIdentity], sizeof(dev_params->irk)); + ble_mgr_dev_params_release(); +} + +static void prv_handle_get_peer_version_complete(const ble_evt_gap_get_peer_version_t *evt) { + Connection *conn = connection_by_idx_check(evt->conn_idx); + + BTDeviceInternal addr; + connection_get_address(conn, &addr); + + BleRemoteVersionInfoReceivedEvent event = { + .peer_address = addr, + .remote_version_info = { + .version_number = evt->version, + .company_identifier = evt->company_identifier, + .subversion_number = evt->subversion, + } + }; + + hc_endpoint_gap_le_connect_send_peer_version_info(&event); +} + +static void prv_ble_peripheral_task(void *params) { + const BleTaskInitInfo *init_info = (const BleTaskInitInfo *)params; + + PBL_LOG(LOG_LEVEL_DEBUG, "At least I started"); + + analytics_init(); + hc_endpoint_analytics_send_reboot_info(); + debug_reboot_reason_print(); + + s_ble_perip_wdog_id = sys_watchdog_register(false); + + // FIXME: + // srand(time(NULL)); + + PBL_LOG(LOG_LEVEL_DEBUG, "hope for the best"); + connection_module_init(); + + prv_configure_irk(init_info->config); + + ble_error_t e = ble_enable(); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_enable: %u", e); + } + + PBL_LOG(LOG_LEVEL_DEBUG, "go go go"); + ble_register_app(); + + advert_init(); + local_addr_init(); + + // Do all the things that clear the ATT table before registering the local services: + prv_configure_local_device_properties(init_info->config); + + gatt_local_services_init(init_info->config); + gatt_local_services_register(); + + PBL_LOG(LOG_LEVEL_DEBUG, "start the loop"); + +#if TEST_HCI_ROM_PASSTHROUGH + test_hci_passthrough(); +#endif + + // Signal to the initing task that the BLE stack is up and running now! + xSemaphoreGive(init_info->semph); + + for (;;) { + BaseType_t ret; + uint32_t notif; + + sys_watchdog_notify(s_ble_perip_wdog_id); + sys_watchdog_suspend(s_ble_perip_wdog_id); + + ret = xTaskNotifyWait(0, (uint32_t) -1, ¬if, portMAX_DELAY); + configASSERT(ret == pdTRUE); + + sys_watchdog_resume(s_ble_perip_wdog_id); + + hc_protocol_process_receive_buffer(); + + extern bool should_log_about_mic_error(uint32_t *max_subsequent_failures); + uint32_t num_subsequent_failures = 0; + if (should_log_about_mic_error(&num_subsequent_failures)) { + hc_endpoint_analytics_log_mic_error_detected(num_subsequent_failures); + } + + /* notified from BLE manager, can get event */ + if (notif & BLE_APP_NOTIFY_MASK) { + ble_evt_hdr_t *hdr; + + hdr = ble_get_event(false); + if (!hdr) { + goto no_event; + } + + if (ble_service_handle_event(hdr)) { + goto handled; + } + + switch (hdr->evt_code) { + // GAP Events: + case BLE_EVT_GAP_CONNECTED: + handle_evt_gap_connected((ble_evt_gap_connected_t *) hdr); + break; + case BLE_EVT_GAP_DISCONNECTED: + prv_handle_evt_gap_disconnected((ble_evt_gap_disconnected_t *) hdr); + break; + case BLE_EVT_GAP_ADV_COMPLETED: + advert_handle_completed((ble_evt_gap_adv_completed_t *) hdr); + break; + case BLE_EVT_GAP_CONN_PARAM_UPDATED: + prv_handle_conn_param_update((const ble_evt_gap_conn_param_updated_t *)hdr); + break; + case BLE_EVT_GAP_CONN_PARAM_UPDATE_COMPLETED: + prv_handle_conn_param_update_request_response( + (const ble_evt_gap_conn_param_update_completed_t *)hdr); + break; + case BLE_EVT_GAP_CONN_PARAM_UPDATE_REQ: + prv_handle_conn_param_update_request((const ble_evt_gap_conn_param_update_req_t *)hdr); + break; + case BLE_EVT_GAP_SEC_LEVEL_CHANGED: + prv_handle_evt_gap_sec_level_changed((ble_evt_gap_sec_level_changed_t *)hdr); + break; + case BLE_EVT_GAP_PAIR_REQ: + prv_handle_pairing_request((const ble_evt_gap_pair_req_t *)hdr); + break; + case BLE_EVT_GAP_PAIR_COMPLETED: { + ble_evt_gap_pair_completed_t *evt = (ble_evt_gap_pair_completed_t *)hdr; + prv_handle_pairing_completed(evt); + break; + } + + case BLE_EVT_GAP_ADDRESS_RESOLVED: { + const ble_evt_gap_address_resolved_t *evt = (const ble_evt_gap_address_resolved_t *)hdr; + prv_handle_address_resolved(evt); + break; + } + + case BLE_EVT_GAP_DEV_ADDR_UPDATED: { + const ble_evt_gap_dev_address_updated_t *evt = + (const ble_evt_gap_dev_address_updated_t *)hdr; + const BTDeviceAddress *addr = (const BTDeviceAddress *)&evt->address.addr; + PBL_LOG(LOG_LEVEL_DEBUG, "Local address updated to "BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE_PTR(addr)); + local_addr_handle_update(addr); + break; + } + + case BLE_EVT_GAP_GET_PEER_VERSION_COMPLETE: + prv_handle_get_peer_version_complete((const ble_evt_gap_get_peer_version_t *)hdr); + break; + + // GATT Client Events: + case BLE_EVT_GATTC_MTU_CHANGED: + prv_handle_gatt_mtu_changed((const ble_evt_gattc_mtu_changed_t *)hdr); + break; + + case BLE_EVT_GATTC_BROWSE_SVC: + gatt_client_discovery_process_service((const ble_evt_gattc_browse_svc_t *)hdr); + break; + + case BLE_EVT_GATTC_BROWSE_COMPLETED: + gatt_client_discovery_handle_complete((const ble_evt_gattc_browse_completed_t *)hdr); + break; + + case BLE_EVT_GATTC_READ_COMPLETED: + gatt_wrapper_handle_read_completed((const ble_evt_gattc_read_completed_t *)hdr); + break; + + case BLE_EVT_GATTC_WRITE_COMPLETED: + gatt_wrapper_handle_write_completed((const ble_evt_gattc_write_completed_t *)hdr); + break; + + case BLE_EVT_GATTC_NOTIFICATION: + gatt_wrapper_handle_notification((const ble_evt_gattc_notification_t *)hdr); + break; + + case BLE_EVT_GATTC_INDICATION: + gatt_wrapper_handle_indication((const ble_evt_gattc_indication_t *)hdr); + break; + + case IRB_BLE_STACK_MSG: + if (((irb_ble_stack_msg_t *)hdr)->msg_type == HCI_EVT_MSG) { + hci_rom_passthrough_handle_evt(&((irb_ble_stack_msg_t *)hdr)->msg.hci.evt); +#if TEST_HCI_ROM_PASSTHROUGH + test_hci_passthrough(); +#endif + break; + } + // FALLTHROUGH + + default: // Unhandled + ble_handle_event_default(hdr); + PBL_LOG(LOG_LEVEL_DEBUG, "Unhandled BLE event: 0x%"PRIx16, hdr->evt_code); + break; + } + + handled: + OS_FREE(hdr); + + no_event: + // notify again if there are more events to process in queue + if (ble_has_event()) { + xTaskNotify(OS_GET_CURRENT_TASK(), BLE_APP_NOTIFY_MASK, eSetBits); + } + } + } +} + +void ble_task_init(const BTDriverConfig *config) { + const BleTaskInitInfo init_info = { + .semph = xSemaphoreCreateBinary(), + .config = config, + }; + PBL_ASSERTN(init_info.semph); + + PBL_LOG(LOG_LEVEL_INFO, "Starting BLE Task..."); + + OS_TASK_CREATE("BT", prv_ble_peripheral_task, (void *)&init_info, + 1280 /* bytes */, mainBLE_PERIPHERAL_TASK_PRIORITY, + DialogTaskList[DialogTask_Ble]); + OS_ASSERT(DialogTaskList[DialogTask_Ble]); + + xSemaphoreTake(init_info.semph, portMAX_DELAY); + vSemaphoreDelete(init_info.semph); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/bonding_sync.c b/src/bluetooth-fw/da1468x/controller/main/src/bonding_sync.c new file mode 100644 index 00000000..b2206165 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/bonding_sync.c @@ -0,0 +1,189 @@ +/* + * 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. + */ + +#include +#include +#include + +#include + +#include + +#include "bonding_flags.h" +#include "connection.h" +#include "dialog_utils.h" +#include "hc_protocol/hc_endpoint_bonding_sync.h" +#include "kernel/pbl_malloc.h" +#include "ppogatt_emulated_server_wa.h" +#include "system/logging.h" +#include "system/passert.h" + +// Dialog SDK: +#include "ble_common.h" +#include "ble_gap.h" +#include "storage.h" + +static key_ltk_t *prv_create_remote_ltk_from_info(const SMRemoteEncryptionInfo *enc_info) { + key_ltk_t *ltk = kernel_zalloc_check(sizeof(key_ltk_t)); + ltk->rand = enc_info->rand; + ltk->ediv = enc_info->ediv; + memcpy(ltk->key, enc_info->ltk.data, sizeof(ltk->key)); + ltk->key_size = sizeof(ltk->key); + return ltk; +} + +static key_ltk_t *prv_create_local_ltk_from_info(const SMLocalEncryptionInfo *enc_info) { + key_ltk_t *ltk = kernel_zalloc_check(sizeof(key_ltk_t)); + ltk->rand = enc_info->rand; + ltk->ediv = enc_info->ediv; + memcpy(ltk->key, enc_info->ltk.data, sizeof(ltk->key)); + ltk->key_size = sizeof(ltk->key); + return ltk; +} + +void bonding_sync_handle_hc_add(const BleBonding *bonding) { + storage_acquire(); + const SMPairingInfo *info = &bonding->pairing_info; + bd_address_t addr; + dialog_utils_bt_device_to_bd_address(&bonding->pairing_info.identity, &addr); + device_t *dev = find_device_by_addr(&addr, true /* create */); + + dev->paired = true; + dev->bonded = true; + dev->is_gateway = bonding->is_gateway; + dev->flags = bonding->flags; + dev->mitm = info->is_mitm_protection_enabled; + + // The LTK that's used when the local device is the slave. + if (info->is_remote_encryption_info_valid) { + dev->ltk = prv_create_remote_ltk_from_info(&info->remote_encryption_info); + } + + // The LTK that's used when the local device is the master + // (we call it "local", Dialog calls it "remote"... :-S ) + if (info->is_local_encryption_info_valid) { + dev->remote_ltk = prv_create_local_ltk_from_info(&info->local_encryption_info); + } + + if (info->is_remote_identity_info_valid) { + if (sm_is_pairing_info_irk_not_used(&info->irk)) { + dev->irk = NULL; + } else { + key_irk_t *irk = kernel_zalloc_check(sizeof(key_irk_t)); + memcpy(&irk->key, info->irk.data, sizeof(irk->key)); + dev->irk = irk; + } + } + + storage_release(); + + PBL_LOG(LOG_LEVEL_DEBUG, "Added pairing for "BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE(info->identity.address)); +} + +void bonding_sync_handle_hc_remove(const BleBonding *bonding) { + const SMPairingInfo *info = &bonding->pairing_info; + uint16_t conn_idx = BLE_CONN_IDX_INVALID; + storage_acquire(); + bd_address_t addr; + dialog_utils_bt_device_to_bd_address(&bonding->pairing_info.identity, &addr); + device_t *dev = find_device_by_addr(&addr, false /* create */); + if (dev) { + conn_idx = dev->conn_idx; + device_remove_pairing(dev); + } + storage_release(); + + // set is_gateway to false within the Connection + if (conn_idx != BLE_CONN_IDX_INVALID) { + Connection *conn = connection_by_idx(conn_idx); + if (conn) { + connection_set_gateway(conn, false); + } + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Removed pairing for "BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE(info->identity.address)); +} + +void bonding_sync_handle_pairing_completed(Connection *connection, uint16_t conn_idx) { + BleBonding bonding = {}; + SMPairingInfo *info = &bonding.pairing_info; + bool success = false; + + storage_acquire(); + device_t *dev = find_device_by_conn_idx(conn_idx); + if (dev && dev->bonded) { + // The LTK that's used when the local device is the master. + if (dev->ltk) { + memcpy(&info->remote_encryption_info.ltk, dev->ltk->key, + sizeof(info->remote_encryption_info.ltk)); + info->remote_encryption_info.rand = dev->ltk->rand; + info->remote_encryption_info.ediv = dev->ltk->ediv; + info->is_remote_encryption_info_valid = true; + } + // The LTK that's used when the local device is the slave + // (we call it "local", Dialog calls it "remote"... :-S ) + if (dev->remote_ltk) { + memcpy(&info->local_encryption_info.ltk, dev->remote_ltk->key, + sizeof(info->local_encryption_info.ltk)); + info->local_encryption_info.ediv = dev->remote_ltk->ediv; + info->local_encryption_info.rand = dev->remote_ltk->rand; + info->is_local_encryption_info_valid = true; + } + if (dev->irk) { + memcpy(&info->irk, dev->irk->key, sizeof(info->irk)); + dialog_utils_bd_address_to_bt_device(&dev->addr, &info->identity); + info->is_remote_identity_info_valid = true; + } else { + // If the device did not exchange an IRK, we have been given the devices identity address and + // this is how we will associate our connection with the device in the future. Therefore, mark + // is_remote_identity_info_valid as true but leave the irk 0'ed out to indicate it is unused + PBL_LOG(LOG_LEVEL_ALWAYS, "Device did not exchange an IRK"); + info->is_remote_identity_info_valid = true; + dialog_utils_bd_address_to_bt_device(&dev->addr, &info->identity); + } + info->is_mitm_protection_enabled = dev->mitm; + bonding.is_gateway = connection_is_gateway(connection); + + if (connection_should_pin_address(connection)) { + bonding.should_pin_address = true; + connection_get_local_address(connection, &bonding.pinned_address); + } + + BleBondingFlag flags = 0; + + if (connection_should_auto_accept_re_pairing(connection)) { + flags |= BleBondingFlag_ShouldAutoAcceptRePairing; + } + + if (connection_is_reversed_ppogatt_enabled(connection)) { + flags |= BleBondingFlag_IsReversedPPoGATTEnabled; + } + + bonding.flags = flags; + dev->flags = flags; + + success = true; + } else { + PBL_LOG(LOG_LEVEL_ERROR, "Pairing fail? conn=%p, dev=%p", connection, dev); + } + storage_release(); + + if (success) { + hc_endpoint_bonding_sync_add(&bonding); + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/chip_id.c b/src/bluetooth-fw/da1468x/controller/main/src/chip_id.c new file mode 100644 index 00000000..ef33454c --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/chip_id.c @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#include "chip_id.h" + +#include "bsp_definitions.h" +#include "da1468x_mem_map.h" +#include "hw_otpc.h" +#include "system/logging.h" + +#include +#include + +#define OTP_CELL_SIZE_BYTES (8) + +static uint32_t prv_get_cell_offset_for_address(uintptr_t otp_addr) { + return (otp_addr - MEMORY_OTP_BASE) / OTP_CELL_SIZE_BYTES; +} + +static bool prv_read_otp_bytes(uintptr_t otp_addr, uint8_t *buffer_out, size_t length_bytes) { + // Ceil! + size_t num_words = (length_bytes + (sizeof(uint32_t) - 1)) / sizeof(uint32_t); + + // Ensure our accesses are aligned by using this temporary buffer: + uint32_t temp_buffer[num_words]; + bool rv = hw_otpc_fifo_read(temp_buffer, prv_get_cell_offset_for_address(otp_addr), + HW_OTPC_WORD_LOW, num_words, false /* spare_rows */); + memcpy(buffer_out, temp_buffer, length_bytes); + + if (!rv) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to read OTP %p", (void *)otp_addr); + } + + return rv; +} + +bool dialog_chip_id_copy(DialogChipID *chip_id_out) { + hw_otpc_init(); + +#if dg_configEXT_CRYSTAL_FREQ == EXT_CRYSTAL_IS_16M + HW_OTPC_SYS_CLK_FREQ freq = HW_OTPC_SYS_CLK_FREQ_16; +#elif dg_configEXT_CRYSTAL_FREQ == EXT_CRYSTAL_IS_32M + HW_OTPC_SYS_CLK_FREQ freq = HW_OTPC_SYS_CLK_FREQ_32; +#else +# error "Unsupported sysclk frequency" +#endif + + hw_otpc_set_speed(freq); + + // Read the position/package/timestamp info: + bool rv = prv_read_otp_bytes(OTP_POS_PKG_TIMESTAMP_ADDRESS, (uint8_t *)&chip_id_out->info, + sizeof(chip_id_out->info)); + + // Read the chip identifier string: + rv &= prv_read_otp_bytes(OTP_CHIP_ID_ADDRESS, (uint8_t *)&chip_id_out->chip_id, + sizeof(chip_id_out->chip_id)); + + // Zero terminate, just in case: + chip_id_out->chip_id[sizeof(chip_id_out->chip_id) - 1] = '\0'; + + hw_otpc_disable(); + + return rv; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/connection.c b/src/bluetooth-fw/da1468x/controller/main/src/connection.c new file mode 100644 index 00000000..5d614770 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/connection.c @@ -0,0 +1,487 @@ +/* + * 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. + */ + +#include "connection.h" +#include "connection_private.h" + +#include "ble_task.h" +#include "util/list.h" +#include "kernel/pbl_malloc.h" +#include "os/mutex.h" +#include "system/logging.h" +#include "system/passert.h" + +#include +#include +#include + +#include +#include + +extern void ppogatt_destroy_state(PPoGATTWorkAroundState *state); + +typedef struct ForEachCbData { + ConnectionForEachCallback cb; + void *data; +} ForEachCbData; + +static Connection *s_connections; + +// We expect several threads to call this module (i.e ble_task, host_transport) so +// make it threadsafe +static PebbleRecursiveMutex *s_connection_lock = NULL; + +void connection_module_init(void) { + s_connection_lock = mutex_create_recursive(); +} + +static void prv_lock(void) { + mutex_lock_recursive(s_connection_lock); +} + +static void prv_unlock(void) { + mutex_unlock_recursive(s_connection_lock); +} + +Connection *connection_create(uint16_t conn_idx, const BTDeviceInternal *initial_addr, + const BTDeviceAddress *local_addr, + const BleConnectionParams *params) { + ble_task_assert_is_executing_on_ble_task(); + Connection *connection; + prv_lock(); + { + connection = kernel_malloc_check(sizeof(Connection)); + *connection = (Connection) { + .conn_idx = conn_idx, + .initial_addr = *initial_addr, + .local_addr = *local_addr, + .conn_params = *params, + }; + s_connections = (Connection *)list_prepend((ListNode *)s_connections, (ListNode *)connection); + } + prv_unlock(); + return connection; +} + +void connection_destroy(Connection *connection) { + ble_task_assert_is_executing_on_ble_task(); + prv_lock(); + { + if (connection_is_valid(connection)) { + list_remove((ListNode *)connection, (ListNode **)&s_connections, NULL); + + ListNode *tmp = (ListNode *) connection->gatt_op_list; + while (tmp) { + ListNode *next = list_get_next((ListNode *)tmp); + kernel_free(tmp); + tmp = next; + } + + if (connection->ppogatt_wa_state) { + ppogatt_destroy_state(connection->ppogatt_wa_state); + } + + kernel_free(connection); + } + } + prv_unlock(); +} + +bool connection_is_valid(Connection *connection) { + bool rv; + prv_lock(); + { + rv = list_contains((ListNode *)s_connections, (const ListNode *)connection); + } + prv_unlock(); + return rv; +} + +static bool prv_connection_by_idx_list_filter_cb(ListNode *found_node, void *data) { + uint16_t conn_idx = (uintptr_t)data; + return (((Connection *)found_node)->conn_idx == conn_idx); +} + +Connection *connection_by_idx(uint16_t conn_idx) { + Connection *connection; + prv_lock(); + { + connection = (Connection *)list_find( + (ListNode *)s_connections, prv_connection_by_idx_list_filter_cb, + (void *)(uintptr_t)conn_idx); + } + prv_unlock(); + return connection; +} + +Connection *connection_by_idx_check(uint16_t conn_idx) { + Connection *connection; + prv_lock(); + { + connection = connection_by_idx(conn_idx); + PBL_ASSERTN(connection); + } + prv_unlock(); + return connection; +} + +static bool prv_connection_by_address_list_filter_cb(ListNode *found_node, void *data) { + const BTDeviceInternal *desired_addr = (const BTDeviceInternal *)data; + const Connection *connection = (const Connection *)found_node; + return (bt_device_internal_equal(desired_addr, &connection->initial_addr) || + (connection->has_updated_addr && + bt_device_internal_equal(desired_addr, &connection->updated_addr))); +} + +Connection *connection_by_address(const BTDeviceInternal *addr) { + Connection *connection; + prv_lock(); + { + connection = (Connection *)list_find( + (ListNode *)s_connections, prv_connection_by_address_list_filter_cb, (void *)addr); + } + prv_unlock(); + return connection; +} + +Connection *connection_by_address_check(const BTDeviceInternal *addr) { + Connection *connection; + prv_lock(); + { + connection = connection_by_address(addr); + PBL_ASSERTN(connection); + } + prv_unlock(); + return connection; +} + +static bool prv_connection_for_each_cb(ListNode *found_node, void *data) { + ForEachCbData *cb_data = (ForEachCbData *)data; + cb_data->cb((Connection *)found_node, cb_data->data); + return false; // Force not found -- iterate the entire list +} + +void connection_for_each(ConnectionForEachCallback cb, void *data) { + prv_lock(); + ForEachCbData cb_data = { .cb = cb, .data = data }; + { + list_find((ListNode *)s_connections, prv_connection_for_each_cb, &cb_data); + } + prv_unlock(); +} + +// +// Getters +// +// Note: Using a lock on the getters doesn't really accomplish anything since +// the fields we read are either set on creation or atomic operations. We do +// run the risk that the connection handle is no longer valid and could add +// some extra checks in the future to protect against that I suppose + +uint16_t connection_get_idx(Connection *connection) { + uint16_t idx; + prv_lock(); + { + idx = connection->conn_idx; + } + prv_unlock(); + return idx; +} + +void connection_get_address(const Connection *connection, BTDeviceInternal *addr_buf) { + prv_lock(); + { + if (connection->has_updated_addr) { + *addr_buf = connection->updated_addr; + } else { + *addr_buf = connection->initial_addr; + } + } + prv_unlock(); +} + +void connection_get_local_address(Connection *connection, BTDeviceAddress *addr_buf) { + prv_lock(); + { + *addr_buf = connection->local_addr; + } + prv_unlock(); +} + +void connection_get_conn_params(const Connection *connection, + BleConnectionParams *params_out) { + prv_lock(); + { + *params_out = connection->conn_params; + } + prv_unlock(); +} + +void connection_get_address_by_idx_check(uint16_t conn_idx, BTDeviceInternal *addr_out) { + prv_lock(); + { + Connection *connection = connection_by_idx_check(conn_idx); + connection_get_address(connection, addr_out); + } + prv_unlock(); +} + +uint8_t connection_get_last_pairing_result(uint16_t conn_idx) { + uint8_t rv = 0; + prv_lock(); + { + Connection *connection = connection_by_idx(conn_idx); + if (connection) { + rv = connection->last_pairing_result; + } + } + prv_unlock(); + return rv; +} + +PPoGATTWorkAroundState *connection_get_ppogatt_wa_state(Connection *connection) { + PPoGATTWorkAroundState *state; + prv_lock(); + { + state = connection->ppogatt_wa_state; + } + prv_unlock(); + return state; +} + +bool connection_is_gateway(Connection *connection) { + bool val; + prv_lock(); + { + val = connection->is_gateway; + } + prv_unlock(); + return val; +} + +static bool prv_get_flag(const Connection *connection, ConnectionFlag flag) { + bool val; + prv_lock(); + { + uint32_t bit = (1 << flag); + val = ((connection->flags & bit) == bit); + } + prv_unlock(); + return val; +} + +bool connection_is_subscribed_to_connection_status_notifications(const Connection *connection) { + return prv_get_flag(connection, ConnectionFlag_IsSubscribedToConnectionStatusNotifications); +} + +bool connection_should_pin_address(const Connection *connection) { + return prv_get_flag(connection, ConnectionFlag_ShouldPinAddress); +} + +bool connection_should_auto_accept_re_pairing(const Connection *connection) { + return prv_get_flag(connection, ConnectionFlag_ShouldAutoAcceptRePairing); +} + +bool connection_is_reversed_ppogatt_enabled(const Connection *connection) { + return prv_get_flag(connection, ConnectionFlag_IsReversedPPoGATTEnabled); +} + +bool connection_is_subscribed_to_gatt_mtu_notifications(const Connection *connection) { + return prv_get_flag(connection, ConnectionFlag_IsSubscribedToGattMtuNotifications); +} + +bool connection_is_subscribed_to_conn_param_notifications(const Connection *connection) { + return prv_get_flag(connection, ConnectionFlag_IsSubscribedToConnParamNotifications); +} + +// +// Setters +// + +void connection_set_gateway(Connection *connection, bool is_gateway) { + prv_lock(); + { + if (connection_is_valid(connection)) { + connection->is_gateway = is_gateway; + } + } + prv_unlock(); +} + +static char prv_debug_char_for_flag(ConnectionFlag flag) { + static const char s_debug_chars[] = { + [ConnectionFlag_IsSubscribedToConnectionStatusNotifications] = 'S', + [ConnectionFlag_IsSubscribedToGattMtuNotifications] = 'M', + [ConnectionFlag_IsSubscribedToConnParamNotifications] = 'C', + [ConnectionFlag_ShouldPinAddress] = 'P', + [ConnectionFlag_ShouldAutoAcceptRePairing] = 'A', + [ConnectionFlag_IsReversedPPoGATTEnabled] = 'R', + }; + return s_debug_chars[flag]; +} + +static void prv_set_flag(Connection *connection, bool enabled, ConnectionFlag flag) { + prv_lock(); + { + if (connection_is_valid(connection)) { + PBL_LOG(LOG_LEVEL_DEBUG, "Changing connection flag: %c=%u", + prv_debug_char_for_flag(flag), enabled); + if (enabled) { + connection->flags |= (1 << flag); + } else { + connection->flags &= ~(1 << flag); + } + } + } + prv_unlock(); +} + +void connection_set_subscribed_to_connection_status_notifications( + Connection *connection, bool is_subscribed) { + prv_set_flag(connection, is_subscribed, + ConnectionFlag_IsSubscribedToConnectionStatusNotifications); +} + +void connection_set_subscribed_to_gatt_mtu_notifications( + Connection *connection, bool is_subscribed) { + prv_set_flag(connection, is_subscribed, + ConnectionFlag_IsSubscribedToGattMtuNotifications); +} + +void connection_set_subscribed_to_conn_param_notifications( + Connection *connection, bool is_subscribed) { + prv_set_flag(connection, is_subscribed, + ConnectionFlag_IsSubscribedToConnParamNotifications); +} + +void connection_set_should_pin_address(Connection *connection, bool should_pin_address) { + prv_set_flag(connection, should_pin_address, + ConnectionFlag_ShouldPinAddress); +} + +void connection_set_should_auto_accept_re_pairing(Connection *connection, + bool should_auto_accept_re_pairing) { + prv_set_flag(connection, should_auto_accept_re_pairing, + ConnectionFlag_ShouldAutoAcceptRePairing); +} + +void connection_set_reversed_ppogatt_enabled(Connection *connection, + bool is_reversed_ppogatt_enabled) { + prv_set_flag(connection, is_reversed_ppogatt_enabled, + ConnectionFlag_IsReversedPPoGATTEnabled); +} + +void connection_set_last_pairing_result(uint16_t conn_idx, uint8_t result) { + prv_lock(); + { + Connection *connection = connection_by_idx(conn_idx); + if (connection) { + connection->last_pairing_result = result; + } + } + prv_unlock(); +} + +void connection_set_ppogatt_wa_state(Connection *connection, PPoGATTWorkAroundState *state) { + prv_lock(); + { + connection->ppogatt_wa_state = state; + } + prv_unlock(); +} + +void connection_set_conn_params(Connection *connection, const BleConnectionParams *params) { + prv_lock(); + { + if (connection_is_valid(connection)) { + connection->conn_params = *params; + } + } + prv_unlock(); +} + +void connection_update_address(Connection *connection, const BTDeviceInternal *updated_addr) { + prv_lock(); + { + if (connection_is_valid(connection)) { + connection->updated_addr = *updated_addr; + connection->has_updated_addr = true; + } + } + prv_unlock(); +} + +// +// Other functions +// + +void connection_enqueue_gatt_op(Connection *connection, uintptr_t context_ref, + GattRespDest resp_dest, GattOpType op_type) { + prv_lock(); + { + GattOperation *node = kernel_malloc_check(sizeof(GattOperation)); + *node = (GattOperation) { + .object_ref = context_ref, + .resp_dest = resp_dest, + .op_type = op_type, + }; + connection->gatt_op_list = (GattOperation *) + list_get_head(list_append((ListNode *)connection->gatt_op_list, (ListNode *)node)); + } + prv_unlock(); +} + +bool connection_dequeue_gatt_op(Connection *connection, uintptr_t *context_ref, + GattRespDest *resp_dest, GattOpType expected_op_type) { + bool rv = true; + prv_lock(); + { + GattOperation *tmp = connection->gatt_op_list; + if (!tmp) { + rv = false; + goto unlock; + } + + *context_ref = tmp->object_ref; + *resp_dest = tmp->resp_dest; + + PBL_ASSERTN(tmp->op_type == expected_op_type); + + connection->gatt_op_list = (GattOperation *)list_pop_head((ListNode *)connection->gatt_op_list); + kernel_free(tmp); + } +unlock: + prv_unlock(); + return rv; +} + +bool connection_pop_gatt_op(Connection *connection) { + bool rv = true; + prv_lock(); + { + GattOperation *tmp_tail = (GattOperation *)list_get_tail((ListNode *)connection->gatt_op_list); + if (!tmp_tail) { + PBL_LOG(LOG_LEVEL_WARNING, "Gatt: Attempted to pop recent op when list empty"); + PBL_ASSERTN(0); + } + + connection->gatt_op_list = (GattOperation *) + list_get_head(list_pop_tail((ListNode *)connection->gatt_op_list)); + kernel_free(tmp_tail); + } + prv_unlock(); + return rv; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/core_dump.c b/src/bluetooth-fw/da1468x/controller/main/src/core_dump.c new file mode 100644 index 00000000..282e09ba --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/core_dump.c @@ -0,0 +1,606 @@ +/* + * 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. + */ + +#include "core_dump.h" + +#include +#include +#include +#include + +#include "sdk_defs.h" +#include "hw_spi.h" +#include "hw_timer0.h" +#include "hw_timer1.h" +#include "hw_watchdog.h" +#include "sys_power_mgr.h" +#include "sys_clock_mgr.h" + +#include "da1468x_mem_map.h" +#include "platform_devices.h" +#include "util/build_id.h" +#include "util/crc32.h" + +#include "host_transport_impl.h" +#include "host_transport_protocol.h" +#include "reboot_reason.h" + +#define TIMER0_CLK_RATE (32000) +#define TIMER0_RELOAD_VALUE (65535) +#define TIMER0_1_S (TIMER0_CLK_RATE / 1) +#define TIMER0_1_MS (TIMER0_CLK_RATE / 1000) +#define TIMER0_10_MS (TIMER0_CLK_RATE / 100) + +// Modified from ad_spi.c. We don't want to use DMA here! +#define CORE_DUMP_SPI_SLAVE_TO_EXT_MASTER(bus, name, _word_mode, pol_mode, _phase_mode, \ + _dma_channel) \ + const spi_device_config dev_##name = { \ + .bus_id = HW_##bus, \ + .bus_res_id = RES_ID_##bus, \ + .hw_init.smn_role = HW_SPI_MODE_SLAVE, \ + .hw_init.cs_pad = { 0, 0 }, \ + .hw_init.word_mode = _word_mode, \ + .hw_init.polarity_mode = pol_mode, \ + .hw_init.phase_mode = _phase_mode, \ + .hw_init.xtal_freq = 0, \ + .hw_init.fifo_mode = HW_SPI_FIFO_RX_TX, \ + .hw_init.disabled = 0, \ + .hw_init.use_dma = _dma_channel >= 0, \ + .hw_init.rx_dma_channel = _dma_channel, \ + .hw_init.tx_dma_channel = _dma_channel + 1, \ + }; + +CORE_DUMP_SPI_SLAVE_TO_EXT_MASTER(SPI1, CORE_DUMP_SPI, CONFIG_SPI_WORD_MODE, + CONFIG_SPI_POL_MODE, CONFIG_SPI_PHASE_MODE, + CONFIG_SPI_DMA_CHANNEL); + +void DMA_Handler(void); // This is the Dialog SPI/DMA Interrupt handler + + +static volatile bool s_spi_dma_complete; + +static const spi_device_config * const s_spi = &dev_CORE_DUMP_SPI; + +static bool s_core_dump_initiated = false; +extern uint32_t *g_stacked_regs; + +// The following structures are accessed below in __asm. Because the Cortex-M0 doesn't handle +// unaligned accesses automatically, and we don't want to do it ourselves, let's force the +// elements we care about to be aligned correctly. +typedef struct { + int8_t padding[3]; + CoreDumpThreadInfo ti; +} CoreDumpThreadInfo_Wrapper; +static CoreDumpThreadInfo_Wrapper ALIGN(4) s_thread_info; +_Static_assert(((uint32_t)&s_thread_info.ti.registers[0] % 4) == 0, + "Structure not correctly aligned"); +static CoreDumpExtraRegInfo ALIGN(4) s_extra_info; + + +extern void debug_uart_init(void); + +// Linker symbols +extern const uint8_t __heap_start; +extern const uint8_t __zero_table_start; +extern const uint8_t __text_start; +extern const uint8_t __text_end; +extern const uint8_t __data_start__; +extern const uint8_t __zero_initialized_start__; +extern const uint8_t __ble_vars_start__; +extern const uint8_t __cache_ram_zi_start__; +extern const uint8_t __debug_region_start__; + +extern const uint32_t __vector_table_length[]; +extern const uint32_t __heap_length[]; +extern const uint32_t __text_length[]; +extern const uint32_t __rwdata_length[]; +extern const uint32_t __stack_bss_length[]; +extern const uint32_t __ble_vars_length[]; +extern const uint32_t __cache_ram_length[]; +extern const uint32_t __debug_region_length[]; + +extern const ElfExternalNote DIALOG_BUILD_ID; + +static const MemoryRegion s_MemoryRegions[MemoryRegionTagCount] = { + { .tag = MemoryRegionTag_Text, // .text is first so it's easily excluded on crc check + .start = &__text_start, + .length = (uint32_t)__text_length, + }, + { .tag = MemoryRegionTag_VectorTable, + .start = (void *)DATA_RAM_BASE_ADDRESS, + .length = (uint32_t)__vector_table_length, + }, + { .tag = MemoryRegionTag_Heap, + .start = &__heap_start, + .length = (uint32_t)__heap_length, + }, + { .tag = MemoryRegionTag_RwData, + .start = &__data_start__, + .length = (uint32_t)__rwdata_length, + }, + { .tag = MemoryRegionTag_StackAndBss, + .start = &__zero_initialized_start__, + .length = (uint32_t)__stack_bss_length, + }, + { .tag = MemoryRegionTag_BleVariables, + .start = &__ble_vars_start__, + .length = (uint32_t)__ble_vars_length, + }, + { .tag = MemoryRegionTag_CacheRam, + .start = &__cache_ram_zi_start__, + .length = (uint32_t)__cache_ram_length, + }, + { + .tag = MemoryRegionTag_RebootReason, + .start = &__debug_region_start__, + .length = (uint32_t)__debug_region_length, + }, +}; + +static uint16_t prv_timer_get_ticks(void) { + return hw_timer0_get_on(); +} + +static uint16_t prv_timer_delta(uint16_t start_ticks, uint16_t end_ticks) { + // The timer is configured to repeatedly count down from 65535, resetting at 0 back to 65535. + // The delta calculated here takes advantage of the underflow to produce the correct result. + return start_ticks - end_ticks; +} + +static bool prv_timer_check_delta(uint16_t start_ticks, uint16_t delta_ticks) { + uint16_t curr_ticks = prv_timer_get_ticks(); + bool s = (prv_timer_delta(start_ticks, curr_ticks) < delta_ticks); + return s; +} + +static void prv_timer_delay_1ms(void) { + uint16_t start_ticks = prv_timer_get_ticks(); + while (prv_timer_check_delta(start_ticks, TIMER0_1_MS)) {}; +} + + +static NORETURN prv_low_power_mode(void) { + // Give the host a few seconds to reboot the chip before we attempt to power everthing off (If + // everything gets powered off the BT reboot reason persisted in RAM (and flushed to analytics on + // reboot) will be lost) + for (int i = 0; i < 4; i++) { + hw_watchdog_set_pos_val(0xFF); + uint16_t wd_start_ticks = prv_timer_get_ticks(); + while (prv_timer_check_delta(wd_start_ticks, TIMER0_1_S)) { + } + } + + REG_SET_BIT(CRG_TOP, CLK_RADIO_REG, BLE_LP_RESET); /* reset BLE LP timer */ + + hw_timer0_disable(); + hw_timer1_disable(); + + // The following block of code was lifted from sys_power_mgr.c:apply_wfi(). + // + // TODO: It's not entirely clear what's going on, but it does reduce current draw to ~0 uA. At + // some point, it's probably worth figuring out how to cleanly hibernate (PBL-42430) + hw_cpm_enable_clockless(); + hw_cpm_disable_xtal32k(); + SCB->SCR |= (1 << 2); + cm_sys_clk_sleep(true); + hw_cpm_pll_sys_off(); + hw_cpm_activate_pad_latches(); + hw_cpm_power_down_periph_pd(); + hw_cpm_wait_per_power_down(); + hw_cpm_wait_rad_power_down(); + hw_cpm_rfcu_clk_off(); + hw_cpm_disable_rc16(); + hw_cpm_dcdc_off(); + hw_cpm_ldo_io_ret_off(); + + hw_watchdog_freeze(); + + while (true) { + __WFI(); + } + + __builtin_unreachable(); +} + +// Timer0 is very limited -- there is no way to reset the count until it's triggered, so it's +// not useful as a generic one-shot. We'll let it run free and calculate deltas. +static void prv_timer_enable(void) { + timer0_config cfg = { + .clk_src = HW_TIMER0_CLK_SRC_SLOW, + .on_clock_div = false, + .on_reload = TIMER0_RELOAD_VALUE, + }; + hw_timer0_init(&cfg); + hw_timer0_enable(); +} + +// The initial handshake is required because a) we can't use INT as a busy/ready signal, and b) +// we can't be sure that the host isn't still clocking out data from the previous command. +// Shift incoming data through a pattern buffer. When we get a match, send our response. +static bool prv_initial_handshake(void) { + uint8_t rx_index = 0; + uint8_t tx_index = 0; + uint8_t buffer[sizeof(core_dump_connect_ping)]; + uint16_t tx_start_ticks; + bool rx_mode; + + printf("Waiting for host:\n"); + + for (int count = 10; count > 0; --count) { + printf("."); + // Feed the watchdog + hw_watchdog_set_pos_val(0xFF); + + // Configure for RX mode + rx_mode = true; + hw_spi_change_fifo_mode(s_spi->bus_id, HW_SPI_FIFO_RX_ONLY); + + // Spin for 1 second before handling the watchdog/INT line + uint16_t wd_start_ticks = prv_timer_get_ticks(); + while (prv_timer_check_delta(wd_start_ticks, TIMER0_1_S)) { + if (rx_mode) { + // Has a byte arrived? + if (!hw_spi_get_interrupt_status(s_spi->bus_id)) { + continue; + } + hw_spi_clear_interrupt(s_spi->bus_id); + buffer[rx_index++] = hw_spi_fifo_read8(s_spi->bus_id); + + if (rx_index == sizeof(core_dump_connect_ping)) { + if (memcmp(buffer, core_dump_connect_ping, sizeof(core_dump_connect_ping)) == 0) { + // Handshake received. Switch to tx mode. + hw_spi_change_fifo_mode(s_spi->bus_id, HW_SPI_FIFO_TX_ONLY); + rx_mode = false; + tx_index = 0; + tx_start_ticks = prv_timer_get_ticks(); + continue; + } else { + // Buffer is full. Shift everything down one byte. + rx_index--; + memmove(&buffer[0], &buffer[1], rx_index); + } + } + } else { // Tx Mode + if (hw_spi_is_tx_fifo_full(s_spi->bus_id)) { + // Don't spin for too long, just in case the host doesn't clock out our response + if (!prv_timer_check_delta(tx_start_ticks, TIMER0_10_MS)) { + break; + } + continue; + } + hw_spi_fifo_write8(s_spi->bus_id, core_dump_connect_response[tx_index++]); + if (tx_index == sizeof(core_dump_connect_response)) { + return true; + } + } + } + + // Pulse the INT line to wake the host + host_transport_set_mcu_int(true); + prv_timer_delay_1ms(); // The timer IRQ just fired so the counter won't be near roll-over + host_transport_set_mcu_int(false); + } + + return false; +} + +// Callback for SPI DMA complete +static void prv_spi_dma_tx_cb(void *user_data, uint16_t transferred) { + s_spi_dma_complete = true; +} + +// Wait for DMA to complete. Note that since we don't have interrupts enabled, we're polling for +// the interrupt and then calling the DMA Interrupt Handler directly. This will cause the +// callback above to be called. +static void prv_wait_for_dma(bool is_tx_only) { + while (!s_spi_dma_complete) { + if (NVIC_GetPendingIRQ(DMA_IRQn)) { + NVIC_ClearPendingIRQ(DMA_IRQn); + DMA_Handler(); + } + } + + // NOTE: It looks like SPI/DMA complete interrupts will fire when the data fills the FIFO, not + // when the FIFO is actually empty. Let's spin until the FIFO is drained, if we're transmitting. + // If we're receiving, this function will spin forever. See ad_spi.c:322. + if (is_tx_only) { + hw_spi_wait_while_busy(s_spi->bus_id); + } +} + +static void prv_get_cmd(uint8_t *buffer, uint8_t len) { + + + s_spi_dma_complete = false; + hw_spi_read_buf(s_spi->bus_id, buffer, len, prv_spi_dma_tx_cb, NULL); + + // Now signal the host that we're ready to RX. + host_transport_set_mcu_int(true); + prv_wait_for_dma(false); + host_transport_set_mcu_int(false); +} + +static void prv_send_response(const uint8_t *buffer, uint16_t len) { + uint32_t crc = crc32(CRC32_INIT, buffer, len); + + s_spi_dma_complete = false; + hw_spi_write_buf(s_spi->bus_id, buffer, len, prv_spi_dma_tx_cb, NULL); + + // Now signal the host that we're ready to TX. + host_transport_set_mcu_int(true); + prv_wait_for_dma(true); + host_transport_set_mcu_int(false); + + prv_timer_delay_1ms(); + + // Now send the CRC + s_spi_dma_complete = false; + hw_spi_write_buf(s_spi->bus_id, (void *)&crc, sizeof(crc), prv_spi_dma_tx_cb, NULL); + + host_transport_set_mcu_int(true); + prv_wait_for_dma(true); + host_transport_set_mcu_int(false); +} + +static void prv_cmd_handler(bool text_crc_matches) { + CoreDumpSPICmd cmd; + + printf("\nHost connected\n"); + + while (true) { + // Feed the watchdog + hw_watchdog_set_pos_val(0xFF); + + prv_get_cmd((uint8_t *)&cmd, sizeof(cmd)); + if (crc32(CRC32_INIT, &cmd, sizeof(cmd)) != CRC32_RESIDUE) { + printf("cmd CRC failed"); + prv_low_power_mode(); + } + + // Feed the watchdog + hw_watchdog_set_pos_val(0xFF); + + switch (cmd.cmd) { + case CoreDumpCmd_GetTextCRC: { + uint16_t response = text_crc_matches; // This can't be a bool (1-byte). The Dialog SPI/DMA + // Tx will lock-up and not signal completion. + printf("get text crc %d\n", sizeof(response)); + prv_send_response((uint8_t *)&response, sizeof(response)); + } + break; + + case CoreDumpCmd_GetBuildID: + printf("build id\n"); + prv_send_response((uint8_t *)&DIALOG_BUILD_ID, BUILD_ID_TOTAL_EXPECTED_LEN); + break; + + case CoreDumpCmd_ReadRegionTable: + printf("region table\n"); + prv_send_response((uint8_t *)s_MemoryRegions, sizeof(s_MemoryRegions)); + break; + + case CoreDumpCmd_ReadMemory: { + printf("read memory %p %d\n", cmd.addr, cmd.len); + prv_send_response(cmd.addr, cmd.len); + } + break; + + case CoreDumpCmd_ReadRunningThreadInfo: + printf("read TI\n"); + prv_send_response((uint8_t *)&s_thread_info.ti, sizeof(s_thread_info.ti)); + break; + + case CoreDumpCmd_ReadExtraThreadInfo: + printf("read ExtraTB\n"); + prv_send_response((uint8_t *)&s_extra_info, sizeof(s_extra_info)); + break; + + case CoreDumpCmd_LowPowerMode: + printf("low power mode\n"); + prv_low_power_mode(); + break; + + default: + break; + } + } +} + +NORETURN core_dump(bool user_requested) { + // Feed the watchdog before we continue + hw_watchdog_set_pos_val(0xFF); + + // Disable interrupts, should we not have been called from fault context + portDISABLE_INTERRUPTS(); + + // Reconfig debug serial + debug_uart_init(); + + printf("\n\nStarting Core Dump\n"); + + // Big problem if we re-enter here - it likely means we encountered and exception during + // the core dump + if (s_core_dump_initiated) { + printf("CD: re-entered\n"); + + // Update the reboot reason. Preserve the LR. + RebootReason reason; + reboot_reason_get(&reason); + reason.code = RebootReasonCode_CoreDumpReentered; + reboot_reason_set(&reason); + + // Go into low-power mode. Hard reset isn't useful. + prv_low_power_mode(); + } + s_core_dump_initiated = true; + + + if (user_requested) { + printf("CD: user requested\n"); + RebootReason reason = { .code = RebootReasonCode_CoreDumpRequested }; + reboot_reason_set(&reason); + } + + // Save the registers that would have been stacked by a fault into CoreDumpThreadInfo struct + uint32_t *regs = (uint32_t *)s_thread_info.ti.registers; + if (user_requested) { + __asm volatile ( + " mov r0, %[regs] \n" + " add r0, r0, #4 \n" // skip over r0, we can't save it + " stmia r0!, {r1-r3} \n" // save r1-r3 + " add r0, r0, #32 \n" // skip r4-r11 (8 * 4bytes) + + " mov r1, r12 \n" // save r12 + " str r1, [r0] \n" + " add r0, r0, #4 \n" + + " mov r1, lr \n" // save lr + " str r1, [r0] \n" + " add r0, r0, #4 \n" + + " mov r1, pc \n" // save pc + " str r1, [r0] \n" + " add r0, r0, #4 \n" + + " mrs r1, xpsr \n" // save xpsr + " str r1, [r0] \n" + " add r0, r0, #4 \n" + + : [regs] "+r" (regs) + : + : "r0", "r1" + ); + } else if (g_stacked_regs != NULL) { + // Copy the stacked registers from the stacked register set + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_R0] = g_stacked_regs[Stacked_Register_R0]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_R1] = g_stacked_regs[Stacked_Register_R1]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_R2] = g_stacked_regs[Stacked_Register_R2]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_R3] = g_stacked_regs[Stacked_Register_R3]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_R12] = g_stacked_regs[Stacked_Register_R12]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_LR] = g_stacked_regs[Stacked_Register_LR]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_PC] = g_stacked_regs[Stacked_Register_PC]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_XPSR] = \ + g_stacked_regs[Stacked_Register_xPSR]; + s_thread_info.ti.registers[portCANONICAL_REG_INDEX_SP] = (uint32_t)g_stacked_regs; + } + + // Copy the remaining registers + regs = (uint32_t *)&s_thread_info.ti.registers; + __asm volatile ( + " mov r0, %[regs] \n" + " add r0, r0, #16 \n" // skip r0-r3 + " stmia r0!, {r4-r7} \n" // save r4-r7 + + " mov r4, r8 \n" // save r8-r11 + " mov r5, r9 \n" + " mov r6, r10 \n" + " mov r7, r11 \n" + " stmia r0!, {r4-r7} \n" + + : [regs] "+r" (regs) + : + : "r0", "r1", "r4", "r5", "r6", "r7" + ); + + // Copy the extra registers + regs = (uint32_t *)&s_extra_info; + __asm volatile ( + " mov r0, %[regs] \n" + + " mrs r1, msp \n" // msp + " mrs r2, psp \n" // psp + " stmia r0!, {r1-r2} \n" + + " mrs r3, primask \n" // primask + " strb r3, [r0] \n" + " add r0, r0, #3 \n" // skip basepri & faultmask -- not on CM0 + + " mrs r1, control \n" // control + " strb r1, [r0] \n" + + : [regs] "+r" (regs) + : + : "r0", "r1" + ); + + // Fill in the rest of the CoreDumpThreadInfo struct + bool in_isr_task = ((s_thread_info.ti.registers[portCANONICAL_REG_INDEX_XPSR] & 0x1FF) != 0); + + if (in_isr_task) { + strncpy((char *)s_thread_info.ti.name, "ISR", sizeof(s_thread_info.ti.name)); + s_thread_info.ti.id = (uint32_t)1; + s_thread_info.ti.running = false; + } else { + TaskHandle_t current_task_id = xTaskGetCurrentTaskHandle(); + char *task_name = pcTaskGetTaskName(current_task_id); + s_thread_info.ti.running = true; + s_thread_info.ti.id = (uint32_t)current_task_id; + if (task_name) { + strncpy((char *)s_thread_info.ti.name, task_name, sizeof(s_thread_info.ti.name)); + } + } + + // Make sure that our stack pointer is somewhere rational + register uint32_t *sp __asm("sp"); + extern uint32_t __StackLimit, __StackTop; + uint32_t *stack_limit = &__StackLimit; + uint32_t *stack_top = &__StackTop; + printf("Stack: %p - %p\n", stack_limit, stack_top); + printf("Our SP = %p\n", sp); + if (!((stack_limit <= sp) || (sp <= stack_top))) { + printf("Stack is not sane (%p)! Best of luck.\n", sp); + } + + // Calculate the Application CRC + uint32_t *text_start = (uint32_t *)&__text_start; + uint32_t *text_end = (uint32_t *)&__text_end; + size_t text_length = (uint32_t)__text_length; + uint32_t text_crc_calculated, text_crc_from_image; + printf(".text: %p - %p, 0x%zX\n", text_start, text_end, text_length); + text_crc_calculated = crc32(CRC32_INIT, text_start, text_length); + text_crc_from_image = *text_end; // The CRC is at __exidx_end. + printf(".text calculated CRC32 = 0x%08" PRIX32 "\n", text_crc_calculated); + printf(".text from image CRC32 = 0x%08" PRIX32 "\n", text_crc_from_image); + if (text_crc_calculated != text_crc_from_image) { + printf(".text CRC32 does not match!\n"); + } + + // Reset the DMA controller + for (HW_DMA_CHANNEL channel = HW_DMA_CHANNEL_0; channel < HW_DMA_CHANNEL_INVALID; channel++) { + hw_dma_channel_enable(channel, HW_DMA_STATE_DISABLED); + } + + // Configure the SPI module. + hw_spi_reset(s_spi->bus_id); + hw_spi_init(s_spi->bus_id, &s_spi->hw_init); + + // Configure the hardware/GPIO interface. Use the host_transport code for now. + host_transport_init_periph(); + host_transport_configure_spi_scs_pin(SCSPinFunction_SPI_CS); + host_transport_set_mcu_int(false); + + prv_timer_enable(); + + // Loop until we get a connection from the host or 10 seconds elapses. + if (prv_initial_handshake()) { + prv_cmd_handler(text_crc_calculated == text_crc_from_image); + } + + printf("Done. Going to sleep"); + + // Go to low power mode? Reset doesn't actually get back to the ROM bootrom. + prv_low_power_mode(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/debug/FreeRTOS-openocd.c b/src/bluetooth-fw/da1468x/controller/main/src/debug/FreeRTOS-openocd.c new file mode 100644 index 00000000..851cac25 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/debug/FreeRTOS-openocd.c @@ -0,0 +1,38 @@ +/* + * 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. + */ + +/* + * Since at least FreeRTOS V7.5.3 uxTopUsedPriority is no longer + * present in the kernel, so it has to be supplied by other means for + * OpenOCD's threads awareness. + * + * Add this file to your project, and, if you're using --gc-sections, + * ``--undefined=uxTopUsedPriority'' (or + * ``-Wl,--undefined=uxTopUsedPriority'' when using gcc for final + * linking) to your LDFLAGS; same with all the other symbols you need. + */ + +#include "FreeRTOS.h" +#include + +#ifdef __GNUC__ +// CC: libutil's attributes.h also defines this: +// #define USED __attribute__((used)) +#else +#define USED +#endif + +const int USED uxTopUsedPriority = configMAX_PRIORITIES; diff --git a/src/bluetooth-fw/da1468x/controller/main/src/debug_gpio.c b/src/bluetooth-fw/da1468x/controller/main/src/debug_gpio.c new file mode 100644 index 00000000..ad8dd700 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/debug_gpio.c @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#include "board.h" +#include "system/logging.h" + +// Dialog SDK +#include + +static void prv_config_gpio_as_output_and_set_state(DebugGPIOInfo *gpio, bool active) { + // While GPIO pin itself can retain state when the system enters deep sleep the PXX_MODE_REG + // holding the configuration gets reset so reprogram the gpio pin function + hw_gpio_set_pin_function(gpio->port, gpio->pin, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_GPIO); + if (active) { + hw_gpio_set_active(gpio->port, gpio->pin); + } else { + hw_gpio_set_inactive(gpio->port, gpio->pin); + } + + gpio->is_active = active; +} + +void debug_gpio_init(void) { + for (int i = 0; i < DEBUG_GPIOS->num_debug_gpios; i++) { + prv_config_gpio_as_output_and_set_state(&DEBUG_GPIOS->debug_gpio[i], false); + } +} + +void debug_gpio_toggle(int debug_gpio_num) { + if (debug_gpio_num >= DEBUG_GPIOS->num_debug_gpios) { + PBL_LOG(LOG_LEVEL_WARNING, "Invalid debug gpio id"); + return; + } + + DebugGPIOInfo *gpio = &DEBUG_GPIOS->debug_gpio[debug_gpio_num]; + prv_config_gpio_as_output_and_set_state(gpio, !gpio->is_active); +} + +void debug_gpio_set_active(int debug_gpio_num, bool is_active) { + if (debug_gpio_num >= DEBUG_GPIOS->num_debug_gpios) { + PBL_LOG(LOG_LEVEL_WARNING, "Invalid debug gpio id"); + return; + } + + DebugGPIOInfo *gpio = &DEBUG_GPIOS->debug_gpio[debug_gpio_num]; + prv_config_gpio_as_output_and_set_state(gpio, is_active); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/debug_reboot_reason.c b/src/bluetooth-fw/da1468x/controller/main/src/debug_reboot_reason.c new file mode 100644 index 00000000..304e3e26 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/debug_reboot_reason.c @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "reboot_reason.h" +#include "system/logging.h" + +void debug_reboot_reason_print(void) { + RebootReason reason; + if (!reboot_reason_get(&reason)) { + PBL_LOG(LOG_LEVEL_DEBUG, "Dialog: Invalid reboot reason"); + goto clear; + }; + + switch (reason.code) { + // Normal stuff + case RebootReasonCode_Unknown: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to Unknown."); + break; + case RebootReasonCode_Shutdown: + // FIXME PBL-38181: For some reason, the reboot reason shutdown never gets persisted/recovered + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to Shutdown."); + break; + // Error occurred + case RebootReasonCode_Watchdog: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to watchdog, stuck task mask: 0x%"PRIx32, + reason.extra); + break; + case RebootReasonCode_Assert: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to Assert: LR 0x%"PRIx32, reason.extra); + break; + case RebootReasonCode_HardFault: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to HardFault: LR 0x%"PRIx32, reason.extra); + break; + case RebootReasonCode_StackOverflow: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to StackOverflow"); + break; + case RebootReasonCode_DialogPlatformReset: + PBL_LOG(LOG_LEVEL_ALWAYS, + "Dialog: Rebooted due to DialogPlatformReset: error 0x%"PRIx32, reason.extra); + break; + case RebootReasonCode_CoreDumpReentered: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Re-entered core dump. Possibly valid -> LR 0x%"PRIx32, + reason.extra); + break; + case RebootReasonCode_CoreDumpRequested: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: User requested core dump"); + break; + case RebootReasonCode_RomError: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to RomError: ERRORTYPESTAT 0x%"PRIx32, + reason.extra); + break; + case RebootReasonCode_NMI: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to NMI"); + break; + default: + PBL_LOG(LOG_LEVEL_ALWAYS, "Dialog: Rebooted due to Unrecognized Reason"); + break; + } + +clear: + reboot_reason_clear(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/debugger.c b/src/bluetooth-fw/da1468x/controller/main/src/debugger.c new file mode 100644 index 00000000..b087c554 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/debugger.c @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#include "debugger.h" + +#include "hw_cpm.h" +#include "hw_watchdog.h" +#include "sdk_defs.h" + +#include + +#if NO_WATCHDOG +void debugger_await(void) { + hw_watchdog_freeze(); + hw_cpm_enable_debugger(); + + printf("\nAttach gdb and enter:\n" + "(gdb) set $r0 = 0\n" + "(gdb) si\n" + "(gdb) \n"); + + NVIC_ClearPendingIRQ(HardFault_IRQn); + __asm("mov r0, #1"); + while (true) { + register uintptr_t r0 __asm("r0"); + if (r0 == 0) { + break; + } + } +} +#endif diff --git a/src/bluetooth-fw/da1468x/controller/main/src/dialog_analytics/analytics.c b/src/bluetooth-fw/da1468x/controller/main/src/dialog_analytics/analytics.c new file mode 100644 index 00000000..5395580e --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/dialog_analytics/analytics.c @@ -0,0 +1,255 @@ +/* + * 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. + */ + +#include "dialog_analytics/analytics.h" + +#include "hc_protocol/hc_endpoint_analytics.h" +#include "kernel/pbl_malloc.h" +#include "kernel/util/freertos_utils.h" +#include "system/passert.h" + +#include +#include +#include + +// FreeRTOS +#include "FreeRTOSConfig.h" + +// Dialog SDK +#include "sys_rtc.h" + +#include + +#define MS_PER_SECOND (1000) + +// Stopwatch +typedef struct { + ListNode node; + DialogAnalyticsMetric metric; + uint32_t starting_ticks; + uint32_t count_per_sec; +} AnalyticsStopwatchNode; + +// Analytic Containers +typedef struct { + ListNode node; + DialogAnalyticsMetric metric; + uint32_t value; +} AnalyticsNode; + +static ListNode *s_analytics_list = NULL; +static ListNode *s_stopwatch_list = NULL; +static PebbleRecursiveMutex *s_analytics_lock = NULL; + +// Locking Functions + +static void prv_lock(void) { + mutex_lock_recursive(s_analytics_lock); +} + +static void prv_unlock(void) { + mutex_unlock_recursive(s_analytics_lock); +} + +// Analytic Node Functions + +static bool prv_compare_analytic(ListNode *found_node, void *data) { + AnalyticsNode *node = (AnalyticsNode *)found_node; + return (node->metric == (DialogAnalyticsMetric)data); +} + +static AnalyticsNode *prv_find_analytic(DialogAnalyticsMetric metric) { + return (AnalyticsNode *)list_find( + s_analytics_list, prv_compare_analytic, (void *)(uintptr_t)metric); +} + +static void prv_add_analytic_node(DialogAnalyticsMetric metric) { + AnalyticsNode *node = kernel_malloc_check(sizeof(AnalyticsNode)); + *node = (AnalyticsNode) { + .metric = metric, + }; + s_analytics_list = list_prepend(s_analytics_list, (ListNode *)node); +} + +// Stopwatch Node Functions + +static bool prv_compare_stopwatch(ListNode *found_node, void *data) { + AnalyticsStopwatchNode *stopwatch = (AnalyticsStopwatchNode *)found_node; + return (stopwatch->metric == (DialogAnalyticsMetric)data); +} + +static AnalyticsStopwatchNode *prv_find_stopwatch_node(DialogAnalyticsMetric metric) { + return (AnalyticsStopwatchNode *)list_find( + s_stopwatch_list, prv_compare_stopwatch, (void *)(uintptr_t)metric); +} + +static void prv_add_stopwatch(DialogAnalyticsMetric metric, uint32_t count_per_sec) { + AnalyticsStopwatchNode *stopwatch = kernel_malloc_check(sizeof(AnalyticsStopwatchNode)); + *stopwatch = (AnalyticsStopwatchNode) { + .metric = metric, + .count_per_sec = count_per_sec, + .starting_ticks = rtc_get(), + }; + s_stopwatch_list = list_prepend(s_stopwatch_list, (ListNode *)stopwatch); +} + +static uint32_t prv_rtc_ticks_to_ms(uint32_t rtc_ticks) { + return (uint32_t)(((uint32_t)rtc_ticks) * MS_PER_SECOND / configSYSTICK_CLOCK_HZ); +} + +static uint32_t prv_stopwatch_elapsed_ms(AnalyticsStopwatchNode *stopwatch, + uint32_t current_ticks) { + // Even if current_ticks is less than stopwatch->starting_ticks (because `rtc_get` rv rolled + // over), this operation will still work correctly since we only use uint32_t + // See unit test `test_dialog_analytics__stopwatch_rtc_rollover` for validation. + const uint32_t difference = current_ticks - stopwatch->starting_ticks; + const uint32_t dt_ms = prv_rtc_ticks_to_ms(difference); + return (((uint32_t) stopwatch->count_per_sec) * dt_ms) / MS_PER_SECOND; +} + +// API Functions + +// Used primarily for unit tests +void analytics_init_private(uint32_t num_nodes) { + s_analytics_lock = mutex_create_recursive(); + + // create and populate list of all Analytics + for (uint32_t i = 0; i < num_nodes; i++) { + prv_add_analytic_node(i); + } +} + +void analytics_init(void) { + analytics_init_private(DialogAnalyticMetric_Count); +} + +void analytics_reset_nodes(void) { + prv_lock(); + + // Zero out all values + ListNode *iter = s_analytics_list; + while (iter) { + AnalyticsNode *node = (AnalyticsNode *)iter; + node->value = 0; + iter = iter->next; + } + + prv_unlock(); +} + +void analytics_set(DialogAnalyticsMetric metric, uint32_t value) { + prv_lock(); + + AnalyticsNode *node = prv_find_analytic(metric); + if (node) { + node->value = value; + } + + prv_unlock(); +} + +void analytics_add(DialogAnalyticsMetric metric, uint32_t amount) { + prv_lock(); + + AnalyticsNode *node = prv_find_analytic(metric); + if (node) { + node->value += amount; + } + + prv_unlock(); +} + +void analytics_inc(DialogAnalyticsMetric metric) { + analytics_add(metric, 1); +} + +void analytics_stopwatch_start_at_rate(DialogAnalyticsMetric metric, uint32_t count_per_second) { + prv_lock(); + prv_add_stopwatch(metric, count_per_second); + prv_unlock(); +} + +void analytics_stopwatch_start(DialogAnalyticsMetric metric) { + analytics_stopwatch_start_at_rate(metric, MS_PER_SECOND); +} + +void analytics_stopwatch_stop(DialogAnalyticsMetric metric) { + prv_lock(); + AnalyticsStopwatchNode *stopwatch = prv_find_stopwatch_node(metric); + if (stopwatch) { + analytics_add(metric, prv_stopwatch_elapsed_ms(stopwatch, rtc_get())); + list_remove(&stopwatch->node, &s_stopwatch_list, NULL); + } + prv_unlock(); +} + +void analytics_stopwatches_update(uint32_t current_ticks) { + prv_lock(); + + ListNode *cur = s_stopwatch_list; + while (cur != NULL) { + AnalyticsStopwatchNode *node = (AnalyticsStopwatchNode *)cur; + analytics_add(node->metric, prv_stopwatch_elapsed_ms(node, current_ticks)); + node->starting_ticks = current_ticks; + cur = cur->next; + } + + prv_unlock(); +} + +// Getters + +void analytics_each(AnalyticsEachCallback cb, void *context) { + analytics_stopwatches_update(rtc_get()); + + prv_lock(); + ListNode *iter = s_analytics_list; + while (iter) { + AnalyticsNode *node = (AnalyticsNode *)iter; + cb(node->metric, node->value, context); + iter = iter->next; + } + prv_unlock(); +} + + +// Unit Test Helpers + +uint32_t analytics_get_value(DialogAnalyticsMetric metric) { + AnalyticsNode *node = prv_find_analytic(metric); + if (node) { + return node->value; + } + return 0; +} + +void analytics_deinit(void) { + while (s_stopwatch_list) { + ListNode *head = s_stopwatch_list; + list_remove(head, &s_stopwatch_list, NULL); + kernel_free(head); + } + s_stopwatch_list = NULL; + + while (s_analytics_list) { + ListNode *head = s_analytics_list; + list_remove(head, &s_analytics_list, NULL); + kernel_free(head); + } + s_analytics_list = NULL; + + mutex_destroy((PebbleMutex *)s_analytics_lock); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/die.c b/src/bluetooth-fw/da1468x/controller/main/src/die.c new file mode 100644 index 00000000..a5e92066 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/die.c @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#include "die.h" + +#include "debugger.h" +#include "core_dump.h" + +NORETURN reset_due_to_software_failure(void) { +#if NO_WATCHDOG + debugger_await(); + while (1) {}; + __builtin_unreachable(); +#else + core_dump(false); +#endif +} + +void core_dump_and_reset_or_reboot(void) { + reset_due_to_software_failure(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/dis.c b/src/bluetooth-fw/da1468x/controller/main/src/dis.c new file mode 100644 index 00000000..a2508096 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/dis.c @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include + +#include "dis_impl.h" +#include "system/logging.h" +#include "system/passert.h" + +// Dialog headers +#include "dis.h" + +static DisInfo s_dis_info; + +void device_information_service_init(const DisInfo *dis_info) { + s_dis_info = *dis_info; +} + +bool device_information_service_register(uint16_t start_hdl) { + dis_device_info_t device_info = { + .model_number = s_dis_info.model_number, + .manufacturer = s_dis_info.manufacturer, + .serial_number = s_dis_info.serial_number, + .fw_revision = s_dis_info.fw_revision, + .sw_revision = s_dis_info.sw_revision, + }; + + bool rv = true; + ble_service_t *ble_service = dis_init(NULL, &device_info, start_hdl); + if (!ble_service) { + PBL_LOG(LOG_LEVEL_ERROR, "DIS failed to init!"); + rv = false; + } else { + PBL_ASSERTN(start_hdl == ble_service->start_h); + } + return rv; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/fault_handling.c b/src/bluetooth-fw/da1468x/controller/main/src/fault_handling.c new file mode 100644 index 00000000..444f37af --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/fault_handling.c @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#include "hw_watchdog.h" + +#include +#include +#include + +#include "core_cm0.h" +#include "core_dump.h" +#include "die.h" +#include "reboot_reason.h" +#include "sdk_defs.h" +#include "sys_watchdog.h" +#include "core_dump.h" + +uint32_t *g_stacked_regs = NULL; + +static void prv_handle_fault(void) { + // Feed the watchdog before we continue. NMI will be triggered very soon if we don't. + // Don't freeze the watchdog. It's better if the core-dump code goes into low-power mode than + // crash and drain the battery. + hw_watchdog_set_pos_val(0xFF); + + printf("R0=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_R0]); + printf("R1=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_R1]); + printf("R2=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_R2]); + printf("R3=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_R3]); + printf("R12=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_R12]); + printf("LR=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_LR]); + printf("PC=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_PC]); + printf("xPSR=0x%08" PRIx32 "\n", g_stacked_regs[Stacked_Register_xPSR]); + printf("SP=0x%08" PRIx32 "\n", (uint32_t) g_stacked_regs); // Stack Pointer + + reset_due_to_software_failure(); +} + +static bool prv_handle_hardfault_due_to_bkpt(void) { + // Note: assumes Thumb2! + const uint16_t instr = *(const uint16_t *)g_stacked_regs[Stacked_Register_PC]; + const uint16_t bkpt_instruction_opcode = 0xbe; + if ((instr >> 8) != bkpt_instruction_opcode) { + return false; + } + const uint8_t arg = (instr & 0xff); + + printf("\nbkpt %"PRIu8"! no dbgr?\n\n", arg); + return true; +} + +void HardFault_HandlerC(uint32_t *fault_args) { + // Save the fault_args pointer for later + g_stacked_regs = fault_args; + + bool is_bkpt = prv_handle_hardfault_due_to_bkpt(); + + uint32_t stacked_lr = g_stacked_regs[Stacked_Register_LR]; + RebootReason reason = { + .code = is_bkpt ? RebootReasonCode_BreakpointButNoDebuggerAttached : RebootReasonCode_HardFault, + .extra = stacked_lr, + }; + reboot_reason_set(&reason); + + printf("HardF:\n"); + prv_handle_fault(); +} + +void NMI_HandlerC(uint32_t *fault_args) { + // Save the fault_args pointer for later + g_stacked_regs = fault_args; + + uint32_t extra_reboot_info = 0; + RebootReason reason; + bool reboot_already_set = reboot_reason_get(&reason); + RebootReasonCode code = RebootReasonCode_NMI; + + // Are we here because the hw watchdog tripped? + if (hw_watchdog_did_trip()) { + if (hw_watchdog_recovered()) { + return; + } + +#if dg_configUSE_WDOG + extra_reboot_info = watchdog_get_tasks_waiting_on(); + code = RebootReasonCode_Watchdog; +#endif + } + + // chained faults could manifest in an NMI or watchdog. We are more interested in the crash that + // happened first + if (!reboot_already_set) { + reason = (RebootReason){ + .code = code, + .extra = extra_reboot_info + }; + reboot_reason_set(&reason); + } + + printf("NMI\n"); + prv_handle_fault(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/freertos_application.c b/src/bluetooth-fw/da1468x/controller/main/src/freertos_application.c new file mode 100644 index 00000000..06b8da9f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/freertos_application.c @@ -0,0 +1,116 @@ +/* + * 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. + */ + +#include + +#include "FreeRTOS.h" +#include "osal.h" + +#include "kernel/pbl_malloc.h" +#include "reboot_reason.h" +#include "util/heap.h" + +/** + * @brief Malloc fail hook + */ +void vApplicationMallocFailedHook( void ) +{ + /* vApplicationMallocFailedHook() will only be called if + configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h. It is a hook + function that will get called if a call to OS_MALLOC() fails. + OS_MALLOC() is called internally by the kernel whenever a task, queue, + timer or semaphore is created. It is also called by various parts of the + demo application. If heap_1.c or heap_2.c are used, then the size of the + heap available to OS_MALLOC() is defined by configTOTAL_HEAP_SIZE in + FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used + to query the size of free heap space that remains (although it does not + provide information on how the remaining heap might be fragmented). */ + taskDISABLE_INTERRUPTS(); +} + +/** + * @brief Application idle task hook + */ +void vApplicationIdleHook( void ) +{ + /* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set + to 1 in FreeRTOSConfig.h. It will be called on each iteration of the idle + task. It is essential that code added to this hook function never attempts + to block in any way (for example, call OS_QUEUE_GET() with a block time + specified, or call OS_DELAY()). If the application makes use of the + OS_TASK_DELETE() API function (as this demo application does) then it is also + important that vApplicationIdleHook() is permitted to return to its calling + function, because it is the responsibility of the idle task to clean up + memory allocated by the kernel to any task that has since been deleted. */ +} + +/** + * @brief Application stack overflow hook + */ +void vApplicationStackOverflowHook( OS_TASK pxTask, char *pcTaskName ) +{ + ( void ) pcTaskName; + ( void ) pxTask; + + /* Run time stack overflow checking is performed if + configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook + function is called if a stack overflow is detected. */ + taskDISABLE_INTERRUPTS(); + + printf("SO on %s", pcTaskName); + + // TODO: Register which stack had the overflow. + RebootReason reason = { + .code = RebootReasonCode_StackOverflow, + }; + reboot_reason_set(&reason); + + while (1) {}; +} + +/** + * @brief Application tick hook + */ +void vApplicationTickHook( void ) +{ +#if mainCHECK_INTERRUPT_STACK == 1 + extern unsigned long _pvHeapStart[]; + + /* This function will be called by each tick interrupt if + configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h. User code can be + added here, but the tick hook is called from an interrupt context, so + code must not attempt to block, and only the interrupt safe FreeRTOS API + functions can be used (those that end in FromISR()). */ + + /* Manually check the last few bytes of the interrupt stack to check they + have not been overwritten. Note - the task stacks are automatically + checked for overflow if configCHECK_FOR_STACK_OVERFLOW is set to 1 or 2 + in FreeRTOSConifg.h, but the interrupt stack is not. */ + OS_ASSERT( memcmp( ( void * ) _pvHeapStart, ucExpectedInterruptStackValues, sizeof( ucExpectedInterruptStackValues ) ) == 0U ); +#endif /* mainCHECK_INTERRUPT_STACK */ +} + +#ifdef JUST_AN_EXAMPLE_ISR + +void Dummy_IRQHandler(void) +{ + /* Clear the interrupt if necessary. */ + Dummy_ClearITPendingBit(); + + OS_EVENT_SIGNAL_FROM_ISR(xTestSemaphore); +} + +#endif /* JUST_AN_EXAMPLE_ISR */ diff --git a/src/bluetooth-fw/da1468x/controller/main/src/gap_le_device_name.c b/src/bluetooth-fw/da1468x/controller/main/src/gap_le_device_name.c new file mode 100644 index 00000000..39a8c10b --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/gap_le_device_name.c @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#include "connection.h" +#include "gatt_wrapper.h" +#include "system/logging.h" +#include "hc_protocol/hc_endpoint_gap_service.h" + +#include "att.h" +#include "ble_att.h" +#include "ble_common.h" +#include "ble_gap.h" +#include "ble_gattc.h" +#include "ble_uuid.h" + +#include + +#include +#include +#include +#include + +void gap_le_device_name_handle_set(const char *name) { + PBL_LOG(LOG_LEVEL_DEBUG, "Setting Local Device Name: %s", name); + ble_error_t e = ble_gap_device_name_set(name, ATT_PERM_READ); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_DEBUG, "Unexpected error setting name: %d", (int)e); + } +} + +static void prv_gap_le_device_name_request(Connection *connection) { + att_uuid_t name_id; + ble_uuid_create16(ATT_CHAR_DEVICE_NAME, &name_id); + + uint16_t conn_idx = connection_get_idx(connection); + + ble_error_t e = gatt_wrapper_read_by_uuid(conn_idx, ATT_1ST_REQ_START_HDL, ATT_1ST_REQ_END_HDL, + &name_id, + (uintptr_t)hc_endpoint_gap_service_device_name_read, + GattReqSourceController); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_WARNING, "Unexpected error requesting remote device name (conn_idx %d): %d", + conn_idx, e); + } +} + +void gap_le_device_name_handle_request(const BTDeviceInternal *addr) { + Connection *connection = connection_by_address(addr); + if (!connection) { + PBL_LOG(LOG_LEVEL_WARNING, "No connection for dev_name_request: " BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE_PTR(&addr->address)); + return; + } + prv_gap_le_device_name_request(connection); +} + +static void prv_handle_request_all_cb(Connection *connection, void *data) { + prv_gap_le_device_name_request(connection); +} + +void gap_le_device_name_handle_request_all(void) { + connection_for_each(prv_handle_request_all_cb, NULL); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/gatt_client_discovery.c b/src/bluetooth-fw/da1468x/controller/main/src/gatt_client_discovery.c new file mode 100644 index 00000000..29c02794 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/gatt_client_discovery.c @@ -0,0 +1,284 @@ +/* + * 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. + */ + +#include "gatt_client_discovery.h" + +#include "connection.h" +#include "gatt_wrapper.h" +#include "hc_protocol/hc_endpoint_discovery.h" +#include "kernel/pbl_malloc.h" +#include "ppogatt_emulated_server_wa.h" +#include "system/logging.h" +#include "system/passert.h" +#include "util/uuid.h" + +// Dialog Headers +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "rwble_hl_error.h" +#include "ble_uuid.h" + +#include +#include +#include +#include + +#define GATT_SERVICE_UUID ((uint16_t) 0x1801) +#define GATT_SERVICE_CHANGED_CHARACTERISTIC_UUID ((uint16_t) 0x2A05) +#define GATT_CCCD_UUID ((uint16_t) 0x2902) + +// Converts from a Dialog 'att_uuid_t' to firmware 'uuid' representation +static void prv_att_uuid_to_uuid(const att_uuid_t *att_uuid, Uuid *uuid) { + if (att_uuid->type == ATT_UUID_16) { + uint16_t uuid16 = att_uuid->uuid16; + *uuid = (Uuid){ BT_UUID_EXPAND(uuid16) }; + } else if (att_uuid->type == ATT_UUID_128) { + *uuid = UuidMakeFromLEBytes(att_uuid->uuid128); + } else { // should never happen + PBL_ASSERTN(0); + } +} + +T_STATIC HcProtocolDiscoveryServiceFoundPayload *prv_gatt_client_discovery_build_gatt_service( + const ble_evt_gattc_browse_svc_t *svc, uint32_t *payload_size) { + uint8_t type_counts[GATTC_ITEM_NUM_TYPES] = { }; + for (int i = 0; i < svc->num_items; i++) { + const gattc_item_t *item = &svc->items[i]; + if (item->type >= GATTC_ITEM_NUM_TYPES) { + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected gattc type: 0x%x!", (int)item->type); + } + type_counts[item->type]++; + } + + uint32_t blob_size = COMPUTE_GATTSERVICE_SIZE_BYTES( + type_counts[GATTC_ITEM_TYPE_CHARACTERISTIC], type_counts[GATTC_ITEM_TYPE_DESCRIPTOR], + type_counts[GATTC_ITEM_TYPE_INCLUDE]); + + uint32_t payload_extra_size = + sizeof(HcProtocolDiscoveryServiceFoundPayload) - sizeof(GATTService); + *payload_size = payload_extra_size + blob_size; + HcProtocolDiscoveryServiceFoundPayload *payload = kernel_zalloc_check(*payload_size); + + GATTService *service = &payload->service; + + uint16_t base_handle = svc->start_h; + + *service = (GATTService) { + .size_bytes = blob_size, + .att_handle = base_handle, + .num_characteristics = type_counts[GATTC_ITEM_TYPE_CHARACTERISTIC], + .num_descriptors = type_counts[GATTC_ITEM_TYPE_DESCRIPTOR], + .num_att_handles_included_services = type_counts[GATTC_ITEM_TYPE_INCLUDE], + }; + + prv_att_uuid_to_uuid(&svc->uuid, &service->uuid); + + char uuid_str[UUID_STRING_BUFFER_LENGTH]; + uuid_to_string(&service->uuid, uuid_str); + PBL_LOG(LOG_LEVEL_DEBUG, + "Found Service %s Handle: 0x%"PRIx16": 0x%"PRIx16" (%"PRIu32" byte blob)", + uuid_str, base_handle, svc->end_h, blob_size); + + uint16_t *att_handles_included_services = + (uint16_t *)((uint8_t *)service + blob_size - + sizeof(uint16_t) * type_counts[GATTC_ITEM_TYPE_INCLUDE]); + uint16_t att_idx = 0; + + GATTCharacteristic *char_dest = NULL; + uint8_t *end_ptr = (uint8_t *)service->characteristics; + + // A few notes: + // + We expect the descriptors to be part of the last discovered characteristic + // + We assume Includes can show up anywhere + // + If we find descriptors before any characteristic is found, we bail (it shouldn't happen) + for (int i = 0; i < svc->num_items; i++) { + const gattc_item_t *item = &svc->items[i]; + if (item->type == GATTC_ITEM_TYPE_CHARACTERISTIC) { + char_dest = (GATTCharacteristic *)end_ptr; + *char_dest = (GATTCharacteristic) { + .att_handle_offset = (item->c.value_handle - base_handle), + .properties = item->c.properties, + }; + prv_att_uuid_to_uuid(&item->uuid, &char_dest->uuid); + end_ptr += sizeof(GATTCharacteristic); + + uuid_to_string(&char_dest->uuid, uuid_str); + PBL_LOG(LOG_LEVEL_DEBUG, + " Found Characteristic %s Handle 0x%"PRIx16, uuid_str, item->c.value_handle); + } else if (item->type == GATTC_ITEM_TYPE_DESCRIPTOR) { + if (char_dest == NULL) { + PBL_LOG(LOG_LEVEL_ERROR, "No characteristic found for descriptor! handle=0x%x", + (int)item->handle); + goto failure; + } + + GATTDescriptor *desc_dest = (GATTDescriptor *)end_ptr; + *desc_dest = (GATTDescriptor) { + .att_handle_offset = item->handle - base_handle, + }; + prv_att_uuid_to_uuid(&item->uuid, &desc_dest->uuid); + char_dest->num_descriptors++; + end_ptr += sizeof(GATTDescriptor); + + uuid_to_string(&desc_dest->uuid, uuid_str); + PBL_LOG(LOG_LEVEL_DEBUG, + " Found Descriptor %s Handle 0x%x", uuid_str, item->handle); + + } else if (item->type == GATTC_ITEM_TYPE_INCLUDE) { + att_handles_included_services[att_idx] = item->i.start_h; + att_idx++; + + PBL_LOG(LOG_LEVEL_DEBUG, "Found Include, Start Handle 0x%x", item->i.start_h); + } + } + + return payload; +failure: + kernel_free(payload); + return NULL; +} + +static void prv_write_cccd_cb(const ble_evt_gattc_write_completed_t *evt) { + if (evt->status != ATT_ERROR_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to write CCCD for idx %d Handle 0x%04X", + evt->conn_idx, evt->handle); + } +} + +//! Inspects the GATT service discovery event in search for the GATT Profile Service and its +//! Service Changed characteristic. In case it's found, its ATT handle is recorded in the +//! GAPLEConnection structure (by passing it up to the module. The characteristic's indications +//! are subscribed to as well. +static void prv_search_service_changed_handle(Connection *connection, + const ble_evt_gattc_browse_svc_t *evt) { + + // Check whether the service is the "GATT Profile Service": + att_uuid_t gatt_service_uuid; + ble_uuid_create16(GATT_SERVICE_UUID, &gatt_service_uuid); + + att_uuid_t service_changed_characteristic_uuid; + ble_uuid_create16(GATT_SERVICE_CHANGED_CHARACTERISTIC_UUID, &service_changed_characteristic_uuid); + + // Attempt to find the "Service Changed" characteristic: + for (uint16_t i = 0; i < evt->num_items; ++i) { + const gattc_item_t *characteristic_info = &evt->items[i]; + + // Sanity: make sure this is a characteristic + if (characteristic_info->type != GATTC_ITEM_TYPE_CHARACTERISTIC) { + continue; + } + + if (!ble_uuid_equal(&characteristic_info->uuid, &service_changed_characteristic_uuid)) { + continue; + } + + // Found the Service Changed characteristic! + att_uuid_t cccd_uuid; + ble_uuid_create16(GATT_CCCD_UUID, &cccd_uuid); + bool cccd_found = false; + + // Attempt to find the CCCD: + for (uint16_t j = 0; j < evt->num_items; ++j) { + const gattc_item_t *descriptor_info = &evt->items[j]; + + // Sanity: make sure this is a descriptor + if (descriptor_info->type != GATTC_ITEM_TYPE_DESCRIPTOR) { + continue; + } + + if (!ble_uuid_equal(&cccd_uuid, &descriptor_info->uuid)) { + continue; + } + + // Found the CCCD! + + // ... and finally subscribe to indications: + const uint16_t cccd_value = GATT_CCC_INDICATIONS; + ble_error_t result = gatt_wrapper_write(evt->conn_idx, descriptor_info->handle, + sizeof(cccd_value), (const uint8_t *)&cccd_value, + (uintptr_t)prv_write_cccd_cb, + GattReqSourceController); + if (result != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to write CCCD %d", result); + } + cccd_found = true; + break; + } + + if (!cccd_found) { + // gah, Android doesn't seem to create the CCCD, but it does seem to auto-subscribe any + // bonded device so let's assume this is what happen and notify the main firmware that we are + // subscribed. + PBL_LOG(LOG_LEVEL_DEBUG, "No cccd found for service changed characteristic, assuming we are " + "auto-subscribed"); + } + + // We've got everything we need, record the characteristic value handle, so we can filter + // out the Service Changed indications later on: + BTDeviceInternal address; + connection_get_address(connection, &address); + + hc_endpoint_discovery_service_changed_handle(&address, characteristic_info->c.value_handle); + } +} + +void gatt_client_discovery_process_service(const ble_evt_gattc_browse_svc_t *service) { + uint32_t payload_size; + HcProtocolDiscoveryServiceFoundPayload *payload = + prv_gatt_client_discovery_build_gatt_service(service, &payload_size); + if (!payload) { + return; + } + Connection *connection = connection_by_idx_check(service->conn_idx); + connection_get_address(connection, &payload->address); + hc_endpoint_discovery_send_service_found(payload, payload_size); + + const Uuid ppogatt_uuid = + (const Uuid){PEBBLE_BT_UUID_EXPAND(PEBBLE_BT_PPOGATT_SERVICE_UUID_32BIT)}; + if (uuid_equal(&payload->service.uuid, &ppogatt_uuid)) { + PBL_LOG(LOG_LEVEL_DEBUG, "PPoGATT found"); + ppogatt_emulated_notify_phone_ppogatt_server_found(connection); + } + + kernel_free(payload); + + prv_search_service_changed_handle(connection, service); +} + +void gatt_client_discovery_handle_complete(const ble_evt_gattc_browse_completed_t *complete_event) { + ppogatt_inject_emulated_ppogatt_service_if_needed(complete_event->conn_idx); + + PBL_LOG(LOG_LEVEL_DEBUG, "Gatt Service Discovery Complete: %d", complete_event->status); + BTDeviceInternal addr; + Connection *connection = connection_by_idx_check(complete_event->conn_idx); + + connection_get_address(connection, &addr); + HciStatusCode status = HciStatusCode_Success; + if (complete_event->status != GAP_ERR_NO_ERROR) { + if (complete_event->status == GAP_ERR_INSUFF_RESOURCES) { + // Due to a bug in the Dialog ROM (PBL-35827) we may get this error. We + // assume all the discovery of services up to this point was successful, + // so return "Success" to the Host + PBL_LOG(LOG_LEVEL_WARNING, "GATT discovery failed due to OOM, " + "considering discovery up to last handle a success"); + } else { + PBL_LOG(LOG_LEVEL_ERROR, "GATT discovery failed, %d\n", complete_event->status); + status = HciStatusCode_VS_Base + complete_event->status; + } + } + + hc_endpoint_discovery_complete(&addr, status); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/gatt_local_services.c b/src/bluetooth-fw/da1468x/controller/main/src/gatt_local_services.c new file mode 100644 index 00000000..0a58825b --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/gatt_local_services.c @@ -0,0 +1,190 @@ +/* + * 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. + */ + +#include "gatt_local_services.h" + +#include "dis_impl.h" +#include "hrm_impl.h" +#include "pebble_pairing_service_impl.h" +#include "ppogatt_emulated_server_wa.h" + +#include "system/logging.h" + +// Dialog SDK: +#include "attm_db.h" +#include "ble_common.h" +#include "ble_gap.h" +#include "ble_mgr.h" +#include "gapm_task.h" + +#include + +#include + +// We'd like to avoid changing the ATT table between FW updates if we can. The implementation of +// the "Service Changed" GATT feature has been buggy on iOS in the past and in the Dialog BT driver +// lib side we still have to implement PBL-35626. +// +// The ATT table currently looks like this (R=Read, W=Write, I=Indicatable, N=Notifiable): +// +// 1 - Service: Generic Access Profile Service (UUID: 0x1800) +// 2 - Characteristic (R): Device Name +// 3 - Value +// 4 - Characteristic (R): Appearance +// 5 - Value +// 6 - Characteristic (R): Preferred Peripheral Connection Parameters +// 7 - Value +// +// 8 - Service: Generic Attribute Profile Service (UUID: 0x1801) +// 9 - Characteristic (RI): Service Changed +// 10 - Value +// 11 - CCCD +// +// 12 - Service: Device Information Service (UUID: 0x180A) +// 13 - Characteristic (R): Manufacturer Name +// 14 - Value +// 15 - Characteristic (R): Model Number +// 16 - Value +// 17 - Characteristic (R): Serial Number +// 18 - Value +// 19 - Characteristic (R): Firmware Revision +// 20 - Value +// 21 - Characteristic (R): Software Revision +// 22 - Value +// +// 23 - Service: Pebble Pairing Service (UUID: 0xFED9) -- Documentation here: http://pbl.io/gatt +// 24 - Characteristic (RN): Connectivity Status +// 25 - Value +// 26 - CCCD +// 27 - Characteristic (R): Trigger Pairing +// 28 - Value +// 29 - Characteristic (RWN): GATT MTU +// 30 - Value +// 31 - CCCD +// 32 - Characteristic (RWN): Connection Parameters +// 33 - Value +// 34 - CCCD +// +// 35 - Service: Heart Rate Monitor (UUID: 0x180D) +// 36 - Characteristic (N): Heart Rate Measurement (UUID: 0x2A37) +// 37 - Value +// 38 - CCCD +// 39 - Characteristic (R): Sensor Location (UUID: 0x2A38) +// 40 - Value +// 41 - Characteristic (W): Heart Rate Control Point (UUID: 0x2A39) +// 42 - Value +// +// 57344 (0xE000) - Service: PPoGATT Work-around Service (UUID: TBD) + +static bool s_has_registered = false; +static PebbleMutex *s_svc_register_mutex; + +void gatt_local_services_init(const BTDriverConfig *config) { + s_svc_register_mutex = mutex_create(); + device_information_service_init(&config->dis_info); + pebble_pairing_service_init(); + hrm_service_init(config->is_hrm_supported_and_enabled); + ppogatt_service_init(); +} + +static void prv_gatt_local_services_register(void) { + device_information_service_register(DEVICE_INFORMATION_SERVICE_EXPECTED_ATT_STARTING_HANDLE); + pebble_pairing_service_register(PEBBLE_PAIRING_SERVICE_EXPECTED_ATT_STARTING_HANDLE); + hrm_service_register(HRM_SERVICE_EXPECTED_ATT_STARTING_HANDLE); + ppogatt_service_register(PEBBLE_PPOGATT_SERVICE_EXPECTED_ATT_STARTING_HANDLE); + + s_has_registered = true; +} + +//! @note this will be called multiple times! +void gatt_local_services_register(void) { + mutex_lock(s_svc_register_mutex); + prv_gatt_local_services_register(); + mutex_unlock(s_svc_register_mutex); +} + +void gatt_local_services_re_register_if_needed(void) { + if (!s_has_registered) { + // Avoid registering at this time, gatt_local_services_register() hasn't been called before. + return; + } + mutex_lock(s_svc_register_mutex); + + // Under the hood all the API calls below can invoke the GAPM_SET_DEV_CONFIG_CMD. Within the RW + // stack this will often result in an attmdb_destroy() call which nukes the internal attribute + // database. It's hard to detect this happening aside from inspecting code paths (even an rv of + // success or failure does not tell you whether or not a flush occurred. To work around this we + // issue a call into a RW ROM function to see if our handle still exists. There isn't any locking + // around this function but AFAICT only API calls made by this module would wind up updating this + // internal list so I think its safe to call + if (attmdb_get_service(PEBBLE_PAIRING_SERVICE_EXPECTED_ATT_STARTING_HANDLE) == NULL) { + PBL_LOG(LOG_LEVEL_DEBUG, "Service flush detected, re-registering ..."); + prv_gatt_local_services_register(); + } + + mutex_unlock(s_svc_register_mutex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// The functions below are wrappers for API calls that are known to clear out the ROM stack's +// ATT table. All the wrappers do is call the original API and then re-registering our services. + +extern ble_error_t __real_ble_gap_address_set(const own_address_t *address, uint16_t renew_dur); +ble_error_t __wrap_ble_gap_address_set(const own_address_t *address, uint16_t renew_dur) { + ble_error_t rv = __real_ble_gap_address_set(address, renew_dur); + gatt_local_services_re_register_if_needed(); + return rv; +} + +extern ble_error_t __real_ble_gap_device_name_set(const char *name, att_perm_t perm); +ble_error_t __wrap_ble_gap_device_name_set(const char *name, att_perm_t perm) { + // Note: in spite of what the docstring says, the ATT database does not seem to get flushed by + // calling ble_gap_device_name_set(), unless the write permissions have changed. We catch this + // with our checks in gatt_local_services_re_register_if_needed + + ble_error_t rv = __real_ble_gap_device_name_set(name, perm); + gatt_local_services_re_register_if_needed(); + + return rv; +} + +extern ble_error_t __real_ble_gap_appearance_set(gap_appearance_t appearance, att_perm_t perm); +ble_error_t __wrap_ble_gap_appearance_set(gap_appearance_t appearance, att_perm_t perm) { + ble_error_t rv = __real_ble_gap_appearance_set(appearance, perm); + gatt_local_services_re_register_if_needed(); + return rv; +} + +extern ble_error_t __real_ble_gap_per_pref_conn_params_set(const gap_conn_params_t *conn_params); +ble_error_t __wrap_ble_gap_per_pref_conn_params_set(const gap_conn_params_t *conn_params) { + ble_error_t rv = __real_ble_gap_per_pref_conn_params_set(conn_params); + gatt_local_services_re_register_if_needed(); + return rv; +} + +extern ble_error_t __real_ble_gap_role_set(const gap_role_t role); +ble_error_t __wrap_ble_gap_role_set(const gap_role_t role) { + ble_error_t rv = __real_ble_gap_role_set(role); + gatt_local_services_re_register_if_needed(); + return rv; +} + +extern ble_error_t __real_ble_gap_mtu_size_set(uint16_t mtu_size); +ble_error_t __wrap_ble_gap_mtu_size_set(uint16_t mtu_size) { + ble_error_t rv = __real_ble_gap_mtu_size_set(mtu_size); + gatt_local_services_re_register_if_needed(); + return rv; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/gatt_wrapper.c b/src/bluetooth-fw/da1468x/controller/main/src/gatt_wrapper.c new file mode 100644 index 00000000..7dcd9dcc --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/gatt_wrapper.c @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#include "gatt_wrapper.h" +#include "gatt_wrapper_types.h" + +#include "connection.h" +#include "hc_protocol/hc_endpoint_gatt.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" +#include "system/passert.h" + +#include "ble_gattc.h" + +#include "rwble_hl_error.h" + +#include +#include + +#include +#include +#include + +static GattRespDest prv_dest_from_source(GattReqSource source) { + return (source == GattReqSourceHost) ? GattRespDestHost : GattRespDestController; +} + +static bool prv_enqueue( + uint16_t conn_idx, uintptr_t context_ref, GattRespDest resp_dest, GattOpType op_type) { + Connection *conn = connection_by_idx(conn_idx); + if (!conn) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to find connection during enqueue attempt"); + return false; + } + connection_enqueue_gatt_op(conn, context_ref, resp_dest, op_type); + return true; +} + +static bool prv_pop_errored_op(uint16_t conn_idx) { + Connection *conn = connection_by_idx(conn_idx); + if (!conn) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to find connection during queue pop attempt"); + return false; + } + connection_pop_gatt_op(conn); + return true; +} + +static void prv_pop_if_failing_rv(uint16_t conn_idx, ble_error_t rv) { + if (rv != BLE_STATUS_OK) { + prv_pop_errored_op(conn_idx); + } +} + +ble_error_t gatt_wrapper_read(uint16_t conn_idx, uint16_t handle, uintptr_t context_ref, + GattReqSource source) { + GATT_LOG_DEBUG("gatt_wrapper_read: handle: %d, context_ref: %d, source: %d", + handle, context_ref, source); + + const GattRespDest resp_dest = prv_dest_from_source(source); + if (!prv_enqueue(conn_idx, context_ref, resp_dest, GattOpType_Read)) { + return BLE_ERROR_NOT_CONNECTED; + } + ble_error_t rv = ble_gattc_read(conn_idx, handle, 0); + prv_pop_if_failing_rv(conn_idx, rv); + return rv; +} + +ble_error_t gatt_wrapper_read_by_uuid(uint16_t conn_idx, uint16_t start_h, uint16_t end_h, + const att_uuid_t *uuid, uintptr_t context_ref, + GattReqSource source) { + GATT_LOG_DEBUG("gatt_wrapper_read_uuid: context_ref: %d, source: %d", context_ref, source); + + const GattRespDest resp_dest = prv_dest_from_source(source); + if (!prv_enqueue(conn_idx, context_ref, resp_dest, GattOpType_Read)) { + return BLE_ERROR_NOT_CONNECTED; + } + ble_error_t rv = ble_gattc_read_by_uuid(conn_idx, start_h, end_h, uuid); + prv_pop_if_failing_rv(conn_idx, rv); + return rv; +} + + +ble_error_t gatt_wrapper_write(uint16_t conn_idx, uint16_t handle, uint16_t length, + const uint8_t *value, uintptr_t context_ref, GattReqSource source) { + GATT_LOG_DEBUG("gatt_wrapper_write: handle: %d, context_ref: %d, source: %d", + handle, context_ref, source); + + const GattRespDest resp_dest = prv_dest_from_source(source); + if (!prv_enqueue(conn_idx, context_ref, resp_dest, GattOpType_Write)) { + return BLE_ERROR_NOT_CONNECTED; + } + ble_error_t rv = ble_gattc_write(conn_idx, handle, 0, length, value); + prv_pop_if_failing_rv(conn_idx, rv); + return rv; +} + +ble_error_t gatt_wrapper_write_no_resp(uint16_t conn_idx, uint16_t handle, uint16_t length, + const uint8_t *value) { + GATT_LOG_DEBUG("gatt_wrapper_write_no_resp: handle: %d", handle); + + const GattRespDest resp_dest = GattRespDestNone; + if (!prv_enqueue(conn_idx, 0, resp_dest, GattOpType_Write)) { + return BLE_ERROR_NOT_CONNECTED; + } + const bool signed_write = false; + ble_error_t rv = ble_gattc_write_no_resp(conn_idx, handle, signed_write, length, value); + prv_pop_if_failing_rv(conn_idx, rv); + return rv; +} + +static GattRespDest prv_handle_gatt_event(uint16_t conn_idx, uint16_t handle, + BTDeviceInternal *addr, uintptr_t *context_ref, + BLEGATTError status, GattOpType expected_type) { + // This used to be `connection_by_idx_check`, but due to PBL-36813, needed to change it. + // The error that comes back in the `evt` is `GAP_ERR_COMMAND_DISALLOWED: 0x43`, but to make + // sure we don't assert on any other future errors, we check for the connection. + Connection *conn = connection_by_idx(conn_idx); + if (!conn) { + return GattRespDestNone; + } + GattRespDest resp_dest; + + if (!connection_dequeue_gatt_op(conn, context_ref, &resp_dest, expected_type)) { + PBL_LOG(LOG_LEVEL_ALWAYS, "No gatt op to dequeue, status %d, hdl: %d", status, (int)handle); + // FIXME: I think this happens if we reconnect too fast. The log above will get flushed to the + // main MCU before the crash and should provide some extra context. I believe this state is + // captured by GAP_ERR_COMMAND_DISALLOWED but let's assert if we catch another scenario + PBL_ASSERTN(status == (int)GAP_ERR_COMMAND_DISALLOWED); + return GattRespDestNone; + } + connection_get_address(conn, addr); + return resp_dest; +} + +void gatt_wrapper_handle_read_completed(const ble_evt_gattc_read_completed_t *evt) { + BTDeviceInternal addr; + uintptr_t context_ref; + GATT_LOG_DEBUG("gatt_wrapper_handle_read_completed: handle: %d, status: %d", + evt->handle, evt->status); + const BLEGATTError status = (BLEGATTError)evt->status; + GattRespDest resp_dest = prv_handle_gatt_event(evt->conn_idx, evt->handle, &addr, &context_ref, + status, GattOpType_Read); + + + switch (resp_dest) { + case GattRespDestHost: + hc_endpoint_gatt_send_read_complete(&addr, evt->handle, status, evt->length, + &evt->value[0] + evt->offset, context_ref); + break; + case GattRespDestController: + PBL_ASSERTN(context_ref); + ((gatt_wrapper_read_cb) context_ref)(evt); + break; + default: + break; + } +} + +void gatt_wrapper_handle_write_completed(const ble_evt_gattc_write_completed_t *evt) { + BTDeviceInternal addr; + uintptr_t context_ref; + GATT_LOG_DEBUG("gatt_wrapper_handle_write_completed: handle: %d, status: %d", + evt->handle, evt->status); + const BLEGATTError status = (BLEGATTError)evt->status; + GattRespDest resp_dest = prv_handle_gatt_event(evt->conn_idx, evt->handle, &addr, &context_ref, + status, GattOpType_Write); + + switch (resp_dest) { + case GattRespDestHost: + hc_endpoint_gatt_send_write_complete(&addr, evt->handle, status, context_ref); + break; + case GattRespDestController: + PBL_ASSERTN(context_ref); + ((gatt_wrapper_write_cb) context_ref)(evt); + break; + default: + break; + } +} + +void gatt_wrapper_handle_notification(const ble_evt_gattc_notification_t *evt) { + BTDeviceInternal addr; + Connection *conn = connection_by_idx_check(evt->conn_idx); + connection_get_address(conn, &addr); + hc_endpoint_gatt_send_notification(&addr, evt->handle, evt->length, evt->value); +} + +void gatt_wrapper_handle_indication(const ble_evt_gattc_indication_t *evt) { + BTDeviceInternal addr; + Connection *conn = connection_by_idx_check(evt->conn_idx); + connection_get_address(conn, &addr); + hc_endpoint_gatt_send_indication(&addr, evt->handle, evt->length, evt->value); + + // GATT Indications are already automatically confirmed by the Dialog SDK. + // See comments with ble_gattc_indication_cfm(). +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_advert.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_advert.c new file mode 100644 index 00000000..d82077aa --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_advert.c @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_advert.h" + +#include "advert.h" +#include "system/logging.h" + +// Dialog SDK: +#include "ble_common.h" + +#include + +#include +#include + +static void prv_send_response(const HcProtocolMessage *msg, ble_error_t err) { + hc_protocol_enqueue_response(msg, (uint8_t *)&err, sizeof(ble_error_t)); +} + +static void prv_handle_advert_enable(const HcProtocolMessage *msg) { + HcAdvertEnableData *data = (HcAdvertEnableData *)msg->payload; + + // One slot is 625us: + const uint16_t min_slots = data->min_interval_ms * 8 / 5; + const uint16_t max_slots = data->max_interval_ms * 8 / 5; + +// PBL_LOG(LOG_LEVEL_DEBUG, "Advert; Setting min/max interval (ms) to %"PRIu16" / %"PRIu16, +// data->min_interval_ms, data->max_interval_ms); + + HcAdvertEnableResponseData response_data = {}; + response_data.error = advert_set_interval(min_slots, max_slots); + if (response_data.error == BLE_STATUS_OK) { + response_data.current_state = advert_enable(); + } + hc_protocol_enqueue_response(msg, (uint8_t *)&response_data, sizeof(response_data)); +} + +static void prv_handle_advert_disable(const HcProtocolMessage *msg) { + advert_disable(); + prv_send_response(msg, BLE_STATUS_OK); +} + +static void prv_handle_advert_set_adv_data(const HcProtocolMessage *msg) { + advert_set_data((const BLEAdData *)&msg->payload[0]); + prv_send_response(msg, BLE_STATUS_OK); +} + +void hc_endpoint_advert_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Advert_Enable: + prv_handle_advert_enable(msg); + break; + case HcMessageID_Advert_Disable: + prv_handle_advert_disable(msg); + break; + case HcMessageID_Advert_SetAdvData: + prv_handle_advert_set_adv_data(msg); + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcAdvert: unhandled message id: %d", msg->command_id); + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_analytics.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_analytics.c new file mode 100644 index 00000000..67c92b16 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_analytics.c @@ -0,0 +1,180 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_analytics.h" +#include "host_transport_protocol.h" + +#include "connection.h" +#include "dialog_analytics/analytics.h" +#include "host_transport.h" +#include "kernel/pbl_malloc.h" +#include "reboot_reason.h" +#include "system/hexdump.h" +#include "system/logging.h" + +// Dialog SDK: +#include "ble_common.h" +#include "ble_gap.h" + +#include +#include + +#include +#include + +void slave_window_stats_collect(SlaveConnEventStats *stats); + +static void prv_handle_collect_ble_parameters(const HcProtocolMessage *msg) { + uint64_t channel_map_uint; + + const ble_error_t e = ble_gap_channel_map_get(&channel_map_uint); + const bool success = (e == BLE_STATUS_OK); + + LEChannelMap channel_map = {}; + if (success) { + memcpy(&channel_map, &channel_map_uint, sizeof(channel_map)); + } else { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_channel_map_get err: %x", e); + } + + HcAnalyticsCollectBleParameters data = { + .success = success, + .le_chan_map_res = channel_map, + }; + + hc_protocol_enqueue_response(msg, (uint8_t *)&data, sizeof(data)); +} + +static void prv_handle_get_connection_quality(const HcProtocolMessage *msg) { + HcAnalyticsGetConnectionQuality data = {}; + + const BTDeviceInternal *device = (BTDeviceInternal *)&msg->payload[0]; + Connection *conn = connection_by_address(device); + if (!conn) { + data.success = false; + goto done; + } + + const uint16_t conn_idx = connection_get_idx(conn); + int8_t conn_rssi; + const ble_error_t e = ble_gap_conn_rssi_get(conn_idx, &conn_rssi); + const bool success = (e == BLE_STATUS_OK); + if (!success) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_conn_rssi_get err: %x", e); + conn_rssi = 0; + } + + data = (HcAnalyticsGetConnectionQuality) { + .success = success, + .rssi = conn_rssi, + }; + +done: + hc_protocol_enqueue_response(msg, (uint8_t *)&data, sizeof(data)); +} + +#if 0 // FIXME with PBL-38365 +static void prv_analytic_each_cb(DialogAnalyticsMetric metric, uint32_t value, void *context) { + HcAnalyticsHeartbeatData *data = context; + data->analytics[metric].metric = cmetric; + data->analytics[metric].value = value; +} + +#define HEARTBEAT_SIZE (sizeof(HcAnalyticsHeartbeatData) + \ + (DialogAnalyticMetric_Count * sizeof(SerializedAnalytic))) + +_Static_assert(HEARTBEAT_SIZE < HOST_TRANSPORT_CTLR_TX_BUFFER_SIZE && + HEARTBEAT_SIZE < HOST_TRANSPORT_HOST_RX_BUFFER_SIZE, + "Heartbeat Size must be less than HOST_TRANSPORT_TX_BUFFER_SIZE"); + +static void prv_handle_get_heartbeat_data(const HcProtocolMessage *msg) { + uint32_t data_size = HEARTBEAT_SIZE; + HcAnalyticsHeartbeatData *data = kernel_zalloc(data_size); + + if (!data) { + PBL_LOG(LOG_LEVEL_ALWAYS, "Not enough resources to allocate analytics heartbeat"); + data_size = 0; + goto done; + } + + data->count = DialogAnalyticMetric_Count; + analytics_each(prv_analytic_each_cb, data); + + analytics_reset_nodes(); + +done: + hc_protocol_enqueue_response(msg, (uint8_t *)data, data_size); + kernel_free(data); +} +#endif + +static void prv_handle_get_connection_event_stats(const HcProtocolMessage *msg) { + SlaveConnEventStats stats; + slave_window_stats_collect(&stats); + hc_protocol_enqueue_response(msg, (uint8_t *)&stats, sizeof(stats)); +} + +//! This symbol and its contents are provided by the linker script, see the +//! .note.gnu.build-id section in src/fw/stm32f2xx_flash_fw.ld +extern const ElfExternalNote DIALOG_BUILD_ID; + +void hc_endpoint_analytics_send_reboot_info(void) { + const RebootReasonCode reason = reboot_reason_get_last_reboot_reason(); + if (reason == RebootReasonCode_Shutdown || reason == RebootReasonCode_Unknown) { + // Don't send the reboot reason to analytics for insignificant data. + return; + } + + HcAnalyticsRebootInfo info = { + .last_crash_lr = reboot_reason_get_crash_lr(), + .reboot_reason_code = reason, + }; + memcpy(info.build_id, &DIALOG_BUILD_ID.data[DIALOG_BUILD_ID.name_length], BUILD_ID_EXPECTED_LEN); + + hc_protocol_enqueue_with_payload(HcEndpointID_Analytics, HcMessageID_Analytics_LogRebootInfo, + (uint8_t *)&info, sizeof(info)); +} + +void hc_endpoint_analytics_log_mic_error_detected(uint32_t num_subsequent_mic_errors) { + HcAnalyticsLogBleMicErrorEvent info = { + .num_subsequent_mic_errors = num_subsequent_mic_errors, + }; + + hc_protocol_enqueue_with_payload( + HcEndpointID_Analytics, HcMessageID_Analytics_LogBleMicErrorEvent, (uint8_t *)&info, + sizeof(info)); +} + +void hc_endpoint_analytics_ctlr_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Analytics_CollectBLEParameters: + prv_handle_collect_ble_parameters(msg); + break; + case HcMessageID_Analytics_GetConnectionQuality: + prv_handle_get_connection_quality(msg); + break; +#if 0 // Enable with PBL-38365 + case HcMessageID_Analytics_GetHeartbeatData: + prv_handle_get_heartbeat_data(msg); + break; +#endif + case HcMessageID_Analytics_GetConnEventStats: + prv_handle_get_connection_event_stats(msg); + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcAnalytics: unhandled message id: %d", msg->command_id); + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_chip_id.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_chip_id.c new file mode 100644 index 00000000..1e85bc70 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_chip_id.c @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_chip_id.h" + +#include "chip_id.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include + +static void prv_handle_chip_info_request(const HcProtocolMessage *request) { + const uint8_t *response_payload = NULL; + DialogChipID chip_id; + if (dialog_chip_id_copy(&chip_id)) { + response_payload = (const uint8_t *)&chip_id; + } + // Send back empty response in case of failure. + hc_protocol_enqueue_response(request, response_payload, response_payload ? sizeof(chip_id) : 0); +} + +void hc_endpoint_chip_id_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Id_ChipInfo: + prv_handle_chip_info_request(msg); + break; + + default: + PBL_LOG(LOG_LEVEL_ERROR, "Unknown cmd ID: 0x%"PRIx8, msg->command_id); + break; + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_ctl.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_ctl.c new file mode 100644 index 00000000..d7a262a5 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_ctl.c @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_ctl.h" + +#include "ble_task.h" +#include "power.h" +#include "reboot_reason.h" +#include "system/logging.h" +#include "tasks.h" + +#include + +#include + +static void prv_handle_init(const HcProtocolMessage *request) { + PBL_LOG(LOG_LEVEL_DEBUG, "Got init message!"); + + const BTDriverConfig *config = (const BTDriverConfig *)request->payload; + ble_task_init(config); + + hc_protocol_enqueue_response(request, NULL, 0); +} + +static void prv_handle_shutdown(const HcProtocolMessage *request) { +#if 0 // Fixme once some space is really freed up + // Dump some statistics on stack usage before we shutdown + tasks_dump_free_space(); +#endif + + PBL_LOG(LOG_LEVEL_DEBUG, "Got shutdown message! Going to fall into a deep sleep..."); + + // Set the reboot reason to signify we shut down gracefully + RebootReason reason = { + .code = RebootReasonCode_Shutdown, + }; + // FIXME PBL-38181: For some reason, the reboot reason shutdown never gets persisted/recovered + reboot_reason_set(&reason); + + // Send empty response as acknowledgement: + hc_protocol_enqueue_response(request, NULL, 0); + + power_enter_hibernation(); +} + +void hc_endpoint_ctl_handler(const HcProtocolMessage *request) { + switch (request->command_id) { + case HcMessageID_Ctl_Init: + prv_handle_init(request); + break; + + case HcMessageID_Ctl_Shutdown: + prv_handle_shutdown(request); + break; + + default: + PBL_LOG(LOG_LEVEL_ERROR, "Unknown command 0x%"PRIx8, request->command_id); + break; + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_discovery.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_discovery.c new file mode 100644 index 00000000..a8e1909d --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_discovery.c @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_discovery.h" + +#include "connection.h" +#include "hc_protocol/hc_protocol.h" +#include "system/logging.h" + +#include +#include + +// Dialog APIs +#include "ble_common.h" +#include "ble_gattc.h" + +void hc_endpoint_discovery_complete(const BTDeviceInternal *address, HciStatusCode status) { + HcProtocolDiscoveryCompletePayload payload = { + .address = *address, + .status = status + }; + hc_protocol_enqueue_with_payload(HcEndpointID_Discovery, HcMessageID_Discovery_Complete, + (uint8_t *)&payload, sizeof(payload)); +} + +static void prv_handle_discovery_start(const HcProtocolDiscoveryStartPayload *req) { + PBL_LOG(LOG_LEVEL_DEBUG, "->" BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE(req->address.address)); + Connection *conn = connection_by_address(&req->address); + HciStatusCode status = HciStatusCode_Success; + if (conn == NULL) { + status = HciStatusCode_UnknownConnectionIdentifier; + PBL_LOG(LOG_LEVEL_WARNING, "No connection to addr!"); + goto failure; + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Received Discovery Start for 0x%x to 0x%x", + (int)req->range.start, (int)req->range.end); + + uint16_t conn_idx = connection_get_idx(conn); + ble_error_t e = ble_gattc_browse(conn_idx, NULL, req->range.start, req->range.end); + if (e != BLE_STATUS_OK) { + status = HciStatusCode_VS_Base + e; + PBL_LOG(LOG_LEVEL_DEBUG, "ble_gattc_browse: %u", e); + goto failure; + } + return; +failure: + // Notify Host that discovery terminated unexpectedly + hc_endpoint_discovery_complete(&req->address, status); +} + +void hc_endpoint_discovery_service_changed_handle(const BTDeviceInternal *address, + uint16_t handle) { + PBL_LOG(LOG_LEVEL_DEBUG, "Gatt Service Discovery Service Changed Handle: %d", handle); + + HcProtocolDiscoveryServiceChangedHandlePayload payload = { + .address = *address, + .handle = handle + }; + hc_protocol_enqueue_with_payload(HcEndpointID_Discovery, + HcMessageID_Discovery_Service_Changed_Handle, + (uint8_t *)&payload, sizeof(payload)); +} + +static void prv_handle_discovery_stop(const BTDeviceInternal *address) { + Connection *conn = connection_by_address(address); + + if (conn == NULL) { + return; + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Received Discovery Stop Request"); + + uint16_t conn_idx = connection_get_idx(conn); + ble_error_t e = ble_gattc_discover_cancel(conn_idx); + + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_DEBUG, "ble_gattc_discover_cancel: %u", e); + } +} + +void hc_endpoint_discovery_send_service_found( + const HcProtocolDiscoveryServiceFoundPayload *payload, uint32_t payload_size) { + // Note: kind of wasteful double copy here, a service node could get sort of + // big (several hundred bytes) + hc_protocol_enqueue_with_payload(HcEndpointID_Discovery, HcMessageID_Discovery_Service_Found, + (uint8_t *)payload, payload_size); +} + +void hc_endpoint_discovery_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Discovery_Start: + prv_handle_discovery_start((HcProtocolDiscoveryStartPayload *)msg->payload); + break; + case HcMessageID_Discovery_Stop: + prv_handle_discovery_stop((BTDeviceInternal *)msg->payload); + break; + default: + PBL_LOG(LOG_LEVEL_WARNING, "Unexpected command: 0x%x", (int)msg->command_id); + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gap_le_connect.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gap_le_connect.c new file mode 100644 index 00000000..32b62cf8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gap_le_connect.c @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gap_le_connect.h" + +#include "ble_common.h" +#include "ble_gap.h" + +#include +#include "connection.h" +#include "system/logging.h" + +#include +#include +#include +#include + + +void hc_endpoint_gap_le_connect_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_GapLEConnect_Disconnect: { + ble_error_t err = BLE_ERROR_NOT_CONNECTED; + Connection *connection = connection_by_address((const BTDeviceInternal *)&msg->payload[0]); + if (connection) { + err = ble_gap_disconnect(connection_get_idx(connection), + BLE_HCI_ERROR_REMOTE_USER_TERM_CON); + } + hc_protocol_enqueue_response(msg, (uint8_t *)&err, sizeof(ble_error_t)); + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcGapLeConnect: unhandled message id: %d", msg->command_id); + } + } +} + +static void prv_send_msg(HcMessageID_GapLEConnect command_id, void *payload, size_t payload_size) { + hc_protocol_enqueue_with_payload(HcEndpointID_GapLEConnect, command_id, payload, payload_size); +} + +void hc_endpoint_gap_le_connect_send_connection_complete(HcGapLeConnectionData *e) { + prv_send_msg(HcMessageID_GapLEConnect_ConnectionComplete, e, sizeof(*e)); +} + +void hc_endpoint_gap_le_connect_send_disconnection_complete(BleDisconnectionCompleteEvent *e) { + prv_send_msg(HcMessageID_GapLEConnect_DisconnectionComplete, e, sizeof(*e)); +} + +void hc_endpoint_gap_le_connect_send_encryption_changed(BleEncryptionChange *e) { + prv_send_msg(HcMessageID_GapLEConnect_EncryptionChange, e, sizeof(*e)); +} + +void hc_endpoint_gap_le_connect_send_address_and_irk_changed(BleAddressAndIRKChange *e) { + prv_send_msg(HcMessageID_GapLEConnect_UpdateAddressAndIRK, e, sizeof(*e)); +} + +void hc_endpoint_gap_le_connect_send_peer_version_info(BleRemoteVersionInfoReceivedEvent *e) { + prv_send_msg(HcMessageID_GapLEConnect_PeerVersionInfo, e, sizeof(*e)); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gap_service.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gap_service.c new file mode 100644 index 00000000..5c6117d8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gap_service.c @@ -0,0 +1,105 @@ +/* + * 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. + */ + +#include "gatt_wrapper.h" +#include "gap_le_device_name_impl.h" +#include "hc_protocol/hc_endpoint_gap_service.h" +#include "local_addr_impl.h" +#include "pra_generate.h" + +#include "system/logging.h" +#include "system/hexdump.h" + +#include + +// Dialog SDK: +#include "att.h" +#include "ble_gap.h" +#include "ble_gattc.h" +#include "ble_uuid.h" + +#include + +void hc_endpoint_gap_service_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_GapService_SetName: + gap_le_device_name_handle_set((const char *)&msg->payload[0]); + break; + case HcMessageID_GapService_DeviceNameRequest: { + BTDeviceInternal addr; + memcpy(&addr, &msg->payload[0], sizeof(BTDeviceInternal)); + gap_le_device_name_handle_request(&addr); + } + break; + case HcMessageID_GapService_DeviceNameRequest_All: + gap_le_device_name_handle_request_all(); + break; + case HcMessageID_GapService_SetLocalAddress: { + const HcProtocol_GapServiceSetLocalAddress *payload = + (const HcProtocol_GapServiceSetLocalAddress *)msg->payload; + local_addr_set(payload->allow_cycling, + payload->allow_cycling ? NULL : &payload->pinned_addr); + break; + } + case HcMessageID_GapService_GeneratePrivateResolvable_address: { + HcProtocol_GapServiceGeneratePrivateResolvableAddressResponse payload; + pra_generate(&payload.address); + hc_protocol_enqueue_response(msg, (const uint8_t *)&payload, sizeof(payload)); + break; + } + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcGap: unhandled message id: %d", msg->command_id); + } +} + +void hc_endpoint_gap_service_mtu_changed(const Connection *connection, uint16_t mtu) { + HcProtocol_GapServiceMtuChanged mtu_resp = {}; + connection_get_address(connection, &mtu_resp.addr); + mtu_resp.mtu = mtu; + hc_protocol_enqueue_with_payload(HcEndpointID_GapService, HcMessageId_GapService_MtuChanged, + (const uint8_t *)&mtu_resp, sizeof(mtu_resp)); +} + +void hc_endpoint_gap_service_device_name_read(const ble_evt_gattc_read_completed_t *evt) { + if (evt->status != ATT_ERROR_OK) { + PBL_LOG(LOG_LEVEL_WARNING, "Read_device_name failed: Idx: %d Att 0x%x Status %d", + evt->conn_idx, evt->handle, evt->status); + return; + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Read_device_name: Idx: %d Handle 0x%x Offset %d Length %d Data:", + evt->conn_idx, evt->handle, evt->offset, evt->length); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, evt->value, evt->length); + Connection *connection = connection_by_idx(evt->conn_idx); + if (!connection) { + PBL_LOG(LOG_LEVEL_WARNING, "Read_device_name: Failed to find connection"); + return; + } + + typedef struct PACKED HcProtocol_GapDeviceNameResponse { + HcProtocol_GapDeviceNameResponseHeader header; + uint8_t name[evt->length]; + } HcProtocol_GapDeviceNameResponse; + HcProtocol_GapDeviceNameResponse response; + + connection_get_address(connection, &response.header.addr); + response.header.name_length = evt->length; + memcpy(response.name, evt->value, evt->length); + + hc_protocol_enqueue_with_payload(HcEndpointID_GapService, + HcMessageID_GapService_DeviceNameRequest, + (void *)&response, sizeof(HcProtocol_GapDeviceNameResponse)); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gatt.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gatt.c new file mode 100644 index 00000000..8ff3a177 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_gatt.c @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gatt.h" + +#include "connection.h" +#include "gatt_wrapper.h" +#include "hc_protocol/hc_protocol.h" +#include "kernel/pbl_malloc.h" +#include "ppogatt_emulated_server_wa.h" +#include "system/logging.h" + +#include + +#include +#include + +void hc_endpoint_gatt_handler_controller(const HcProtocolMessage *msg) { + BTErrno rv = BTErrnoOK; + + HcGattHdr *hdr = (HcGattHdr *)&msg->payload[0]; + Connection *conn = connection_by_address(&hdr->addr); + if (!conn) { + rv = BTErrnoInvalidParameter; + goto respond; + } + + const uint16_t conn_idx = connection_get_idx(conn); + if (ppogatt_emulated_server_handle_msg(conn_idx, conn, msg)) { + return; + } + + const GattReqSource req_source = GattReqSourceHost; + + switch (msg->command_id) { + case HcMessageID_Gatt_Read: { + HcGattReadData *r_data = (HcGattReadData *)hdr; + gatt_wrapper_read(conn_idx, r_data->att_handle, r_data->context_ref, req_source); + break; + } + case HcMessageID_Gatt_Write: { + HcGattWriteData *w_data = (HcGattWriteData *)hdr; + gatt_wrapper_write(conn_idx, w_data->att_handle, w_data->value_length, w_data->value, + w_data->context_ref, req_source); + break; + } + case HcMessageID_Gatt_WriteNoResponse: { + HcGattWriteData *w_data = (HcGattWriteData *)hdr; + gatt_wrapper_write_no_resp(conn_idx, w_data->att_handle, w_data->value_length, w_data->value); + return; // Don't respond + } + } + + rv = BTErrnoOK; + +respond: + hc_protocol_enqueue_response(msg, (uint8_t *)&rv, sizeof(rv)); +} + +void hc_endpoint_gatt_send_read_complete(const BTDeviceInternal *addr, uint16_t handle, + BLEGATTError status, uint16_t value_length, const uint8_t *value, uintptr_t context_ref) { + + const uint32_t alloc_size = sizeof(HcGattReadRespData) + value_length; + HcGattReadRespData *data = kernel_zalloc_check(alloc_size); + *data = (HcGattReadRespData) { + .status = status, + .hdr.addr = *addr, + .att_handle = handle, + .value_length = value_length, + .context_ref = context_ref, + }; + memcpy(data->value, value, value_length); + hc_protocol_enqueue_with_payload(HcEndpointID_Gatt, HcMessageID_Gatt_ReadCompleted, + (uint8_t *)data, alloc_size); + kernel_free(data); +} + +void hc_endpoint_gatt_send_write_complete(const BTDeviceInternal *addr, uint16_t handle, + BLEGATTError status, uintptr_t context_ref) { + const uint32_t alloc_size = sizeof(HcGattWriteRespData); + HcGattWriteRespData *data = kernel_zalloc_check(alloc_size); + *data = (HcGattWriteRespData) { + .status = status, + .hdr.addr = *addr, + .att_handle = handle, + .context_ref = context_ref, + }; + hc_protocol_enqueue_with_payload(HcEndpointID_Gatt, HcMessageID_Gatt_WriteCompleted, + (uint8_t *)data, alloc_size); + kernel_free(data); +} + +static void prv_send_notification_indication(const BTDeviceInternal *addr, uint16_t handle, + uint16_t value_length, const uint8_t *value, + HcMessageID_Gatt msg_id) { + const uint32_t alloc_size = sizeof(HcGattNotifIndicData) + value_length; + HcGattNotifIndicData *data = kernel_zalloc_check(alloc_size); + *data = (HcGattNotifIndicData) { + .hdr.addr = *addr, + .att_handle = handle, + .value_length = value_length, + }; + memcpy(data->value, value, value_length); + hc_protocol_enqueue_with_payload(HcEndpointID_Gatt, msg_id, (uint8_t *)data, alloc_size); + kernel_free(data); +} + +void hc_endpoint_gatt_send_notification(const BTDeviceInternal *addr, uint16_t handle, + uint16_t value_length, const uint8_t *value) { + prv_send_notification_indication(addr, handle, value_length, value, + HcMessageID_Gatt_Notification); +} + +void hc_endpoint_gatt_send_indication(const BTDeviceInternal *addr, uint16_t handle, + uint16_t value_length, const uint8_t *value) { + prv_send_notification_indication(addr, handle, value_length, value, + HcMessageID_Gatt_Indication); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_hrm.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_hrm.c new file mode 100644 index 00000000..5a3d43be --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_hrm.c @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_hrm.h" +#include "hc_protocol/hc_protocol.h" + +#include "hrm_impl.h" + +static void prv_handle_measurement_msg(const HcProtocolMessage *msg) { + const HcHrmMeasurement *hc_hrm_measurement = (const HcHrmMeasurement *)msg->payload; + + const BleHrmServiceMeasurement hrm_measurement = { + .bpm = hc_hrm_measurement->bpm, + .is_on_wrist = hc_hrm_measurement->is_on_wrist, + }; + hrm_service_handle_measurement(&hrm_measurement, hc_hrm_measurement->devices, + hc_hrm_measurement->num_devices); +} + +void hc_endpoint_hrm_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_HRM_Measurement: + prv_handle_measurement_msg(msg); + break; + + case HcMessageID_HRM_Enable: + hrm_service_handle_enable(((HcHrmEnableCmd *)msg)->enable); + break; + + default: + break; + } +} + +void hc_endpoint_hrm_update_subscription(const HcHrmSubscription *subscription) { + hc_protocol_enqueue_with_payload(HcEndpointID_HRM, HcMessageID_HRM_UpdateSubscription, + (const uint8_t *)subscription, sizeof(*subscription)); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_logging.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_logging.c new file mode 100644 index 00000000..9b60bd01 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_logging.c @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_logging.h" +#include "system/logging.h" + +extern bool host_transport_ready; + +void hc_endpoint_logging_handler(const HcProtocolMessage *msg) { + uint8_t level; + + switch (msg->command_id) { + case HcMessageID_Logging_SetLevel: + level = msg->payload[0]; + pbl_log_set_level(level); + break; + case HcMessageID_Logging_GetLevel: + level = pbl_log_get_level(); + hc_protocol_enqueue_response(msg, &level, sizeof(level)); + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcLogging: unhandled message id: %d", msg->command_id); + break; + } +} + +// The caller must have created the HcProtocolMessage + payload correctly. +bool hc_endpoint_logging_send_msg(HcProtocolMessage *msg) { + if (!host_transport_ready) { + return true; // This is not an "out of space in the ring buffer" situation. Can't alert user. + } + + msg->endpoint_id = HcEndpointID_Logging; + msg->command_id = HcMessageID_Logging_LogMsg; + return hc_protocol_enqueue(msg); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_pairing.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_pairing.c new file mode 100644 index 00000000..450eb24b --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_pairing.c @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_pairing.h" +#include "hc_protocol/hc_protocol.h" + +#include "connection.h" +#include "system/logging.h" + +// Dialog SDK: +#include "ble_common.h" +#include "ble_gap.h" + +#include + +#include + +void pair_reply(uint16_t conn_idx, bool is_confirmed) { + ble_error_t e = ble_gap_pair_reply(conn_idx, + is_confirmed /* should_accept */, + is_confirmed /* should_bond */); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_pair_reply: %d", e); + } +} + +static void prv_handle_pairing_response(const HcProtocolMessagePairingResponsePayload *response) { + Connection *connection = connection_by_address(&response->device); + if (!connection) { + PBL_LOG(LOG_LEVEL_WARNING, "Got pairing response, but disconnected in the mean time."); + return; + } + + uint16_t conn_idx = connection_get_idx(connection); + pair_reply(conn_idx, response->is_confirmed); +} + +void hc_endpoint_pairing_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Pairing_PairingResponse: + prv_handle_pairing_response((const HcProtocolMessagePairingResponsePayload *)msg->payload); + break; + + default: + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected cmd ID: %d", msg->command_id); + break; + } +} + +void hc_endpoint_pairing_send_pairing_request(const BTDeviceInternal *device) { + hc_protocol_enqueue_with_payload(HcEndpointID_Pairing, HcMessageID_Pairing_PairingRequest, + (const uint8_t *)device, sizeof(*device)); +} + +void hc_endpoint_pairing_send_pairing_complete(const BTDeviceInternal *device, + HciStatusCode status) { + const HcProtocolMessagePairingCompletePayload payload = { + .device = *device, + .status = status, + }; + hc_protocol_enqueue_with_payload(HcEndpointID_Pairing, HcMessageID_Pairing_PairingComplete, + (const uint8_t *)&payload, sizeof(payload)); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_pebble_pairing_service.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_pebble_pairing_service.c new file mode 100644 index 00000000..c5369ad1 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_pebble_pairing_service.c @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_pebble_pairing_service.h" +#include "hc_protocol/hc_protocol.h" + +#include "connection.h" +#include "kernel/pbl_malloc.h" + +#include + +#include + +void hc_endpoint_pebble_pairing_service_send_ios_app_termination_detected(void) { + hc_protocol_enqueue_with_payload(HcEndpointID_PebblePairingService, + HcMessageID_PebblePairingServiceiOSAppTerminationDetected, + NULL, 0); +} + +void hc_endpoint_pebble_pairing_service_found_gateway(BTDeviceInternal *device) { + hc_protocol_enqueue_with_payload(HcEndpointID_PebblePairingService, + HcMessageID_PebblePairingServiceFoundGateway, + (uint8_t *)device, sizeof(*device)); +} + +void hc_endpoint_pebble_pairing_service_send_conn_params(const Connection *connection, + const PebblePairingServiceConnParamsWrite *params, size_t params_length) { + size_t len = (sizeof(HcProtocolMessage) + offsetof(HcPpsConnParamsPayload, conn_params) + + params_length); + HcProtocolMessage *msg = (HcProtocolMessage *)kernel_zalloc_check(len); + msg->endpoint_id = HcEndpointID_PebblePairingService; + msg->command_id = HcMessageID_PebblePairingServiceConnParams; + msg->message_length = len; + + HcPpsConnParamsPayload *payload = (HcPpsConnParamsPayload *)&msg->payload[0]; + connection_get_address(connection, &payload->device); + memcpy(&payload->conn_params, params, params_length); + + hc_protocol_enqueue(msg); + kernel_free(msg); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_responsiveness.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_responsiveness.c new file mode 100644 index 00000000..32fa60a9 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_responsiveness.c @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#include "ble_gap.h" + +#include "connection.h" + +#include "dialog_utils.h" +#include "hc_protocol/hc_protocol.h" +#include "hc_protocol/hc_endpoint_responsiveness.h" +#include "system/logging.h" + +#include + +#include + +static void prv_handle_update_request(const HcProtocolMessageResponsivenessPayload *req) { + const BleConnectionParamsUpdateReq *params = &req->params; + gap_conn_params_t conn_params = { + .interval_min = params->interval_min_1_25ms, + .interval_max = params->interval_max_1_25ms, + .slave_latency = params->slave_latency_events, + .sup_timeout = params->supervision_timeout_10ms, + }; + + Connection *conn = connection_by_address(&req->address); + ble_error_t e = BLE_ERROR_NOT_CONNECTED; + if (conn != NULL) { + int conn_idx = connection_get_idx(conn); + PBL_LOG(LOG_LEVEL_DEBUG, "Requesting conn param change, Conn Idx: %d - (%d %d %d %d)", + conn_idx, (int)conn_params.interval_min, (int)conn_params.interval_max, + (int)conn_params.slave_latency, (int)conn_params.sup_timeout); + e = ble_gap_conn_param_update(conn_idx, &conn_params); + } + + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Error prv_handle_update_request: %d", (int)e); + BleConnectionParams params = {}; + hc_endpoint_responsiveness_notify_update( + ¶ms, &req->address, ble_error_to_hci_status_error(e)); + } +} + +void hc_endpoint_responsiveness_notify_update( + const BleConnectionParams *params, const BTDeviceInternal *addr, + HciStatusCode status) { + uint8_t response_len = sizeof(HcProtocolMessage) + + sizeof(BleConnectionUpdateCompleteEvent); + + uint8_t buf[response_len]; + HcProtocolMessage *msg = (HcProtocolMessage *)&buf[0]; + + *msg = (HcProtocolMessage) { + .message_length = response_len, + .endpoint_id = HcEndpointID_Responsiveness, + .command_id = HcMessageID_Id_ConnParamUpdateResponse, + }; + + BleConnectionUpdateCompleteEvent *payload = + (BleConnectionUpdateCompleteEvent *)msg->payload; + *payload = (BleConnectionUpdateCompleteEvent) { + .status = status, + .dev_address = addr->address, + }; + + if (params) { + payload->conn_params = *params; + } + + hc_protocol_enqueue(msg); +} + +void hc_endpoint_responsiveness_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Id_ConnParamUpdateReq: + prv_handle_update_request((HcProtocolMessageResponsivenessPayload *)msg->payload); + break; + default: + PBL_LOG(LOG_LEVEL_WARNING, "Unexpected command: 0x%x", (int)msg->command_id); + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_test.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_test.c new file mode 100644 index 00000000..3bf15750 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoint_test.c @@ -0,0 +1,114 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_test.h" +#include "system/logging.h" +#include + +#include "core_dump.h" + +// Dialog APIs +#include "sdk_defs.h" +#include "hw_rf.h" + +void hw_fem_set_pin_config(BtlePaConfig config); +void rwip_prevent_sleep(bool enable); +void ble_force_wakeup(); + + +static void prv_hard_fault(void) { + // Store 0x00000000 at 0x000003. *(int *)0x03 = 0 + __asm("mov r0, #0\n\t" + "mov r1, #3\n\t" + "str r0, [r1]\n\t"); + PBL_LOG(LOG_LEVEL_ERROR, "HcTest: CoreDump - Unaligned Access succeeded!?!"); +} + +static void prv_handle_core_dump_request(BtleCoreDump type) { + switch (type) { + case BtleCoreDump_UserRequest: + core_dump(true); + break; + case BtleCoreDump_ForceHardFault: + prv_hard_fault(); + break; + case BtleCoreDump_Watchdog: + while (1) {} // wedge the task + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcTest: unhandled core dump id: %d", type); + break; + } +} + +static void prv_enable_continuous_wave_mode(const HcTestUnmodTxStart *cmd) { + PBL_LOG(LOG_LEVEL_ALWAYS, "TX'ing unmodulated CW on BT channel %d", cmd->tx_channel); + + // Prevent the RW ROM from powering down the radio in the future + rwip_prevent_sleep(true); + + // Power up the radio if the RW ROM already put us to sleep + ble_force_wakeup(); + + // Wait for the BLE Radio to be up + while (!REG_GETF(CRG_TOP, SYS_STAT_REG, BLE_IS_UP)) {} + + // Start the CW pattern + hw_rf_start_continuous_wave(0x1, cmd->tx_channel); +} + +extern void cm_lp_clk_force_available(bool force_available); + +static void prv_handle_sleep_test_cmd(const HcProtocolMessage *msg) { + const HcTestSleep *sleep_test = (HcTestSleep *)&msg->payload[0]; + + // Dialog gives the 32K clock time to settle by default. The settling time is 8s. Since we use a + // digital clock this isn't really necessary. However, since there are issues around + // entering/exiting sleep we may want a delay of some sort before sleeping. Thus, so the sleep + // test can fail faster let's just override this check rather than set the timeout to 0 + cm_lp_clk_force_available(sleep_test->force_sleep); + + + uint8_t response[20] = { 0 }; + hc_protocol_enqueue_response(msg, &response[0], sizeof(response)); +} + +void hc_endpoint_test_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { +#if PLATFORM_ROBERT + case HcMessageID_Test_Config_PA: + PBL_LOG(LOG_LEVEL_INFO, "HcTest: Config PA: %d", msg->payload[0]); + hw_fem_set_pin_config(msg->payload[0]); + break; +#endif + case HCMessageID_Test_Sleep: + prv_handle_sleep_test_cmd(msg); + break; + case HcMessageID_Test_UnmodulatedTxStart: + prv_enable_continuous_wave_mode((HcTestUnmodTxStart *)&msg->payload[0]); + break; + case HcMessageID_Test_UnmodulatedTxStop: + rwip_prevent_sleep(false); + hw_rf_stop_continuous_wave(); + break; + case HcMessageID_Test_Core_Dump: + prv_handle_core_dump_request((BtleCoreDump) msg->payload[0]); + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcTest: unhandled message id: %d", msg->command_id); + break; + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoints_hci.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoints_hci.c new file mode 100644 index 00000000..7ef97421 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_endpoints_hci.c @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#include "ad_ble.h" +#include "co_bt.h" +#include "hc_protocol/hc_endpoint_hci.h" +#include "hc_protocol/hc_protocol.h" +#include "hci_rom_passthrough.h" +#include "system/hexdump.h" +#include "system/logging.h" + +#include +#include + +void hc_endpoint_hci_handler(const HcProtocolMessage *msg) { + if (msg->command_id != HcMessageID_Hci_Cmd) { + PBL_LOG(LOG_LEVEL_ERROR, "Unhandled HCI command id: 0x%x", (int)msg->command_id); + } + + PBL_LOG(LOG_LEVEL_DEBUG, "HCI CMD Received:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)msg, msg->message_length); + + hci_cmd_msg_t *hci_cmd = (hci_cmd_msg_t *)&msg->payload[0]; + + uint16_t opcode = hci_cmd->op_code; + hci_rom_passthrough_send_cmd( + HCI_OP2OGF(opcode), HCI_OP2OCF(opcode), &hci_cmd->param[0], + hci_cmd->param_length); +} + +void hc_endpoint_enqueue_hci_evt(const uint8_t *hci_evt, uint8_t payload_len) { + uint8_t hc_message_len = sizeof(HcProtocolMessage) + 1 /* for HCI_EVT_MSG */ + payload_len; + uint8_t hc_protocol_message[hc_message_len]; + + HcProtocolMessage *hc_msg = (HcProtocolMessage *)&hc_protocol_message[0]; + memset(hc_msg, 0x00, hc_message_len); + + *hc_msg = (HcProtocolMessage) { + .message_length = sizeof(hc_protocol_message), + .endpoint_id = HcEndpointID_Hci, + .command_id = HcMessageID_Hci_Evt, + }; + + uint8_t *hc_msg_data = &hc_msg->payload[0]; + *hc_msg_data++ = 0x4; + + memcpy(hc_msg_data, hci_evt, payload_len); + + PBL_LOG(LOG_LEVEL_DEBUG, "Sending:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)hc_msg, hc_message_len); + + hc_protocol_enqueue(hc_msg); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_protocol_endpoints_table.c b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_protocol_endpoints_table.c new file mode 100644 index 00000000..e8b1cad5 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hc_protocol/hc_protocol_endpoints_table.c @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_protocol.h" + +#include "hc_protocol/hc_endpoint_advert.h" +#include "hc_protocol/hc_endpoint_analytics.h" +#include "hc_protocol/hc_endpoint_bonding_sync.h" +#include "hc_protocol/hc_endpoint_chip_id.h" +#include "hc_protocol/hc_endpoint_ctl.h" +#include "hc_protocol/hc_endpoint_discovery.h" +#include "hc_protocol/hc_endpoint_gap_le_connect.h" +#include "hc_protocol/hc_endpoint_gap_service.h" +#include "hc_protocol/hc_endpoint_gatt.h" +#include "hc_protocol/hc_endpoint_logging.h" +#include "hc_protocol/hc_endpoint_hci.h" +#include "hc_protocol/hc_endpoint_hrm.h" +#include "hc_protocol/hc_endpoint_pairing.h" +#include "hc_protocol/hc_endpoint_responsiveness.h" +#include "hc_protocol/hc_endpoint_test.h" + +#include + +const HcProtocolMessageHandler g_hc_protocol_endpoints_table[HcEndpointIDCount] = { + [HcEndpointID_Invalid] = NULL, + [HcEndpointID_Ctl] = hc_endpoint_ctl_handler, + [HcEndpointID_Hci] = hc_endpoint_hci_handler, + [HcEndpointID_GapService] = hc_endpoint_gap_service_handler, + [HcEndpointID_Id] = hc_endpoint_chip_id_handler, + [HcEndpointID_Advert] = hc_endpoint_advert_handler, + [HcEndpointID_Responsiveness] = hc_endpoint_responsiveness_handler, + [HcEndpointID_Gatt] = hc_endpoint_gatt_handler_controller, + [HcEndpointID_Discovery] = hc_endpoint_discovery_handler, + [HcEndpointID_BondingSync] = hc_endpoint_bonding_sync_handler, + [HcEndpointID_Pairing] = hc_endpoint_pairing_handler, + [HcEndpointID_GapLEConnect] = hc_endpoint_gap_le_connect_handler, + [HcEndpointID_Logging] = hc_endpoint_logging_handler, + [HcEndpointID_Analytics] = hc_endpoint_analytics_ctlr_handler, + [HcEndpointID_Test] = hc_endpoint_test_handler, + [HcEndpointID_HRM] = hc_endpoint_hrm_handler, +}; + +void hc_protocol_cb_dispatch_handler( + const HcProtocolMessageHandler handler, HcProtocolMessage *message, bool *should_free) { + handler(message); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hci_rom_passthrough.c b/src/bluetooth-fw/da1468x/controller/main/src/hci_rom_passthrough.c new file mode 100644 index 00000000..719dd8a8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hci_rom_passthrough.c @@ -0,0 +1,140 @@ +/* + * 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. + */ + +#include "ad_ble.h" +#include "ble_common.h" +#include "ble_mgr.h" +#include "ble_mgr_irb_common.h" +#include "co_bt.h" +#include "hc_protocol/hc_endpoint_hci.h" +#include "hci_rom_passthrough.h" +#include "system/hexdump.h" +#include "system/logging.h" + +#include "llc.h" +#include "llm.h" +#include "gapc.h" + +#include + +#include +#include + +bool hci_rom_passthrough_send_cmd( + uint16_t ogf, uint16_t ocf, const uint8_t *param_buf, uint8_t param_length) { + + uint8_t msg_size = sizeof( irb_ble_stack_msg_t ) + sizeof(hci_cmd_msg_t) + param_length; + irb_ble_stack_msg_t *msg_buf = OS_MALLOC(msg_size); // memory free'd in ad_ble.c:ble_task() + + *msg_buf = (irb_ble_stack_msg_t) { + .op_code = IRB_BLE_STACK_MSG, + .msg_type = HCI_CMD_MSG, + .msg_size = HCI_CMD_HEADER_LENGTH + param_length, + }; + + hci_cmd_msg_t *hci_cmd = (hci_cmd_msg_t *)&msg_buf->msg; + *hci_cmd = (hci_cmd_msg_t) { + .op_code = HCI_OPCODE(ocf, ogf), + .param_length = param_length, + }; + + if (param_length != 0) { + memcpy(&hci_cmd->param, param_buf, param_length); + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Sending HCI CMD to ROM stack:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)hci_cmd, msg_buf->msg_size); + + bool result = (ad_ble_command_queue_send(&msg_buf, OS_QUEUE_FOREVER) == pdPASS); + return result; +} + +void hci_rom_passthrough_handle_evt(hci_evt_msg_t *hci_evt) { + uint16_t payload_len = sizeof(hci_evt_msg_t) + hci_evt->param_length - sizeof(hci_evt->param); + PBL_LOG(LOG_LEVEL_DEBUG, "HCI Event Response:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)hci_evt, payload_len); + + hc_endpoint_enqueue_hci_evt((uint8_t *)hci_evt, payload_len); +} + +#if SUPPORTS_PACKET_LENGTH_EXTENSION +void hci_initiate_length_change(uint16_t conn_idx) { + uint16_t connhdl = gapc_get_conhdl(conn_idx); + + struct llc_env_tag *llc_env_ptr = llc_env[connhdl]; + // The ROM HCI handler will only send a length request if it thinks we have requested a parameter + // change. Change the setting here to force the negotiation to be sent. + // Check out ROM function hci_le_set_data_length_cmd_handler() + llc_env_ptr->connMaxTxOctets = LE_LENGTH_EXT_OCTETS_MIN + 1; + + // In llc_le_length_conn_init_func_wa() we set the RX window sizes to be the minimum packet size + // so that in case the other side sends an LL_LENGTH_REQ and there are interopobility issues the + // connection will start without changing the size. If this routine gets called, it's been + // determined that the device connected to supports extended packets so bump our supported RX + // window sizes to the max allowed so these values are sent as part of the LL_LENGTH_REQ + llc_env_ptr->connMaxRxOctets = LE_LENGTH_EXT_OCTETS_MAX; + llc_env_ptr->connMaxRxTime = LE_LENGTH_EXT_TIME_MAX; + + struct PACKED { + uint16_t connhdl; + uint16_t tx_octets; + uint16_t tx_time; + } params = { + .connhdl = connhdl, + .tx_octets = LE_LENGTH_EXT_OCTETS_MIN, + .tx_time = LE_LENGTH_EXT_TIME_MIN, + }; + + uint16_t ocf = HCI_OP2OCF(HCI_LE_SET_DATA_LENGTH_CMD_OPCODE); // 0x22 + uint16_t ogf = HCI_OP2OGF(HCI_LE_SET_DATA_LENGTH_CMD_OPCODE); // 0x08 + + hci_rom_passthrough_send_cmd(ogf, ocf, (uint8_t *)¶ms, sizeof(params)); +} +#endif + +void test_hci_passthrough(void) { + static int i = 0; + + switch (i) { + case 0: + PBL_LOG(LOG_LEVEL_DEBUG, "===Reset==="); + hci_rom_passthrough_send_cmd(0x3, 0x3, NULL, 0); + break; + case 1: { + PBL_LOG(LOG_LEVEL_DEBUG, "===LE Transmit Test==="); + uint8_t params[] = {0x00, 0x20, 0x01}; + hci_rom_passthrough_send_cmd(0x8, 0x1e, ¶ms[0], sizeof(params)); + break; + } + case 2: + PBL_LOG(LOG_LEVEL_DEBUG, "===LE Stop Tx Test==="); + hci_rom_passthrough_send_cmd(0x8, 0x1f, NULL, 0); + break; + case 3: { + PBL_LOG(LOG_LEVEL_DEBUG, "===LE Receiver Test==="); + uint8_t params[] = { 0x00 }; + hci_rom_passthrough_send_cmd(0x8, 0x1d, ¶ms[0], sizeof(params)); + break; + } + case 4: + PBL_LOG(LOG_LEVEL_DEBUG, "===LE Stop Rx Test==="); + hci_rom_passthrough_send_cmd(0x8, 0x1f, NULL, 0); + break; + default: + return; + } + i++; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hexdump.c b/src/bluetooth-fw/da1468x/controller/main/src/hexdump.c new file mode 100644 index 00000000..4c0116a1 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hexdump.c @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#include "system/hexdump.h" +#include "system/logging.h" +#include "util/hexdump.h" + +#include + +extern int _write(int file, char *ptr, int len); + +static void prv_retarget_write_line_cb(int level, const char *src_filename, int src_line_number, + const char *line_buffer) { + PBL_LOG(LOG_LEVEL_DEBUG, "%s", line_buffer); +} + +void hexdump_log_src(int level, const uint8_t *data, size_t length) { + hexdump(NULL, 0, level, data, length, prv_retarget_write_line_cb); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/host_transport.c b/src/bluetooth-fw/da1468x/controller/main/src/host_transport.c new file mode 100644 index 00000000..b55e4ef9 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/host_transport.c @@ -0,0 +1,430 @@ +/* + * 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. + */ + +#include "host_transport.h" +#include "host_transport_protocol.h" +#include "host_transport_impl.h" + +#include "hc_protocol/hc_protocol.h" + +#include "board.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" +#include "system/passert.h" +#include "system/hexdump.h" +#include "tasks.h" +#include "util/attributes.h" +#include "util/circular_buffer.h" +#include "util/crc32.h" + +#include "mcu/interrupts.h" + +#include +#include +#include +#include +#include +#include +#include + +// Design doc: +// https://docs.google.com/document/d/1or2Ygs3sWt_5XNW_Mpe3Vxmhwuh3DTzdgZr6QlEe7iQ/edit# + +#define HOST_TRANSPORT_DEBUG (0) + +#if HOST_TRANSPORT_DEBUG +#define HOST_TRANSPORT_DEBUG_LOG(fmt, ...) PBL_LOG(LOG_LEVEL_DEBUG, fmt, ## __VA_ARGS__) +#else +#define HOST_TRANSPORT_DEBUG_LOG(fmt, ...) +#endif + +// Used to Host Logging +bool host_transport_ready = false; + +static bool s_is_transacting __RETAINED; +static CircularBuffer s_tx_buffer __RETAINED; +static CircularBuffer s_rx_buffer __RETAINED; +static uint8_t s_tx_storage[HOST_TRANSPORT_CTLR_TX_BUFFER_SIZE] __RETAINED; +static uint8_t s_rx_storage[HOST_TRANSPORT_CTLR_RX_BUFFER_SIZE] __RETAINED; + +static void prv_lock(void) { + OS_ENTER_CRITICAL_SECTION(); +} + +static void prv_unlock(void) { + OS_LEAVE_CRITICAL_SECTION(); +} + +static void prv_core_dump(void) { + // TODO Implement core dump + PBL_ASSERTN(0); +} + +void host_transport_set_mcu_int(bool is_ready_to_transact) { + if (is_ready_to_transact) { + hw_gpio_set_active(HOST_SPI->mcu_int.port, HOST_SPI->mcu_int.pin); + } else { + hw_gpio_set_inactive(HOST_SPI->mcu_int.port, HOST_SPI->mcu_int.pin); + } +} + +static void prv_disable_spi_cs_wakeup_interrupt_handling_and_unblock_transaction_loop(void) { + if (__atomic_test_and_set(&s_is_transacting, __ATOMIC_RELAXED)) { + // Already transacting + return; + } + + // Disable SPI CS wakeup interrupt, otherwise the ISR will keep firing during the SPI transfers. + HW_WKUP_REG_SETF(CTRL, WKUP_ENABLE_IRQ, 0); + + // Put a task notification to our task to act upon the interrupt: + if (mcu_state_is_isr()) { + OS_TASK_NOTIFY_FROM_ISR(DialogTaskList[DialogTask_HostTrans], 0, eNoAction); + } else { + OS_TASK_NOTIFY(DialogTaskList[DialogTask_HostTrans], 0, eNoAction); + } +} + +static void prv_reenable_spi_cs_wakeup_interrupt_handling(void) { + hw_wkup_reset_interrupt(); + HW_WKUP_REG_SETF(CTRL, WKUP_ENABLE_IRQ, 1); +} + +static void prv_sample_spi_cs_and_unblock_loop_if_needed(void) { + // Sample CS, to handle case where the edge was missed: + bool is_cs_asserted = !hw_gpio_get_pin_status(HOST_SPI->spi.cs.port, + HOST_SPI->spi.cs.pin); + if (is_cs_asserted && !s_is_transacting) { + // Edge was missed, pretend interrupt to be fired: + prv_disable_spi_cs_wakeup_interrupt_handling_and_unblock_transaction_loop(); + } +} + +static bool prv_is_scs_asserted(void) { + return (false == hw_gpio_get_pin_status(HOST_SPI->spi.cs.port, HOST_SPI->spi.cs.pin)); +} + +// TODO: Can we avoid re-configuring every time? We might be missing a trigger if it happens +// before this reconfiguring is completed... :( +// Dialog Aart: "Unfortunately it is not possible to program an I/O pin with both functions as +// Wake-up and SPI (or other). Either toggle between the 2 functions or define a dedicated GPIO pin +// for Wake-up." +// +// NB: used by core_dump.c +void host_transport_configure_spi_scs_pin(SCSPinFunction function) { + if (function == SCSPinFunction_Wakeup_GPIO) { + // Configure SCS to generate a wake up interrupt when the line is pulled down: + hw_gpio_set_pin_function(HOST_SPI->spi.cs.port, HOST_SPI->spi.cs.pin, + HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_GPIO); + } else { + // Configure the SCS pin as "SPI Enable" alternate function: + hw_gpio_set_pin_function(HOST_SPI->spi.cs.port, HOST_SPI->spi.cs.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.cs.function); + } +} + +static void prv_spi_chip_select_interrupt_handler(void) { + // Interrupt handler should always reset interrupt state, otherwise it will be called again. + hw_wkup_reset_interrupt(); + + prv_disable_spi_cs_wakeup_interrupt_handling_and_unblock_transaction_loop(); +} + +// NB: used by core_dump.c +void init_spi_pins(void) { + hw_gpio_set_pin_function(HOST_SPI->spi.clk.port, HOST_SPI->spi.clk.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.clk.function); + hw_gpio_set_pin_function(HOST_SPI->spi.mosi_di.port, HOST_SPI->spi.mosi_di.pin, + HW_GPIO_MODE_INPUT, HOST_SPI->spi.mosi_di.function); + hw_gpio_set_pin_function(HOST_SPI->spi.miso_do.port, HOST_SPI->spi.miso_do.pin, + HW_GPIO_MODE_OUTPUT, HOST_SPI->spi.miso_do.function); + hw_gpio_set_pin_function(HOST_SPI->spi.cs_2.port, HOST_SPI->spi.cs_2.pin, + HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_GPIO); + + hw_gpio_configure_pin(HOST_SPI->mcu_int.port, HOST_SPI->mcu_int.pin, + HW_GPIO_MODE_OUTPUT, HOST_SPI->mcu_int.function, false /* is_high */); +} + +static void prv_do_blocking_spi_transfer(spi_device dev, spi_transfer_data *transfer) { + ad_spi_complex_transact(dev, transfer, 1, host_transport_set_mcu_int); +} + +static bool prv_transact(spi_device dev) { + const uint8_t *tx_bytes = NULL; + + prv_lock(); + uint16_t tx_bytes_available = circular_buffer_get_read_space_remaining(&s_tx_buffer); + if (tx_bytes_available) { + circular_buffer_read(&s_tx_buffer, tx_bytes_available, &tx_bytes, &tx_bytes_available); + } + uint8_t *rx_buffer_ptr = NULL; + uint16_t rx_bytes_available = circular_buffer_write_prepare(&s_rx_buffer, &rx_buffer_ptr); + prv_unlock(); + + // OK to use the stack for these variables, + // because ad_spi_complex_transact blocks until transfer is completed: + SPITransportMsgStatus remote_status_in = {}; + SPITransportMsgStatus local_status_out = { + .msg_id = SPITransportMsgID_Status, + .bytes_sendable_count = tx_bytes_available, + .bytes_receivable_count = rx_bytes_available, + }; + size_t crc_len = sizeof(local_status_out) - sizeof(local_status_out.crc); + local_status_out.crc = crc32(CRC32_INIT, &local_status_out, crc_len); + + // Full duplex transaction to exchange Status: + { + spi_transfer_data transfer = { + .wbuf = &local_status_out, + .rbuf = &remote_status_in, + .length = sizeof(SPITransportMsgStatus), + }; + prv_do_blocking_spi_transfer(dev, &transfer); + + // Check the incoming CRC + uint32_t crc = crc32(CRC32_INIT, &remote_status_in, sizeof(remote_status_in)); + if (crc != CRC32_RESIDUE) { + PBL_LOG(LOG_LEVEL_ERROR, "CRC32 failed on Status Exchange: 0x%"PRIu32 " vs 0x%"PRIu32, + crc, (uint32_t)CRC32_RESIDUE); + PBL_LOG(LOG_LEVEL_DEBUG, "->OUT"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)&local_status_out, sizeof(local_status_out)); + PBL_LOG(LOG_LEVEL_DEBUG, "->IN"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)&remote_status_in, sizeof(remote_status_in)); + prv_core_dump(); + } + + HOST_TRANSPORT_DEBUG_LOG("Local Status: %u bytes sendable, %u bytes receivable", + local_status_out.bytes_sendable_count, + local_status_out.bytes_receivable_count); + HOST_TRANSPORT_DEBUG_LOG("Remote Status: %u bytes sendable, %u bytes receivable", + remote_status_in.bytes_sendable_count, + remote_status_in.bytes_receivable_count); + } + + // Single duplex write: + size_t tx_len = MIN(tx_bytes_available, remote_status_in.bytes_receivable_count); + if (tx_len) { + // Calculate the CRC before Transmitting + SPITransportMsgFooter tx_footer; + tx_footer.crc = crc32(CRC32_INIT, tx_bytes, tx_len); + + HOST_TRANSPORT_DEBUG_LOG("Expecting to write %u bytes:", tx_len); + spi_transfer_data transfer = { + .wbuf = tx_bytes, + .length = tx_len, + }; + prv_do_blocking_spi_transfer(dev, &transfer); + + prv_lock(); + circular_buffer_consume(&s_tx_buffer, tx_len); + prv_unlock(); + + // Send the footer + spi_transfer_data tx_footer_transfer = { + .wbuf = &tx_footer, + .length = sizeof(tx_footer), + }; + + prv_do_blocking_spi_transfer(dev, &tx_footer_transfer); + + HOST_TRANSPORT_DEBUG_LOG("Sent %u bytes.", tx_len); + } else { + HOST_TRANSPORT_DEBUG_LOG("Nothing to send."); + } + + // Single duplex read: + const size_t rx_len = MIN(remote_status_in.bytes_sendable_count, rx_bytes_available); + if (rx_len) { + HOST_TRANSPORT_DEBUG_LOG("Expecting to read %u bytes:", rx_len); + spi_transfer_data transfer = { + .rbuf = rx_buffer_ptr, + .length = rx_len, + }; + prv_do_blocking_spi_transfer(dev, &transfer); + + // Read CRC32 & confirm + SPITransportMsgFooter rx_footer; + spi_transfer_data rx_footer_transfer = { + .rbuf = &rx_footer, + .length = sizeof(rx_footer), + }; + prv_do_blocking_spi_transfer(dev, &rx_footer_transfer); + uint32_t crc = crc32(CRC32_INIT, rx_buffer_ptr, rx_len); + if (crc != rx_footer.crc) { + PBL_LOG(LOG_LEVEL_ERROR, "CRC32 failed on Data Read: 0x%"PRIu32 " vs 0x%"PRIu32, + crc, rx_footer.crc); + prv_core_dump(); + } +#if HOST_TRANSPORT_DEBUG + HOST_TRANSPORT_DEBUG_LOG("Received %u bytes:", rx_len); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, rx_buffer_ptr, rx_len); +#endif + } else { + HOST_TRANSPORT_DEBUG_LOG("Nothing to receive."); + } + + bool has_more_rx_data = (remote_status_in.bytes_sendable_count > rx_bytes_available); + bool should_continue_to_rx_data = + (has_more_rx_data && (local_status_out.bytes_receivable_count != 0)); + if (has_more_rx_data && !should_continue_to_rx_data) { + HOST_TRANSPORT_DEBUG_LOG("Host Transport Receive Buffer Full, exiting from rx'ing to process"); + } + + prv_lock(); + circular_buffer_write_finish(&s_rx_buffer, rx_len); + + // Check if more data is available in the circular buffer. + // If not, flip s_is_transacting back while the lock is taken. Otherwise, a concurrent call to + // host_transport_tx_enqueue() would be prevented to unblock the transaction loop again. + bool has_more_tx_data = circular_buffer_get_read_space_remaining(&s_tx_buffer); + bool should_continue = (has_more_tx_data || should_continue_to_rx_data); + s_is_transacting = should_continue; + prv_unlock(); + + return should_continue; +} + +static void prv_host_transport_main(void *ctx) { + static int8_t s_ble_host_transport_wdog_id; + s_ble_host_transport_wdog_id = sys_watchdog_register(false); + while (true) { + sys_watchdog_notify(s_ble_host_transport_wdog_id); + sys_watchdog_suspend(s_ble_host_transport_wdog_id); + + // Handle missed SPI CS edge: + prv_sample_spi_cs_and_unblock_loop_if_needed(); + + // Block the transaction loop until there's either data to transmit, or until the master + // asserts the SCS line: + xTaskNotifyWait(0, ~0, NULL, portMAX_DELAY); + + sys_watchdog_resume(s_ble_host_transport_wdog_id); + + HOST_TRANSPORT_DEBUG_LOG("prv_host_transport_main loop unblocked, about to read.."); + + spi_device dev = ad_spi_open(PEBBLE_HOST); + ad_spi_device_acquire(dev); + + host_transport_configure_spi_scs_pin(SCSPinFunction_SPI_CS); + + while (prv_transact(dev)) {}; + + // Re-enable interrupt handling before processing, + // so that endpoint handlers can cause the loop to get unblocked immediately: + prv_reenable_spi_cs_wakeup_interrupt_handling(); + + if (DialogTaskList[DialogTask_Ble] == 0) { + // We don't bring up the ble task until we have received an init cmd + hc_protocol_process_receive_buffer(); + } else { + OS_TASK_NOTIFY(DialogTaskList[DialogTask_Ble], 0x0, eSetBits); + } + + ad_spi_device_release(dev); + ad_spi_close(dev); + + host_transport_configure_spi_scs_pin(SCSPinFunction_Wakeup_GPIO); + } +} + +HostTransportEnqueueStatus host_transport_tx_enqueue(const uint8_t *buffer, size_t length) { + PBL_ASSERTN(length < HOST_TRANSPORT_CTLR_TX_BUFFER_SIZE && + length < HOST_TRANSPORT_HOST_RX_BUFFER_SIZE); + + prv_lock(); + bool success = circular_buffer_write(&s_tx_buffer, buffer, length); + prv_unlock(); + + if (success) { + prv_disable_spi_cs_wakeup_interrupt_handling_and_unblock_transaction_loop(); + return HostTransportEnqueueStatus_Success; + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Failed to enqueue %u bytes", length); + return HostTransportEnqueueStatus_RetryLater; +} + +size_t host_transport_rx_get_length(void) { + prv_lock(); + size_t rx_length = circular_buffer_get_read_space_remaining(&s_rx_buffer); + prv_unlock(); + return rx_length; +} + +bool host_transport_rx_read(uint8_t **data_ptr_out, size_t length) { + prv_lock(); + bool caller_should_free = false; + PBL_ASSERTN(circular_buffer_read_or_copy(&s_rx_buffer, data_ptr_out, length, + kernel_malloc, &caller_should_free)); + prv_unlock(); + return caller_should_free; +} + +void host_transport_rx_consume(size_t length) { + prv_lock(); + circular_buffer_consume(&s_rx_buffer, length); + prv_unlock(); + + hc_protocol_buffer_gained_space(); +} + +bool host_transport_is_current_task_host_transport_task(void) { + return (DialogTaskList[DialogTask_HostTrans] == xTaskGetCurrentTaskHandle()); +} + +// NB: used by core_dump.c +void host_transport_init_periph(void) { + init_spi_pins(); +} + +static void prv_wakeup_init(void) { + hw_wkup_init(NULL); + hw_wkup_set_counter_threshold(1); + hw_wkup_set_debounce_time(0); + hw_wkup_configure_pin(HOST_SPI->spi.cs.port, HOST_SPI->spi.cs.pin, + true /* enabled */, HW_WKUP_PIN_STATE_LOW); + hw_wkup_reset_interrupt(); + hw_wkup_reset_counter(); + hw_wkup_register_interrupt(prv_spi_chip_select_interrupt_handler, 1); +} + +void host_transport_init(void) { + SPI_BUS_INIT(SPI1); + SPI_DEVICE_INIT(PEBBLE_HOST); + + circular_buffer_init(&s_tx_buffer, s_tx_storage, HOST_TRANSPORT_CTLR_TX_BUFFER_SIZE); + circular_buffer_init(&s_rx_buffer, s_rx_storage, HOST_TRANSPORT_CTLR_RX_BUFFER_SIZE); + + // Start the task that runs the transaction loop: + OS_BASE_TYPE status = OS_TASK_CREATE("HT", prv_host_transport_main, + NULL /* ctx */, 1280 /* stack_size */, + (tskIDLE_PRIORITY + 1) /* same as BLE task */, + DialogTaskList[DialogTask_HostTrans]); + PBL_ASSERTN(status == OS_TASK_CREATE_SUCCESS); + + prv_wakeup_init(); + + host_transport_configure_spi_scs_pin(SCSPinFunction_Wakeup_GPIO); + + // Read SCS to see if it's already asserted by the MCU and we need to run the loop already: + if (prv_is_scs_asserted()) { + prv_disable_spi_cs_wakeup_interrupt_handling_and_unblock_transaction_loop(); + } + + hc_protocol_boot(); + hc_protocol_init(); + host_transport_ready = true; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/hrm.c b/src/bluetooth-fw/da1468x/controller/main/src/hrm.c new file mode 100644 index 00000000..c5ec8aaf --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/hrm.c @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#include "hrm_impl.h" + +#include "connection.h" +#include "dialog_utils.h" +#include "gatt_local_services.h" +#include "service_changed.h" +#include "system/logging.h" +#include "system/passert.h" +#include "hc_protocol/hc_endpoint_hrm.h" + +// Dialog SDK: +#include "hrs.h" +#include "osal.h" + +#include +#include + +static bool s_is_hrm_enabled = false; +static hr_service_t s_hrs; + +void hrm_service_init(bool is_hrm_supported_and_enabled) { + s_is_hrm_enabled = is_hrm_supported_and_enabled; + ble_service_add(&s_hrs.svc); +} + +static void prv_handle_subscribe(uint16_t conn_idx, bool enabled) { + Connection *connection = connection_by_idx(conn_idx); + if (!connection) { + return; + } + + HcHrmSubscription subscription; + subscription.is_subscribed = enabled; + connection_get_address(connection, &subscription.device); + hc_endpoint_hrm_update_subscription(&subscription); +} + +void hrm_service_register(uint16_t start_hdl) { + if (!s_is_hrm_enabled) { + // Set all handlers to NULL, so the ble_service dispatcher won't ever try to call us: + s_hrs.svc = (const ble_service_t) {}; + return; + } + + const hrs_body_sensor_location_t sensor_location = HRS_SENSOR_LOC_WRIST; + static const hrs_callbacks_t s_callbacks = { + .ee_reset = NULL, // Beat-to-beat interval data is not supported at the moment. + .notif_changed = prv_handle_subscribe, + }; + hrs_init(&s_hrs, sensor_location, &s_callbacks, start_hdl); + PBL_ASSERTN(start_hdl == s_hrs.svc.start_h); +} + +void hrm_service_handle_measurement(const BleHrmServiceMeasurement *measurement, + const BTDeviceInternal *permitted_devices, + size_t num_permitted_devices) { + const hrs_measurement_t hrs_measurement = { + .bpm = measurement->bpm, + .contact_supported = true, + .contact_detected = measurement->is_on_wrist, + .has_energy_expended = false, + // NTH: Use calories burnt calculation from the activity algo to set energy_expended. + // https://pebbletechnology.atlassian.net/browse/PBL-42867 + .energy_expended = 0, + .rr_num = 0, + }; + + // Only notify permitted devices: + gap_device_t devices[8]; + size_t length = ARRAY_LENGTH(devices); + ble_gap_get_devices(GAP_DEVICE_FILTER_CONNECTED, NULL, &length, devices); + for (int i = 0; i < (int)length; ++i) { + const gap_device_t *const gap_device = &devices[i]; + BTDeviceInternal device; + dialog_utils_bd_address_to_bt_device(&gap_device->address, &device); + for (int j = 0; j < (int)num_permitted_devices; ++j) { + if (bt_device_internal_equal(&device, &permitted_devices[j])) { + hrs_notify_measurement(&s_hrs.svc, gap_device->conn_idx, &hrs_measurement); + } + } + } +} + +void hrm_service_handle_enable(bool enable) { + if (s_is_hrm_enabled == enable) { + return; + } + PBL_LOG(LOG_LEVEL_DEBUG, "hrm_service_handle_enable %u", enable); + s_is_hrm_enabled = enable; + + if (enable) { + hrm_service_register(HRM_SERVICE_EXPECTED_ATT_STARTING_HANDLE); + service_changed_send_indication_to_all(HRM_SERVICE_EXPECTED_ATT_STARTING_HANDLE, + HRM_SERVICE_EXPECTED_ATT_ENDING_HANDLE); + } else { + // Unfortunately, there is no clean way to remove a service from RivieraWaves once it has been + // added. The various ble_... APIs do nuke the GATT DB entirely, but can only be called when + // there is no connection / on-going air operations. + // The next time the BT stack is restarted (granted the user pref hasn't changed to "enabled"), + // the service will be gone. + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/kernel_heap.c b/src/bluetooth-fw/da1468x/controller/main/src/kernel_heap.c new file mode 100644 index 00000000..d68a3290 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/kernel_heap.c @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#include "kernel/pbl_malloc.h" + +#include "system/passert.h" +#include "util/attributes.h" +#include "util/heap.h" + +#include "FreeRTOS.h" +#include "sdk_defs.h" + +#include + +static Heap s_kernel_heap; +static bool s_interrupts_disabled_by_heap; + +// FIXME: Cortex-M0 does not have the CMSIS, therefore can't disable interrupts for lower priority +// tasks. It's all or nothing. We should come up with a better way for this. +static void prv_heap_lock(void *ctx) { + if ((__get_PRIMASK() & 0x1) == 0) { + __disable_irq(); + s_interrupts_disabled_by_heap = true; + } +} + +static void prv_heap_unlock(void *ctx) { + if (s_interrupts_disabled_by_heap) { + __enable_irq(); + s_interrupts_disabled_by_heap = false; + } +} + +void kernel_heap_init(void) { + const bool fuzz_on_free = true; + + extern uint8_t __heap_start; + uint8_t *heap_start = &__heap_start; + uint8_t *heap_end = heap_start + configTOTAL_HEAP_SIZE; + heap_init(&s_kernel_heap, heap_start, heap_end, fuzz_on_free); + heap_set_lock_impl(&s_kernel_heap, (HeapLockImpl) { + .lock_function = prv_heap_lock, + .unlock_function = prv_heap_unlock + }); +} + +// kernel_* functions that allocate on the kernel heap +/////////////////////////////////////////////////////////// + +static ALWAYS_INLINE void *prv_heap_malloc(size_t bytes) { + const uintptr_t saved_lr = (uintptr_t) __builtin_return_address(0); + return heap_malloc(&s_kernel_heap, bytes, saved_lr); +} + +void *kernel_malloc(size_t bytes) { + return prv_heap_malloc(bytes); +} + +void *kernel_zalloc(size_t bytes) { + void *ptr = prv_heap_malloc(bytes); + if (ptr) { + memset(ptr, 0, bytes); + } + return ptr; +} + +void *kernel_malloc_check(size_t bytes) { + void *ptr = prv_heap_malloc(bytes); + PBL_ASSERTN(ptr); + return ptr; +} + +void *kernel_zalloc_check(size_t bytes) { + void *ptr = prv_heap_malloc(bytes); + PBL_ASSERTN(ptr); + memset(ptr, 0, bytes); + return ptr; +} + +void kernel_free(void *ptr) { + register uintptr_t lr __asm("lr"); + uintptr_t saved_lr = lr; + + heap_free(&s_kernel_heap, ptr, saved_lr); +} + +// Fun fact: The unconditional branch range for cortex M0 is very small (See: +// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0497a/BABEFHAE.html) +// This seems to result in the compiler always emitting "bl" instructions even +// if a function is just calling one other function. Thus, just alias the +// following routines to the function they call so that we get the lr from the +// original call site saved. +ALIAS("kernel_malloc") void* pvPortMalloc(size_t xSize); +ALIAS("kernel_free") void vPortFree(void* pv); diff --git a/src/bluetooth-fw/da1468x/controller/main/src/libos_platform.c b/src/bluetooth-fw/da1468x/controller/main/src/libos_platform.c new file mode 100644 index 00000000..0238f213 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/libos_platform.c @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#include "kernel/pbl_malloc.h" +#include "system/logging.h" +#include "system/passert.h" +#include "util/attributes.h" + +#include +#include + +void os_log(const char *filename, int line, const char *string) { + PBL_LOG(LOG_LEVEL_DEBUG, "%s:%d> %s", filename, line, string); +} + +NORETURN os_assertion_failed(const char *filename, int line) { + const uintptr_t lr = (uintptr_t) __builtin_return_address(0); + PBL_ASSERTN_LR(0, lr); +} + +NORETURN os_assertion_failed_lr(const char *filename, int line, uint32_t lr) { + PBL_ASSERTN_LR(0, lr); +} + +void *os_malloc(size_t size) { + return kernel_malloc(size); +} + +void *os_malloc_check(size_t size) { + return kernel_malloc_check(size); +} + +void os_free(void *ptr) { + kernel_free(ptr); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/local_addr.c b/src/bluetooth-fw/da1468x/controller/main/src/local_addr.c new file mode 100644 index 00000000..b0396c97 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/local_addr.c @@ -0,0 +1,193 @@ +/* + * 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. + */ + +#include "local_addr_impl.h" +#include "ble_task.h" +#include "advert.h" +#include "system/logging.h" +#include "system/passert.h" + +// Dialog SDK: +#include "ble_common.h" +#include "ble_gap.h" +#include "ble_irb_helper.h" +#include "ble_mgr.h" +#include "osal.h" + +#include + +#include +#include +#include + +extern bool advert_execute_cb_if_adverts_are_paused(void (*cb)(void *), void *ctx); + +#define LOCAL_ADDR_RENEW_DURATION_SECS (600) // Cycle every 600s or 10mins + +typedef enum { + LocalAddressState_Uninitialized, + LocalAddressState_AwaitingAdvertisementsPaused, + LocalAddressState_AwaitingDisconnection, + LocalAddressState_InSync, +} LocalAddressState; + +typedef struct { + bool allow_cycling; + BTDeviceAddress pinned_address; +} LocalAddressPolicy; + +// These statics should only get access from ble_task, so no locking needed. +static LocalAddressPolicy s_local_addr_desired_policy; +static LocalAddressState s_local_addr_state; + +static void prv_get_desired_own_address(own_address_t *desired_address_out) { + if (s_local_addr_desired_policy.allow_cycling) { + PBL_LOG(LOG_LEVEL_DEBUG, + "Trying to set local address to auto-cycling private resolvable address!"); + desired_address_out->addr_type = PRIVATE_RANDOM_RESOLVABLE_ADDRESS; + return; + } + + PBL_LOG(LOG_LEVEL_DEBUG, + "Trying to set local address: allow_cycling=%d, pinned_address="BT_DEVICE_ADDRESS_FMT, + s_local_addr_desired_policy.allow_cycling, + BT_DEVICE_ADDRESS_XPLODE(s_local_addr_desired_policy.pinned_address)); + + // Note: the pinned address is actually resolvable, but we need to lie to the SDK here to avoid + // automatically cycling, see irb_ble_handler_gap_address_set_cmd(): + desired_address_out->addr_type = PRIVATE_STATIC_ADDRESS; + memcpy(&desired_address_out->addr, + &s_local_addr_desired_policy.pinned_address.octets, sizeof(desired_address_out->addr)); +} + +typedef struct { + own_address_t desired_own_address; + bool did_succeed_setting_own_address; +} OwnAddressUpdateInfo; + +static void prv_try_set_own_address_cb(void *ctx) { + OwnAddressUpdateInfo *info = ctx; + // Note: renew param is not used when addr_type is "static address": + uint16_t renew_duration_10ms_steps = LOCAL_ADDR_RENEW_DURATION_SECS * 100 /* 10ms steps */; + ble_error_t e = ble_gap_address_set(&info->desired_own_address, renew_duration_10ms_steps); + if (e == BLE_STATUS_OK) { + info->did_succeed_setting_own_address = true; + } else { + // This is possible if there is still a connection. We could try checking if there are any + // connections at the moment, but this is prone to races because of the various queues that + // are used. Instead, just try ble_gap_address_set() and see if it was successful. + PBL_LOG(LOG_LEVEL_DEBUG, + "Address couldn't be updated (e=0x%x). Will try again later.", e); + info->did_succeed_setting_own_address = false; + } +} + +static bool prv_current_address_is_already_pinned_address(void) { + if (s_local_addr_desired_policy.allow_cycling) { + // Pinned address isn't used. + return false; + } + own_address_t own_addr = {}; + ble_gap_address_get(&own_addr); + if (own_addr.addr_type != PRIVATE_STATIC_ADDRESS) { + return false; + } + return (0 == memcmp(own_addr.addr, s_local_addr_desired_policy.pinned_address.octets, + sizeof(own_addr.addr))); +} + +static void prv_try_updating_own_address(void) { + OwnAddressUpdateInfo update_info = {}; + + prv_get_desired_own_address(&update_info.desired_own_address); + + if (!prv_current_address_is_already_pinned_address()) { + // FIXME - PBL-36339: Pause scanning too when we start using it for BLE Central APIs + if (!advert_execute_cb_if_adverts_are_paused(prv_try_set_own_address_cb, &update_info)) { + // Wait for local_addr_handle_disconnection() to happen. + s_local_addr_state = LocalAddressState_AwaitingAdvertisementsPaused; + return; + } + + if (!update_info.did_succeed_setting_own_address) { + // Wait for local_addr_handle_disconnection() to happen. + s_local_addr_state = LocalAddressState_AwaitingDisconnection; + return; + } + } + + PBL_LOG(LOG_LEVEL_DEBUG, "Local address policy in sync!"); + s_local_addr_state = LocalAddressState_InSync; +} + +// @note: Everything below executes on ble_task to avoid locking. +static void prv_assert_is_executing_on_ble_task(void) { + ble_task_assert_is_executing_on_ble_task(); +} + +void local_addr_set(bool allow_cycling, const BTDeviceAddress *pinned_address) { + prv_assert_is_executing_on_ble_task(); + + s_local_addr_desired_policy.allow_cycling = allow_cycling; + if (!allow_cycling) { + s_local_addr_desired_policy.pinned_address = *pinned_address; + } else { + s_local_addr_desired_policy.pinned_address = (BTDeviceAddress) {}; + } + prv_try_updating_own_address(); +} + +void local_addr_handle_update(const BTDeviceAddress *updated_address) { + prv_assert_is_executing_on_ble_task(); + + if (s_local_addr_state == LocalAddressState_InSync && + (!s_local_addr_desired_policy.allow_cycling)) { + PBL_LOG(LOG_LEVEL_ERROR, + "Address cycled even though it was expected not to! " BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE_PTR(updated_address)); + } +} + +void local_addr_handle_adverts_stopped(void) { + prv_assert_is_executing_on_ble_task(); + + if (s_local_addr_state == LocalAddressState_AwaitingAdvertisementsPaused) { + PBL_LOG(LOG_LEVEL_DEBUG, "Adverts paused, trying to update own address"); + prv_try_updating_own_address(); + } +} + +void local_addr_handle_disconnection(void) { + prv_assert_is_executing_on_ble_task(); + + if (s_local_addr_state == LocalAddressState_AwaitingDisconnection) { + PBL_LOG(LOG_LEVEL_DEBUG, "Got disconnection, trying to update own address"); + prv_try_updating_own_address(); + } +} + +void local_addr_init(void) { + s_local_addr_state = LocalAddressState_Uninitialized; + + // Put all zeroes into ble_dev_params.own_address, as a known starting state. + // The all zeroes address is used in prv_get_desired_own_address(), to check whether a resolvable + // address has been generated yet or not. + const own_address_t init_own_address = { + .addr_type = PRIVATE_RANDOM_RESOLVABLE_ADDRESS, + .addr = {}, + }; + ble_gap_address_set(&init_own_address, LOCAL_ADDR_RENEW_DURATION_SECS); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/logging.c b/src/bluetooth-fw/da1468x/controller/main/src/logging.c new file mode 100644 index 00000000..eaf56158 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/logging.c @@ -0,0 +1,455 @@ +/* + * 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. + */ + +#include "system/logging.h" +#include "custom_config_main.h" +#include "tasks.h" +#include "hc_protocol/hc_endpoint_logging.h" + +#include +#include + +#include "kernel/pbl_malloc.h" +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// See pbl_log_init() +extern const uint8_t __ble_vars_start__; + +_Static_assert((CORE_ID_BLE & PACKED_CORE_MASK) == CORE_ID_BLE, "Core number invalid"); +#define str(s) xstr(s) +#define xstr(s) #s + + +#define MAX_MSG_LEN (128) +#define MAX_MSG_STR_LEN (MAX_MSG_LEN - sizeof(BinLogMessage_Param_v1) - (9 * sizeof(uint32_t))) +#define MAX_MSG_STR_LEN_HALF (MAX_MSG_STR_LEN / 2) + +// Gives us ~13ms of message flush latency. +#define LOG_BUFFER_SIZE (MAX_MSG_LEN * 3) + +#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member) + +#define NEW_LOG_HEADER "NL" NEW_LOG_VERSION + +// Define the .log_string section format. +static const char prv_NewLogHeader[] __attribute__((nocommon, used, section(".log_string.header"))) + = NEW_LOG_HEADER "=::::," \ + "CORE_ID=" str(CORE_ID_BLE) ",CORE_NAME=da1468x"; + +// Confirm the size calculations. If these fail, update tools/loghashing/check_elf_log_strings.py +// We can't currently handle 64 bit values. +_Static_assert(sizeof(long int) <= 4, "long int larger than expected"); +_Static_assert(sizeof(size_t) <= 4, "size_t larger than expected"); +_Static_assert(sizeof(ptrdiff_t) <= 4, "ptrdiff_t larger than expected"); + + +static void prv_start_tx(void); +void debug_uart_init(void); + +// LogBuffer -- Circular buffer & control for the log hashing output buffer +typedef struct LogBuffer { + CircularBuffer circular_buffer; + uint8_t level; + volatile bool transmitting; +} LogBuffer; + +static uint8_t s_log_buffer_ram[LOG_BUFFER_SIZE] __attribute__((section(".log_buffer"))); +static LogBuffer s_log_buffer = { .level = LOG_LEVEL_ALWAYS, .transmitting = false, }; + +/* + * Duplicate _VERBOSE -- let's have a reasonable entry for every value should something go + * wrong on the packing end. + */ +static const uint8_t LEVEL_MAP[8] = { LOG_LEVEL_ALWAYS, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, + LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_VERBOSE, + LOG_LEVEL_DEBUG_VERBOSE, LOG_LEVEL_DEBUG_VERBOSE }; +static const struct OVERRUN { + HcProtocolMessage hc_header; + BinLogMessage_Header_v1 header; + uint8_t string[8]; +} OVERRUN = { + .hc_header = { .message_length = sizeof(OVERRUN), }, + .header = { .version = BINLOGMSG_VERSION_STRING_V1, .length = 8, }, + .string = "OVERRUN\0", +}; + + +void pbl_log_set_level(uint8_t level) { + s_log_buffer.level = level; +} + +uint8_t pbl_log_get_level(void) { + return s_log_buffer.level; +} + +int printf(const char *__restrict format, ...) { + char line_buffer[128]; + va_list args; + va_start(args, format); + int len = vsnprintf(line_buffer, sizeof(line_buffer), format, args); + hw_uart_write_buffer(CONFIG_LOG_UART, + line_buffer, MIN((int)sizeof(line_buffer), (unsigned int)len)); + va_end(args); + return 0; +} + +static void prv_lock(void) { + OS_ENTER_CRITICAL_SECTION(); +} + +static void prv_unlock(void) { + OS_LEAVE_CRITICAL_SECTION(); +} + +static bool prv_pm_prepare_for_sleep(void) { + /* Do not sleep when there is transmission in progress */ + return !hw_uart_tx_in_progress(CONFIG_LOG_UART); +} + +static void prv_pm_sleep_canceled(void) { +} + +static void prv_pm_wake_up_ind(bool arg) { +} + +static void prv_pm_xtal16m_ready_ind(void) { + debug_uart_init(); +} + +// Called by Core Dump +void debug_uart_init(void) { + const uart_config uart_init = { + .baud_rate = CONFIG_LOG_UART_BAUDRATE, + .data = CONFIG_LOG_UART_DATABITS, + .stop = CONFIG_LOG_UART_STOPBITS, + .parity = CONFIG_LOG_UART_PARITY, + .use_fifo = 1, + .rx_dma_channel = CONFIG_LOG_UART_RX_DMA_CHANNEL, + .tx_dma_channel = CONFIG_LOG_UART_TX_DMA_CHANNEL, + }; + hw_uart_init(CONFIG_LOG_UART, &uart_init); + + hw_gpio_set_pin_function(HW_GPIO_PORT_1, HW_GPIO_PIN_0, HW_GPIO_MODE_OUTPUT, + HW_GPIO_FUNC_UART_RX); + hw_gpio_set_pin_function(HW_GPIO_PORT_1, HW_GPIO_PIN_1, HW_GPIO_MODE_OUTPUT, + HW_GPIO_FUNC_UART_TX); +} + +void pbl_log_init(void) { + debug_uart_init(); + + // Let's use all available RAM for the log buffer! + // That is, the difference between s_log_buffer_ram and RETENTION_BLE (__ble_vars_start__). + // This will always be at least sizeof(s_log_buffer_ram). + uint32_t start_addr = (uint32_t)s_log_buffer_ram; + uint32_t end_addr = (uint32_t)&__ble_vars_start__; + uint32_t available_ram = end_addr - start_addr; + + uint16_t circular_buffer_size = sizeof(s_log_buffer_ram); + if (circular_buffer_size < available_ram) { + circular_buffer_size = MIN(available_ram, USHRT_MAX); + } + + circular_buffer_init_ex(&s_log_buffer.circular_buffer, + s_log_buffer_ram, circular_buffer_size, + false /* auto_reset */); // Leave old data in the buffer for later debug + + static const adapter_call_backs_t s_ad_uart_pm_call_backs = { + .ad_prepare_for_sleep = prv_pm_prepare_for_sleep, + .ad_sleep_canceled = prv_pm_sleep_canceled, + .ad_wake_up_ind = prv_pm_wake_up_ind, + .ad_xtal16m_ready_ind = prv_pm_xtal16m_ready_ind, + .ad_sleep_preparation_time = 0 + }; + + pm_register_adapter(&s_ad_uart_pm_call_backs); +} + +static void prv_uart_tx_callback(void *user_data, uint16_t written) { + prv_lock(); + s_log_buffer.transmitting = false; + circular_buffer_consume(&s_log_buffer.circular_buffer, written); + + // Attempt to kick off another transmit + prv_start_tx(); + prv_unlock(); +} + +//! Must be in a critical section (interrupts off) when this function is called! +static void prv_start_tx(void) { + const uint8_t *data; + uint16_t length; + + // Don't start another TX if we're already writing + if (s_log_buffer.transmitting) { + return; + } + + // How much is available to transmit? + length = circular_buffer_get_read_space_remaining(&s_log_buffer.circular_buffer); + + if (length == 0) { + return; + } + + s_log_buffer.transmitting = true; + circular_buffer_read(&s_log_buffer.circular_buffer, length, &data, &length); + hw_uart_send(CONFIG_LOG_UART, data, length, prv_uart_tx_callback, NULL); +} + +static void prv_log_to_buffer(uint8_t *buffer, uint16_t length) { + prv_lock(); + if (!circular_buffer_write(&s_log_buffer.circular_buffer, buffer, length)) { + // Out of memory! Attempt to print an overrun message + circular_buffer_write(&s_log_buffer.circular_buffer, (uint8_t *)&OVERRUN.header, + sizeof(OVERRUN) - sizeof(HcProtocolMessage)); + } + prv_start_tx(); + prv_unlock(); +} + +#ifdef PBL_LOGS_HASHED + +void pbl_log_hashed(const uint32_t packed_loghash, ...) { + unsigned num_fmt_conversions = (packed_loghash >> PACKED_NUM_FMT_OFFSET) & PACKED_NUM_FMT_MASK; + unsigned str_index_1 = (packed_loghash >> PACKED_STR1FMT_OFFSET) & PACKED_STR1FMT_MASK; + unsigned str_index_2 = (packed_loghash >> PACKED_STR2FMT_OFFSET) & PACKED_STR2FMT_MASK; + unsigned level = (packed_loghash >> PACKED_LEVEL_OFFSET) & PACKED_LEVEL_MASK; + unsigned hash = (packed_loghash & PACKED_HASH_MASK) | + ((CORE_ID_BLE & PACKED_CORE_MASK) << PACKED_CORE_OFFSET); + + // Calculate the total message size. This is more complicated if there are strings + // Start with the header and the number of parameters. + unsigned num_strings = (str_index_1 ? 1 : 0) + (str_index_2 ? 1 : 0); + unsigned msg_size = sizeof(BinLogMessage_Param_v1) + + (num_fmt_conversions - num_strings) * sizeof(uint32_t); + + unsigned str1_len = 0; + unsigned str2_len = 0; + + // Now, count the string size, if necessary + if (num_strings) { + va_list args; + va_start(args, packed_loghash); + for (unsigned index = 0; index < num_fmt_conversions; ++index) { + const char *str = (const char *)va_arg(args, const char *); + if ((index + 1 == str_index_1) || (index + 1 == str_index_2)) { + unsigned len = strlen(str) + 1; // TODO: remove NULL termination + if ((index + 1) == str_index_1) { + str1_len = len; + } else { + str2_len = len; + } + } + } + va_end(args); + + // Make sure the strings don't blow our max message setting + if ((str1_len + str2_len) >= MAX_MSG_STR_LEN) { + str1_len = MIN(str1_len, MAX_MSG_STR_LEN_HALF); + str2_len = MIN(str2_len, MAX_MSG_STR_LEN_HALF); + } + + if (str1_len) { + // length + strlen(string) + padding (round up to nearest uint32_t). + msg_size += 1 + str1_len + (3 - (1 + str1_len + 3) % 4); + } + if (str2_len) { + // length + strlen(string) + padding (round up to nearest uint32_t). + msg_size += 1 + str2_len + (3 - (1 + str2_len + 3) % 4); + } + } + + // Buffer for UART ASCII log output + char expanded_fmt_buffer[64]; + memset(expanded_fmt_buffer, 0, sizeof(expanded_fmt_buffer)); + + // Craft a buffer that will hold HcProtocolMessage followed by BinLogMessage. + int msg_buffer_size = MAX((msg_size + sizeof(HcProtocolMessage)), 130); + uint8_t msg_buffer[msg_buffer_size]; + memset(msg_buffer, 0, msg_buffer_size); + + HcProtocolMessage *hc_msg = (HcProtocolMessage *)&msg_buffer[0]; + BinLogMessage_Param_v1 *msg = (BinLogMessage_Param_v1 *)&msg_buffer[sizeof(HcProtocolMessage)]; + + msg->header.version = BINLOGMSG_VERSION_PARAM_V1; + msg->header.length = msg_size; + msg->header.time.millisecond = LEVEL_MAP[level]; // HACK: Carry the level in the millisecond field + // msg->header.date & msg->header.time will be filled in on the host side + + // Set the message ID. Use as much as possible directly from the hash, then set the core & task. + msg->body.msgid.msg_id = (packed_loghash & MSGID_STR_AND_HASH_MASK); + msg->body.msgid.core_number = CORE_ID_BLE; + msg->body.msgid.task_id = task_get_dialogtask(); + + uint32_t *param = msg->body.payload; + + va_list args; + va_start(args, packed_loghash); + for (unsigned index = 0; index < num_fmt_conversions; ++index) { + if ((index + 1 == str_index_1) || (index + 1 == str_index_2)) { + BinLogMessage_StringParam *str_param = (BinLogMessage_StringParam *)param; + const char *str = (const char *)va_arg(args, const char *); + int str_len = ((index + 1) == str_index_1) ? str1_len : str2_len; + str_param->length = str_len; + + // TODO remove NULL termination + memcpy(str_param->string, str, str_len - 1); + str_param->string[str_len - 1] = '\0'; + + param += (str_len + 1 + 3) / sizeof(uint32_t); + + // For UART Output + strcat(expanded_fmt_buffer, " `%s`"); + } else { + *param++ = (uint32_t)va_arg(args, uint32_t); + + // For UART Output + strcat(expanded_fmt_buffer, " %x"); + } + } + va_end(args); + + // Log to host if the level is sufficient + if (LEVEL_MAP[level] <= s_log_buffer.level) { + // Craft the HcProtocolMessage & send to the host + hc_msg->message_length = sizeof(HcProtocolMessage) + msg->header.length; + if (!hc_endpoint_logging_send_msg(hc_msg)) { + // Attempt to send an OVERRUN message. + hc_endpoint_logging_send_msg((HcProtocolMessage *)&OVERRUN.hc_header); + } + } + + + // Re-use the msg_buffer. + va_start(args, packed_loghash); + int header_length = snprintf((char *)msg_buffer, msg_buffer_size, ":0> NL:%x", hash); + int length = vsnprintf((char *)msg_buffer + header_length, msg_buffer_size - header_length, + expanded_fmt_buffer, args); + length += header_length; + va_end(args); + + if ((length + 2) >= msg_buffer_size) { + length -= 2; + } + msg_buffer[length++] = '\r'; + msg_buffer[length++] = '\n'; + + prv_log_to_buffer((uint8_t *)msg_buffer, length); +} + +#else // PBL_LOGS_HASHED + +static char prv_get_log_level_char(uint8_t log_level) { + switch (log_level) { + case LOG_LEVEL_ALWAYS: return 'A'; + case LOG_LEVEL_ERROR: return 'E'; + case LOG_LEVEL_WARNING: return 'W'; + case LOG_LEVEL_INFO: return 'I'; + case LOG_LEVEL_DEBUG: return 'D'; + case LOG_LEVEL_DEBUG_VERBOSE: return 'V'; + default: return '?'; + } +} + +void pbl_log(uint8_t log_level, const char *src_filename, int src_line_number, + const char *fmt, ...) { + // Craft a buffer that will hold HcProtocolMessage followed by BinLogMessage. + unsigned int msg_buffer_size = MAX((MAX_MSG_LEN + sizeof(HcProtocolMessage) + 40), 130); + uint8_t msg_buffer[msg_buffer_size]; + memset(msg_buffer, 0, msg_buffer_size); + + HcProtocolMessage *hc_msg = (HcProtocolMessage *)&msg_buffer[0]; + BinLogMessage_Unhashed_v1 *msg = (BinLogMessage_Unhashed_v1 *) + &msg_buffer[sizeof(HcProtocolMessage)]; + + msg->header.version = BINLOGMSG_VERSION_UNHASHED_V1; + // HACK: store the log level in time.millisecond + msg->header.time.millisecond = log_level; + // msg->header.date & msg->header.time will be filled in on the host side + + msg->body.line_number = src_line_number; + strncpy((char *)msg->body.filename, src_filename, + MEMBER_SIZE(BinLogMessage_UnhashedBody, filename)); + msg->body.core_number = CORE_ID_BLE; + msg->body.task_id = task_get_dialogtask(); + msg->body.level = log_level; + + // Temporarily reserve space for a NULL terminator + // TODO: remove NULL termination + uint8_t max_str_len = msg_buffer_size - sizeof(BinLogMessage_Unhashed_v1) - 1; + + va_list args; + va_start(args, fmt); + msg->body.length = vsnprintf((char *)msg->body.string, max_str_len, fmt, args); + va_end(args); + + // Temporarily force NULL termination. The buffer is already set to zero -- increment the length + // by one to reflect the increased string length + // TODO: remove NULL termination + msg->body.length++; + + // Calculate the total packet length. + msg->header.length = sizeof(BinLogMessage_Unhashed_v1) + msg->body.length; + // Rounding up to the nearest uint32_t + msg->header.length = (msg->header.length + (sizeof(uint32_t) - 1)) & ~(sizeof(uint32_t) - 1); + + // Log to host if the level is sufficient + if (LEVEL_MAP[log_level] <= s_log_buffer.level) { + // Craft the HcProtocolMessage & send to the Host. + hc_msg->message_length = sizeof(HcProtocolMessage) + msg->header.length; + if (!hc_endpoint_logging_send_msg(hc_msg)) { + // Attempt to send an OVERRUN message. + hc_endpoint_logging_send_msg((HcProtocolMessage *)&OVERRUN.hc_header); + } + } + + // Send the unhashed string to the UART buffer + size_t filename_max_len = MEMBER_SIZE(BinLogMessage_UnhashedBody, filename); + size_t src_filename_len = strnlen((const char *)msg->body.filename, filename_max_len); + size_t pad_length = (src_filename_len < filename_max_len) ? + (filename_max_len - src_filename_len) : 1; + + // Re-use the msg_buffer. + int length = snprintf((char *)msg_buffer, msg_buffer_size, "%c %d %*c%.*s:%3i> %.*s", + prv_get_log_level_char(msg->body.level), msg->body.task_id, + pad_length, ' ', filename_max_len, msg->body.filename, + msg->body.line_number, msg->body.length, msg->body.string); + + const unsigned length_with_null = length + 1; + if (length_with_null >= msg_buffer_size) { + length--; + } + msg_buffer[length++] = '\n'; + + prv_log_to_buffer((uint8_t *)msg_buffer, length); +} + +#endif // PBL_LOGS_HASHED diff --git a/src/bluetooth-fw/da1468x/controller/main/src/main.c b/src/bluetooth-fw/da1468x/controller/main/src/main.c new file mode 100644 index 00000000..97dbf497 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/main.c @@ -0,0 +1,213 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "chip_id.h" +#include "hc_protocol/hc_protocol.h" +#include "host_transport_impl.h" +#include "kernel/pbl_malloc.h" +#include "power.h" +#include "system/logging.h" +#include "tasks.h" + +#include +#include + +// Dialog SDK: +#include "ad_ble.h" +#include "ad_nvms.h" +#include "ad_spi.h" +#include "ble_mgr.h" +#include "hw_gpio.h" +#include "hw_watchdog.h" +#include "osal.h" +#include "sys_clock_mgr.h" +#include "sys_power_mgr.h" +#include "sys_watchdog.h" + +/* The configCHECK_FOR_STACK_OVERFLOW setting in FreeRTOSConifg can be used to +check task stacks for overflows. It does not however check the stack used by +interrupts. This demo has a simple addition that will also check the stack used +by interrupts if mainCHECK_INTERRUPT_STACK is set to 1. Note that this check is +only performed from the tick hook function (which runs in an interrupt context). +It is a good debugging aid - but won't catch interrupt stack problems until the +tick interrupt next executes. */ +// #define mainCHECK_INTERRUPT_STACK 1 +#if mainCHECK_INTERRUPT_STACK == 1 +const unsigned char ucExpectedInterruptStackValues[] = { + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC +}; +#endif + + +/* + * Perform any application specific hardware configuration. The clocks, + * memory, etc. are configured before main() is called. + */ +static void prvSetupHardware( void ); +/* + * Task functions . + */ + +static void prv_print_banner(bool pre_host_logging) { + if (pre_host_logging) { + PBL_LOG(LOG_LEVEL_ALWAYS, "*** Main BLE FW booting... (pre-host logging) ***"); + } else { + PBL_LOG(LOG_LEVEL_ALWAYS, "*** Main BLE Controller booted in %d ms... ***", + (int)ticks_to_milliseconds(xTaskGetTickCount())); + } + + DialogChipID chip_id = {}; + dialog_chip_id_copy(&chip_id); + PBL_LOG(LOG_LEVEL_DEBUG, + "Chip ID='%s', ts=%"PRIu32", pkg=0x%"PRIx8", wafer=%"PRIu8", x=%"PRIu8", y=%"PRIu8"", + chip_id.chip_id, chip_id.info.timestamp, chip_id.info.package_type, + chip_id.info.wafer_number, chip_id.info.x_coord, chip_id.info.y_coord); +} + +/** + * @brief System Initialization and creation of the BLE task + */ +static void system_init( void *pvParameters ) { + /* Prepare clocks. Note: cm_cpu_clk_set() and cm_sys_clk_set() can be called only from a + * task since they will suspend the task until the XTAL16M has settled and, maybe, the PLL + * is locked. + */ + cm_sys_clk_init(sysclk_XTAL16M); + cm_apb_set_clock_divider(apb_div1); + cm_ahb_set_clock_divider(ahb_div1); + cm_lp_clk_init(); + + /* Prepare the hardware to run this demo. */ + prvSetupHardware(); + + /* Set system clock */ + cm_sys_clk_set(sysclk_XTAL16M); + + // Set the desired sleep mode. + // It's important to do this *AFTER* setting up the system clock. This is because cm_sys_clk_set() + // has the potential to block this task while waiting for the xtal to settle. When using deep + // sleep, you'd fall into an eternal sleep because the "xtal ready" interrupt doesn't wake it up. + power_init(); + + pbl_log_init(); + + prv_print_banner(true); + + // This registers the power manager callbacks for the SPI adapter. + ad_spi_init(); + + /* Initialize BLE Adapter */ + ad_ble_init(); + + /* Initialize BLE Manager */ + ble_mgr_init(); + + host_transport_init(); + + prv_print_banner(false); + + /* the work of the SysInit task is done */ + task_unregister_task(OS_GET_CURRENT_TASK()); + OS_TASK_DELETE(OS_GET_CURRENT_TASK()); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Basic initialization and creation of the system initialization task. + */ +uint8_t *ucHeap; +static void prv_init_main_heap(void) { + // We get the location of the heap from a variable provided by the linker script. + // The heap is zero-initialized by startup_ARMCM0.S. + extern uint8_t __heap_start; + ucHeap = &__heap_start; +} + +int main( void ) { + prv_init_main_heap(); + + OS_BASE_TYPE status; + + cm_clk_init_low_level(); /* Basic clock initializations. */ + + /* Initialize BLE Heap */ + kernel_heap_init(); + + sys_watchdog_init(); + + /* Start SysInit task. */ + status = OS_TASK_CREATE("SI", /* The text name assigned to the task, for + debug only; not used by the kernel. */ + system_init, /* The System Initialization task. */ + ( void * ) 0, /* The parameter passed to the task. */ + 1024, /* The number of bytes to allocate to the + stack of the task. */ + configMAX_PRIORITIES - 1, /* The priority assigned to the task. */ + DialogTaskList[DialogTask_SysInit]); /* The task handle */ + configASSERT(status == OS_TASK_CREATE_SUCCESS); + + /* Start the tasks and timer running. */ + + vTaskStartScheduler(); + + /* If all is well, the scheduler will now be running, and the following + line will never be reached. If the following line does execute, then + there was insufficient FreeRTOS heap memory available for the idle and/or + timer tasks to be created. See the memory management section on the + FreeRTOS web site for more details. */ + for( ;; ); +} + + +static void prv_periph_setup(void) { + host_transport_init_periph(); +} + +static void prvSetupHardware( void ) +{ +#if mainCHECK_INTERRUPT_STACK == 1 + extern unsigned long _vStackTop[], _pvHeapStart[]; + unsigned long ulInterruptStackSize; +#endif + + /* Init hardware */ + pm_system_init(prv_periph_setup); + +#if mainCHECK_INTERRUPT_STACK == 1 + /* The size of the stack used by main and interrupts is not defined in + the linker, but just uses whatever RAM is left. Calculate the amount of + RAM available for the main/interrupt/system stack, and check it against + a reasonable number. If this assert is hit then it is likely you don't + have enough stack to start the kernel, or to allow interrupts to nest. + Note - this is separate to the stacks that are used by tasks. The stacks + that are used by tasks are automatically checked if + configCHECK_FOR_STACK_OVERFLOW is not 0 in FreeRTOSConfig.h - but the stack + used by interrupts is not. Reducing the conifgTOTAL_HEAP_SIZE setting will + increase the stack available to main() and interrupts. */ + ulInterruptStackSize = ( ( unsigned long ) _vStackTop ) - ( ( unsigned long ) _pvHeapStart ); + OS_ASSERT( ulInterruptStackSize > 350UL ); + + /* Fill the stack used by main() and interrupts to a known value, so its + use can be manually checked. */ + memcpy( ( void * ) _pvHeapStart, ucExpectedInterruptStackValues, sizeof( ucExpectedInterruptStackValues ) ); +#endif +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/passert.c b/src/bluetooth-fw/da1468x/controller/main/src/passert.c new file mode 100644 index 00000000..cdec8836 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/passert.c @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#include "system/passert.h" + +#include "core_dump.h" +#include "die.h" +#include "reboot_reason.h" + +#include + +NORETURN passert_failed_no_message(void) { + passert_failed_no_message_with_lr((uint32_t)__builtin_return_address(0)); +} + +NORETURN passert_failed_no_message_with_lr(uint32_t lr) { + RebootReason reason = { + .code = RebootReasonCode_Assert, + .extra = lr, + }; + reboot_reason_set(&reason); + + printf("ASSERT! lr=0x%lx\n", lr); + reset_due_to_software_failure(); +} + +NORETURN passert_rom_error_no_message_with_errortype(uint32_t error_type_stat) { + RebootReason reason = { + .code = RebootReasonCode_RomError, + .extra = error_type_stat, + }; + reboot_reason_set(&reason); + + printf("RomErr! stat=0x%lx\n", error_type_stat); + reset_due_to_software_failure(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/patches.c b/src/bluetooth-fw/da1468x/controller/main/src/patches.c new file mode 100644 index 00000000..731d7d7f --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/patches.c @@ -0,0 +1,214 @@ +/* + * 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. + */ + +#include "system/passert.h" +#include "system/logging.h" + +// Dialog SDK: +#include "ble_common.h" +#include "dbg_swdiag.h" +#include "gapc.h" +#include "ke_timer.h" +#include "llc.h" +#include "llc_data.h" +#include "llc_util.h" +#include "lld_data.h" +#include "llm_util.h" + +#include + +#include +#include + +#define IS_AE_CHIP ((dg_configBLACK_ORCA_IC_REV == BLACK_ORCA_IC_REV_A) && \ + (dg_configBLACK_ORCA_IC_STEP == BLACK_ORCA_IC_STEP_E)) + +//! Defined in sdk/interfaces/ble_stack/(Debug|Release)/libble_stack.a +//! Also see https://github.com/pebble/bt-dialog-source/blob/master/ble_stack/src/rom_patch/rom_patch.c#L2687 +extern ble_error_t __real_patch_rom_functions(void); + +static bool s_should_log_about_mic_error = false; +static uint32_t s_max_subsequent_error_count_since_last_log = 0; +static uint32_t s_subsequent_mic_errors = 0; +#define MAX_SUBSEQUENT_MIC_ERRORS (16) + +bool should_log_about_mic_error(uint32_t *max_subsequent_failures) { + if (s_should_log_about_mic_error) { + *max_subsequent_failures = s_max_subsequent_error_count_since_last_log; + s_should_log_about_mic_error = false; + s_max_subsequent_error_count_since_last_log = 0; + return true; + } + + return false; +} + +static void prv_mic_error_resilience_workaround(struct co_buf_rx_desc *rxdesc) { + // MT: PBL-39297 -- Qualcomm WCN3660/WCN3680's encryption goes off in the weeds + // occassionally for a couple packets... + if (rxdesc->rxstatus & BLE_MIC_ERR_BIT) { + if (++s_subsequent_mic_errors > s_max_subsequent_error_count_since_last_log) { + s_max_subsequent_error_count_since_last_log = s_subsequent_mic_errors; + } + + if (s_subsequent_mic_errors <= MAX_SUBSEQUENT_MIC_ERRORS) { + // Pretend the MIC error was a CRC so the upper layers will ignore the packet: + rxdesc->rxstatus &= ~BLE_MIC_ERR_BIT; + rxdesc->rxstatus |= BLE_CRC_ERR_BIT; + } else { + // We are going to disconnect as a result of MIC errors, let's generate a log about this + s_should_log_about_mic_error = true; + } + } else { + if (s_subsequent_mic_errors != 0) { + // We have recovered from the MIC error issue but let's log how many bad packets in a row we + // received + s_should_log_about_mic_error = true; + s_subsequent_mic_errors = 0; + } + } +} + +static void prv_lld_data_rx_check(struct lld_evt_tag *evt, + struct lld_data_ind *msg, + uint8_t rx_cnt) { + uint8_t hdl = co_buf_rx_current_get(); + + // Initialize the message + msg->rx_cnt = rx_cnt; + msg->rx_hdl = hdl; + + //Get the event counter + msg->evt_cnt = evt->counter; + + // If required, copy the received buffers from exchange memory to system RAM + while (rx_cnt--) + { +#if (BLE_PERIPHERAL) + struct co_buf_rx_desc *rxdesc = co_buf_rx_get(hdl); + + prv_mic_error_resilience_workaround(rxdesc); + + // If we are waiting for the acknowledgment, and it is received, enable the slave + // latency + if (LLD_EVT_FLAG_GET(evt, WAITING_ACK) && !(rxdesc->rxstatus & BLE_NESN_ERR_BIT)) + { + // We received the acknowledgment + LLD_EVT_FLAG_RESET(evt, WAITING_ACK); + } +#endif //(BLE_PERIPHERAL) + + // Go to the next descriptor + hdl = co_buf_rx_next(hdl); + }; + + // Move the current RX buffer + co_buf_rx_current_set(hdl); +} + +#if IS_AE_CHIP +static void prv_llm_util_get_supp_features(struct le_features *feats) { + memcpy(&feats->feats[0], &llm_local_le_feats.feats[0], LE_FEATS_LEN); + feats->feats[0] &= ~BLE_CON_PARAM_REQ_PROC_FEATURE; +#if !SUPPORTS_PACKET_LENGTH_EXTENSION + feats->feats[0] &= ~BLE_LE_LENGTH_FEATURE; +#endif +} + +#if !SUPPORTS_PACKET_LENGTH_EXTENSION +void llc_pdu_send_func(uint16_t conhdl, uint8_t length); + +// This is called in the ROM by llcp_length_req_handler(). We want to pretend to the remote that we +// do not support the feature. To do this we progress the dialog state as if things are normal but +// when we respond to the remote we pretend the message was not understood. This should be a valid +// response per v4.2 "5.1.9 Data Length Update Procedure" +static void prv_llc_length_rsp_pdu_send(uint16_t conhdl) +{ + LLC_FLAG_RESET(conhdl, LE_LENGTH_REQ_PEND); + + // Get the TX buffer to be used + struct co_buf_tx_desc *txdesc = co_buf_tx_desc_get(LLC_LE_CNTL_PKT_BASE_IDX + conhdl); + struct llcp_unknown_rsp *data = (struct llcp_unknown_rsp *)co_buf_tx_buffer_get(txdesc); + + // Build the Unknown Response PDU + data->opcode = LL_UNKNOWN_RSP; + data->unk_type = LL_LENGTH_REQ; + + // Send the LLCP PDU + llc_pdu_send_func(conhdl, LL_UNKN_RSP_LEN); +} +#endif + +#endif + +static const uint32_t s_orig_functions[] = { +#if IS_AE_CHIP + (uint32_t) llm_util_get_supp_features, +#if !SUPPORTS_PACKET_LENGTH_EXTENSION + (uint32_t) llc_length_rsp_pdu_send, +#endif +#endif + (uint32_t) lld_data_rx_check, +}; + +static const uint32_t s_patched_functions[] = { +#if IS_AE_CHIP + (uint32_t) prv_llm_util_get_supp_features, +#if !SUPPORTS_PACKET_LENGTH_EXTENSION + (uint32_t) prv_llc_length_rsp_pdu_send, +#endif +#endif + (uint32_t) prv_lld_data_rx_check, +}; + +_Static_assert(ARRAY_LENGTH(s_orig_functions) == ARRAY_LENGTH(s_patched_functions), ""); + +void __wrap_patch_rom_functions(void) { + // Disable all patches: + PATCH->PATCH_VALID_RESET_REG = ~0; + + // Execute original first: + __real_patch_rom_functions(); + + const size_t max_patches = 28; + const size_t bits_in_reg = sizeof(uint32_t) * 8; + uint32_t patch_enabled_bits = PATCH->PATCH_VALID_REG; + // Use count leading zeroes, assuming the LSB slots are used first. + size_t slots_left = __builtin_clz(patch_enabled_bits) - (bits_in_reg - max_patches); + + size_t num_patches = ARRAY_LENGTH(s_patched_functions); + PBL_ASSERTN(slots_left >= num_patches); + + // Write PATCH registers: + int idx = max_patches - slots_left; + uint32_t *addr_reg = ((uint32_t *)&PATCH->PATCH_ADDR0_REG) + (2 * idx); + const uint32_t *patch_function = s_orig_functions; + while (num_patches) { + *addr_reg = *patch_function; + addr_reg += 2; + patch_enabled_bits |= (1 << idx); + ++patch_function; + --num_patches; + idx++; + } + + // Write vector table: + idx = max_patches - slots_left; + uint32_t *vec = ((uint32_t *)0x7fc00c0) + idx; + memcpy(vec, s_patched_functions, sizeof(s_patched_functions)); + + PATCH->PATCH_VALID_SET_REG = patch_enabled_bits; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/pebble_pairing_service.c b/src/bluetooth-fw/da1468x/controller/main/src/pebble_pairing_service.c new file mode 100644 index 00000000..4ac63d92 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/pebble_pairing_service.c @@ -0,0 +1,589 @@ +/* + * 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. + */ + +#include "pebble_pairing_service_impl.h" +#include "connection.h" +#include "hc_protocol/hc_endpoint_pebble_pairing_service.h" +#include "system/logging.h" +#include "system/passert.h" + +#include +#include +#include +#include +#include +#include + +// Dialog SDK + +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_service.h" +#include "ble_uuid.h" +#include "storage.h" + +#include +#include + +#define READ_RESPONSE_BUFFER_SIZE \ + (MAX(sizeof(PebblePairingServiceConnParamsReadNotif), \ + sizeof(PebblePairingServiceConnectivityStatus))) +#define ATT_DEFAULT_MTU (23) + +typedef struct { + ble_service_t svc; + + //! ATT handles + struct { + uint16_t conn_status; + uint16_t conn_status_cccd; + uint16_t trigger_pairing; + uint16_t gatt_mtu; + uint16_t gatt_mtu_cccd; + uint16_t conn_params; + uint16_t conn_params_cccd; + } att_hdl; +} PebblePairingServiceCtx; + +static PebblePairingServiceCtx s_pps_ctx; + +static bool prv_is_encrypted(uint16_t conn_idx) { + gap_sec_level_t level = GAP_SEC_LEVEL_1; + ble_error_t e = ble_gap_get_sec_level(conn_idx, &level); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_get_sec_level: %u", e); + return false; + } + return (level > GAP_SEC_LEVEL_1); +} + +static void prv_set_gateway(Connection *connection, bool is_gateway) { + const uint16_t conn_idx = connection_get_idx(connection); + + // Set for Connection + connection_set_gateway(connection, is_gateway); + + // Set for `device_t` + storage_acquire(); + device_t *dev = find_device_by_conn_idx(conn_idx); + if (dev) { + dev->is_gateway = is_gateway; + } + storage_release(); + + BTDeviceInternal device; + connection_get_address(connection, &device); + hc_endpoint_pebble_pairing_service_found_gateway(&device); +} + +static bool prv_is_bonded(uint16_t conn_idx) { + storage_acquire(); + device_t *dev = find_device_by_conn_idx(conn_idx); + const bool is_bonded = dev ? dev->bonded : false; + storage_release(); + return is_bonded; +} + +static void prv_device_foreach_search_gateway(const device_t *dev, void *ud) { + bool *has_bonded_gateway = ud; + if (dev->is_gateway) { + *has_bonded_gateway = true; + } +} + +static bool prv_has_bonded_gateway(void) { + bool has_bonded_gateway = false; + + storage_acquire(); + device_foreach(prv_device_foreach_search_gateway, &has_bonded_gateway); + storage_release(); + + return has_bonded_gateway; +} + +extern bool ppogatt_emulated_server_wa_enabled(uint16_t conn_idx); +static void prv_get_connectivity_status(uint16_t conn_idx, + PebblePairingServiceConnectivityStatus *status_out) { + const bool is_encrypted = prv_is_encrypted(conn_idx); + const bool is_bonded = prv_is_bonded(conn_idx); + + *status_out = (PebblePairingServiceConnectivityStatus) { + .ble_is_encrypted = is_encrypted, + .ble_is_bonded = is_bonded, + .ble_is_connected = true, + .has_bonded_gateway = prv_has_bonded_gateway(), + .supports_pinning_without_security_request = true, + .last_pairing_result = connection_get_last_pairing_result(conn_idx), + .is_reversed_ppogatt_enabled = ppogatt_emulated_server_wa_enabled(conn_idx) + }; +} + +static uint8_t prv_handle_connection_state_read(uint16_t conn_idx, + uint8_t response_buf[READ_RESPONSE_BUFFER_SIZE]) { + PebblePairingServiceConnectivityStatus status = {}; + prv_get_connectivity_status(conn_idx, &status); + memcpy(response_buf, &status, sizeof(status)); + return sizeof(status); +} + +static uint8_t prv_handle_gatt_mtu_read(uint16_t conn_idx, + uint8_t response_buffer[READ_RESPONSE_BUFFER_SIZE]) { + uint16_t mtu = ATT_DEFAULT_MTU; + ble_error_t e = ble_gattc_get_mtu(conn_idx, &mtu); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gattc_get_mtu: %u", e); + } + memcpy(response_buffer, &mtu, sizeof(mtu)); + return sizeof(mtu); +} + +static uint8_t prv_handle_conn_params_read(uint16_t conn_idx, + uint8_t response_buffer[READ_RESPONSE_BUFFER_SIZE]) { + Connection *connection = connection_by_idx_check(conn_idx); + BleConnectionParams params; + connection_get_conn_params(connection, ¶ms); + PebblePairingServiceConnParamsReadNotif *resp = + (PebblePairingServiceConnParamsReadNotif *)response_buffer; +#if SUPPORTS_PACKET_LENGTH_EXTENSION + resp->packet_length_extension_supported = true; +#else + resp->packet_length_extension_supported = false; +#endif + resp->rsvd = 0; + resp->current_interval_1_25ms = params.conn_interval_1_25ms; + resp->current_slave_latency_events = params.slave_latency_events; + resp->current_supervision_timeout_10ms = params.supervision_timeout_10ms; + return sizeof(PebblePairingServiceConnParamsReadNotif); +} + +static void prv_handle_trigger_pairing(uint16_t conn_idx, const PairingTriggerRequestData *req) { + PBL_LOG(LOG_LEVEL_DEBUG, + "Trigger pairing! should_pin_address=%d, no_slave_security_request=%d, auto_acc=%d " + "rev_ppogatt=%d", + req->should_pin_address, req->no_slave_security_request, + req->should_auto_accept_re_pairing, + req->is_reversed_ppogatt_enabled); + gap_sec_level_t level = GAP_SEC_LEVEL_3; + if (prv_is_encrypted(conn_idx)) { + PBL_LOG(LOG_LEVEL_INFO, "Link already encrypted!"); + if (!req->should_force_slave_security_request) { + return; + } + // Make sure to request the same security level when sending a request to refresh encryption: + ble_gap_get_sec_level(conn_idx, &level); + } + + Connection *connection = connection_by_idx(conn_idx); + connection_set_should_pin_address(connection, req->should_pin_address); + connection_set_should_auto_accept_re_pairing(connection, req->should_auto_accept_re_pairing); + connection_set_reversed_ppogatt_enabled(connection, req->is_reversed_ppogatt_enabled); + + if (!req->no_slave_security_request) { + PBL_LOG(LOG_LEVEL_INFO, "Trying to establish security %u", level); + ble_error_t e = ble_gap_set_sec_level(conn_idx, level); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gap_set_sec_level: %u", e); + } + } +} + +static uint8_t prv_handle_handle_cccd_read(uint16_t handle, uint16_t conn_idx, + uint8_t response_buffer[READ_RESPONSE_BUFFER_SIZE]) { + Connection *connection = connection_by_idx_check(conn_idx); + bool notifications_enabled = false; + if (handle == s_pps_ctx.att_hdl.conn_status_cccd) { + notifications_enabled = connection_is_subscribed_to_connection_status_notifications(connection); + } else if (handle == s_pps_ctx.att_hdl.gatt_mtu_cccd) { + notifications_enabled = connection_is_subscribed_to_gatt_mtu_notifications(connection); + } + const uint16_t cccd_value = notifications_enabled ? GATT_CCC_NOTIFICATIONS : 0; + memcpy(response_buffer, &cccd_value, sizeof(cccd_value)); + return sizeof(cccd_value); +} + +static bool prv_get_value(uint16_t conn_idx, uint16_t value_handle, + uint8_t response[READ_RESPONSE_BUFFER_SIZE], + size_t *response_length_out) { + if (value_handle == s_pps_ctx.att_hdl.conn_status) { + *response_length_out = prv_handle_connection_state_read(conn_idx, response); + } else if (value_handle == s_pps_ctx.att_hdl.gatt_mtu) { + *response_length_out = prv_handle_gatt_mtu_read(conn_idx, response); + } else if (value_handle == s_pps_ctx.att_hdl.conn_params) { + *response_length_out = prv_handle_conn_params_read(conn_idx, response); + } else { + return false; + } + return true; +} + +static void prv_pps_handle_read_request(ble_service_t *svc, const ble_evt_gatts_read_req_t *evt) { + uint8_t response[READ_RESPONSE_BUFFER_SIZE] = {}; + size_t response_length = 0; + att_error_t response_status = ATT_ERROR_OK; + + if (evt->handle == s_pps_ctx.att_hdl.trigger_pairing) { + const PairingTriggerRequestData request = { + .should_pin_address = false, + .no_slave_security_request = false, + }; + prv_handle_trigger_pairing(evt->conn_idx, &request); + } else if (evt->handle == s_pps_ctx.att_hdl.conn_status_cccd || + evt->handle == s_pps_ctx.att_hdl.gatt_mtu_cccd || + evt->handle == s_pps_ctx.att_hdl.conn_params_cccd) { + response_length = prv_handle_handle_cccd_read(evt->handle, evt->conn_idx, response); + } else if (!prv_get_value(evt->conn_idx, evt->handle, response, &response_length)) { + PBL_LOG(LOG_LEVEL_ERROR, "Reading unhandled handle: %u !", evt->handle); + response_status = ATT_ERROR_INVALID_HANDLE; + } + + ble_error_t e = ble_gatts_read_cfm(evt->conn_idx, evt->handle, response_status, + response_length, response); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gatts_read_cfm: %u", e); + } +} + +static void prv_send_notification(uint16_t conn_idx, uint16_t value_handle) { + uint8_t data[READ_RESPONSE_BUFFER_SIZE] = {}; + size_t length = 0; + + if (!prv_get_value(conn_idx, value_handle, data, &length)) { + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected handle! %u", value_handle); + return; + } + + ble_error_t e = ble_gatts_send_event(conn_idx, value_handle, + GATT_EVENT_NOTIFICATION, length, data); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gatts_send_event: %u", e); + } +} + +static att_error_t prv_handle_cccd_write(const ble_evt_gatts_write_req_t *evt) { + if (evt->length < sizeof(uint16_t)) { + PBL_LOG(LOG_LEVEL_ERROR, "Invalid CCCD value length %u!", evt->length); + return ATT_ERROR_INVALID_VALUE_LENGTH; + } + uint16_t cccd_value = 0; + memcpy(&cccd_value, evt->value, sizeof(cccd_value)); + bool is_subscribed = (cccd_value & GATT_CCC_NOTIFICATIONS); + Connection *connection = connection_by_idx_check(evt->conn_idx); + uint16_t value_handle; + if (evt->handle == s_pps_ctx.att_hdl.conn_status_cccd) { + connection_set_subscribed_to_connection_status_notifications(connection, is_subscribed); + value_handle = s_pps_ctx.att_hdl.conn_status; + // If the device has subscribed to this characteristic, we assume that it's the gateway or the + // phone that is running the Pebble app: + prv_set_gateway(connection, true); + + if (!is_subscribed) { + // The CoreBluetooth central that the iOS app uses to subscribe to the Connection Status + // characteristic does not use state restoration. Therefore, when the app crashes / is + // jetsam'd, iOS will unsubscribe from the characteristic. Use this as a trigger to re-launch + // the app: + PBL_LOG(LOG_LEVEL_INFO, "Characteristic got unsubscribed, triggering app launch!"); + hc_endpoint_pebble_pairing_service_send_ios_app_termination_detected(); + } + } else if (evt->handle == s_pps_ctx.att_hdl.gatt_mtu_cccd) { + connection_set_subscribed_to_gatt_mtu_notifications(connection, is_subscribed); + value_handle = s_pps_ctx.att_hdl.gatt_mtu; + } else if (evt->handle == s_pps_ctx.att_hdl.conn_params_cccd) { + connection_set_subscribed_to_conn_param_notifications(connection, is_subscribed); + value_handle = s_pps_ctx.att_hdl.conn_params; + } else { + PBL_LOG(LOG_LEVEL_ERROR, "Unknown CCCD handle %u!", evt->handle); + return ATT_ERROR_INVALID_HANDLE; + } + + if (is_subscribed) { + prv_send_notification(evt->conn_idx, value_handle); + } + + return ATT_ERROR_OK; +} + +static att_error_t prv_handle_mtu_value_write(const ble_evt_gatts_write_req_t *evt) { + if (evt->length != sizeof(uint16_t)) { + PBL_LOG(LOG_LEVEL_ERROR, "Invalid GATT MTU size %u!", evt->length); + return ATT_ERROR_INVALID_VALUE_LENGTH; + } + // https://pebbletechnology.atlassian.net/browse/PBL-34474: + // we ought to change our local MTU according to the requested MTU size. + // However, if we call ble_gap_mtu_size_set() here, we'd reset the ATT table... :( + // For now, stick to the configured maximum and just re-negotiate: + ble_error_t e = ble_gattc_exchange_mtu(evt->conn_idx); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gattc_exchange_mtu: %u", e); + return ATT_ERROR_UNLIKELY; + } + return ATT_ERROR_OK; +} + +static att_error_t prv_validate_set_remote_param_mgmt_settings_cmd( + const PebblePairingServiceRemoteParamMgmtSettings *settings, size_t length) { + if (length < sizeof(PebblePairingServiceRemoteParamMgmtSettings)) { + return ATT_ERROR_INVALID_VALUE_LENGTH; + } + bool has_param_set = + (length >= PEBBLE_PAIRING_SERVICE_REMOTE_PARAM_MGTM_SETTINGS_SIZE_WITH_PARAM_SETS); + if (has_param_set) { + for (int i = 0; i < NumResponseTimeState; ++i) { + if (settings->connection_parameter_sets[i].interval_min_1_25ms < LL_CONN_INTV_MIN_SLOTS) { + return (att_error_t)PebblePairingServiceGATTError_ConnParamsMinSlotsTooSmall; + } + if (settings->connection_parameter_sets[i].interval_min_1_25ms > LL_CONN_INTV_MAX_SLOTS) { + return (att_error_t)PebblePairingServiceGATTError_ConnParamsMinSlotsTooLarge; + } + if (settings->connection_parameter_sets[i].interval_min_1_25ms + + settings->connection_parameter_sets[i].interval_max_delta_1_25ms > + LL_CONN_INTV_MAX_SLOTS) { + return (att_error_t)PebblePairingServiceGATTError_ConnParamsMaxSlotsTooLarge; + } + if (settings->connection_parameter_sets[i].supervision_timeout_30ms * 30 < + LL_SUPERVISION_TIMEOUT_MIN_MS) { + return (att_error_t)PebblePairingServiceGATTError_ConnParamsSupervisionTimeoutTooSmall; + } + } + } + return ATT_ERROR_OK; +} + +static att_error_t prv_validate_set_desired_state_cmd( + const PebblePairingServiceRemoteDesiredState *desired_state, size_t length) { + if (length < sizeof(PebblePairingServiceRemoteDesiredState)) { + return ATT_ERROR_INVALID_VALUE_LENGTH; + } + if (desired_state->state >= NumResponseTimeState) { + return (att_error_t)PebblePairingServiceGATTError_ConnParamsInvalidRemoteDesiredState; + } + return ATT_ERROR_OK; +} + +static att_error_t prv_handle_ple_update_request( + uint16_t conn_idx, const PebblePairingServicePacketLengthExtension *ple_req, size_t length) { + if (length < sizeof(PebblePairingServicePacketLengthExtension)) { + return ATT_ERROR_INVALID_VALUE_LENGTH; + } + +#if !SUPPORTS_PACKET_LENGTH_EXTENSION + return ((att_error_t)PebblePairingServiceGATTError_DeviceDoesNotSupportPLE); +#else + if (ple_req->trigger_ll_length_req) { + extern void hci_initiate_length_change(uint16_t conn_idx); + hci_initiate_length_change(conn_idx); + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "PLE call a no-op"); + } + + return ATT_ERROR_OK; +#endif +} + +extern void power_inhibit_sleep(void); + +static att_error_t prv_handle_conn_params_write(const ble_evt_gatts_write_req_t *evt) { + Connection *connection = connection_by_idx(evt->conn_idx); + if (!connection) { + return ATT_ERROR_UNLIKELY; + } + + att_error_t rv; + + if (evt->length == 0) { + rv = ATT_ERROR_INVALID_VALUE_LENGTH; + goto finally; + } + + const PebblePairingServiceConnParamsWrite *conn_params = + (const PebblePairingServiceConnParamsWrite *)evt->value; + + const size_t length = (evt->length - offsetof(PebblePairingServiceConnParamsWrite, + remote_desired_state)); + switch (conn_params->cmd) { + case PebblePairingServiceConnParamsWriteCmd_SetRemoteParamMgmtSettings: + rv = prv_validate_set_remote_param_mgmt_settings_cmd(&conn_params->remote_param_mgmt_settings, + length); + break; + case PebblePairingServiceConnParamsWriteCmd_SetRemoteDesiredState: + rv = prv_validate_set_desired_state_cmd(&conn_params->remote_desired_state, length); + break; + case PebblePairingServiceConnParamsWriteCmd_EnablePacketLengthExtension: + rv = prv_handle_ple_update_request(evt->conn_idx, &conn_params->ple_req, length); + break; + case PebblePairingServiceConnParamsWriteCmd_InhibitBLESleep: + power_inhibit_sleep(); + rv = (length >= sizeof(PebblePairingServiceInhibitBLESleep)) ? + ATT_ERROR_OK : ATT_ERROR_INVALID_VALUE_LENGTH; + break; + default: + rv = (att_error_t)PebblePairingServiceGATTError_UnknownCommandID; + break; + } + +finally: + if (rv == ATT_ERROR_OK) { + hc_endpoint_pebble_pairing_service_send_conn_params(connection, conn_params, evt->length); + } else if (rv == ATT_ERROR_INVALID_VALUE_LENGTH) { + PBL_LOG(LOG_LEVEL_ALWAYS, "Invalid value length %"PRIu16, evt->length); + } + return rv; +} + +static void prv_pps_handle_write_request(ble_service_t *svc, const ble_evt_gatts_write_req_t *evt) { + att_error_t rv = ATT_ERROR_INVALID_HANDLE; + if (evt->handle == s_pps_ctx.att_hdl.trigger_pairing) { + const PairingTriggerRequestData *pairing_trigger_request = + (PairingTriggerRequestData *)evt->value; + prv_handle_trigger_pairing(evt->conn_idx, pairing_trigger_request); + } else if (evt->handle == s_pps_ctx.att_hdl.conn_status_cccd || + evt->handle == s_pps_ctx.att_hdl.gatt_mtu_cccd || + evt->handle == s_pps_ctx.att_hdl.conn_params_cccd) { + rv = prv_handle_cccd_write(evt); + } else if (evt->handle == s_pps_ctx.att_hdl.gatt_mtu) { + rv = prv_handle_mtu_value_write(evt); + } else if (evt->handle == s_pps_ctx.att_hdl.conn_params) { + rv = prv_handle_conn_params_write(evt); + } else { + PBL_LOG(LOG_LEVEL_ERROR, "Writing unhandled handle: %u !", evt->handle); + } + + ble_error_t e = ble_gatts_write_cfm(evt->conn_idx, evt->handle, rv); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gatts_write_cfm: %u", e); + } +} + +void pebble_pairing_service_handle_status_change(Connection *connection, uint16_t conn_idx) { + if (!connection_is_subscribed_to_connection_status_notifications(connection)) { + return; + } + prv_send_notification(conn_idx, s_pps_ctx.att_hdl.conn_status); +} + +void pebble_pairing_service_handle_gatt_mtu_change(Connection *connection, uint16_t conn_idx) { + if (!connection_is_subscribed_to_gatt_mtu_notifications(connection)) { + return; + } + prv_send_notification(conn_idx, s_pps_ctx.att_hdl.gatt_mtu); +} + +void pebble_pairing_service_handle_conn_params_change(Connection *connection, uint16_t conn_idx) { + if (!connection_is_subscribed_to_conn_param_notifications(connection)) { + return; + } + prv_send_notification(conn_idx, s_pps_ctx.att_hdl.conn_params); +} + +static void prv_convert_uuid_to_little_endian(const uint8_t *buf, att_uuid_t *uuid) { + uint8_t *reverse_uuid = (uint8_t *)&UuidMakeFromLEBytes(buf); + ble_uuid_from_buf(reverse_uuid, uuid); +} + +static void prv_register(PebblePairingServiceCtx *ctx, uint16_t start_hdl) { + *ctx = (PebblePairingServiceCtx) { + .svc = { + .read_req = prv_pps_handle_read_request, + .write_req = prv_pps_handle_write_request, + }, + }; + + const uint16_t num_included_services = 0; + const uint16_t num_characteristics = 4; + const uint16_t num_descriptors = 3; + const uint16_t num_attr = ble_gatts_get_num_attr(num_included_services, num_characteristics, + num_descriptors); + + att_uuid_t uuid; + + ble_uuid_create16(PEBBLE_BT_PAIRING_SERVICE_UUID_16BIT, &uuid); + PBL_ASSERTN(ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr, start_hdl) == 0); + + // RivieraWaves caches values internally and only calls back once if we don't set this: + const uint8_t enable_read_cb_flag = GATTS_FLAG_CHAR_READ_REQ; + + // Connectivity Status characteristic + CCCD: + prv_convert_uuid_to_little_endian( + (const uint8_t[]){PEBBLE_BT_PAIRING_SERVICE_CONNECTION_STATUS_UUID}, &uuid); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_READ | GATT_PROP_NOTIFY, ATT_PERM_READ, + sizeof(PebblePairingServiceConnectivityStatus), + enable_read_cb_flag, NULL, + &ctx->att_hdl.conn_status) == BLE_STATUS_OK); + + ble_uuid_create16(UUID_GATT_CLIENT_CHAR_CONFIGURATION, &uuid); + PBL_ASSERTN(ble_gatts_add_descriptor(&uuid, ATT_PERM_RW, sizeof(uint16_t), enable_read_cb_flag, + &ctx->att_hdl.conn_status_cccd) == BLE_STATUS_OK); + + // Trigger Pairing characteristic: + prv_convert_uuid_to_little_endian( + (const uint8_t[]){PEBBLE_BT_PAIRING_SERVICE_TRIGGER_PAIRING_UUID}, &uuid); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_READ | GATT_PROP_WRITE, ATT_PERM_RW, + sizeof(uint8_t), enable_read_cb_flag, NULL, + &ctx->att_hdl.trigger_pairing) == BLE_STATUS_OK); + + // GATT MTU characteristic + CCCD: + prv_convert_uuid_to_little_endian( + (const uint8_t[]){PEBBLE_BT_PAIRING_SERVICE_GATT_MTU_UUID}, &uuid); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_READ | GATT_PROP_NOTIFY | + GATT_PROP_WRITE, + ATT_PERM_RW, sizeof(uint16_t), enable_read_cb_flag, NULL, + &ctx->att_hdl.gatt_mtu) == BLE_STATUS_OK); + + ble_uuid_create16(UUID_GATT_CLIENT_CHAR_CONFIGURATION, &uuid); + PBL_ASSERTN(ble_gatts_add_descriptor(&uuid, ATT_PERM_RW, sizeof(uint16_t), enable_read_cb_flag, + &ctx->att_hdl.gatt_mtu_cccd) == BLE_STATUS_OK); + + // GATT MTU characteristic + CCCD: + prv_convert_uuid_to_little_endian( + (const uint8_t[]){PEBBLE_BT_PAIRING_SERVICE_CONNECTION_PARAMETERS_UUID}, &uuid); + const size_t max_len = MAX(sizeof(PebblePairingServiceConnParamsReadNotif), + PEBBLE_PAIRING_SERVICE_CONN_PARAMS_WRITE_SIZE_WITH_PARAM_SETS); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_READ | GATT_PROP_NOTIFY | + GATT_PROP_WRITE, + ATT_PERM_RW, max_len, enable_read_cb_flag, NULL, + &ctx->att_hdl.conn_params) == BLE_STATUS_OK); + + ble_uuid_create16(UUID_GATT_CLIENT_CHAR_CONFIGURATION, &uuid); + PBL_ASSERTN(ble_gatts_add_descriptor(&uuid, ATT_PERM_RW, sizeof(uint16_t), enable_read_cb_flag, + &ctx->att_hdl.conn_params_cccd) == BLE_STATUS_OK); + + // Register service with the stack and update the handle offsets to absolute handle values: + PBL_ASSERTN(ble_gatts_register_service(&ctx->svc.start_h, + &ctx->att_hdl.conn_status, + &ctx->att_hdl.conn_status_cccd, + &ctx->att_hdl.trigger_pairing, + &ctx->att_hdl.gatt_mtu, + &ctx->att_hdl.gatt_mtu_cccd, + &ctx->att_hdl.conn_params, + &ctx->att_hdl.conn_params_cccd, + NULL) == BLE_STATUS_OK); + + ctx->svc.end_h = ctx->svc.start_h + num_attr; + // If this assert gets hit, it means we've shuffled the ATT table around! + PBL_ASSERTN(ctx->svc.start_h == start_hdl); +} + +void pebble_pairing_service_init(void) { + memset(&s_pps_ctx.svc, 0, sizeof(s_pps_ctx.svc)); + + // Register for callbacks with the dispatcher -- this only needs to happen once + ble_service_add(&s_pps_ctx.svc); +} + +void pebble_pairing_service_register(uint16_t start_hdl) { + prv_register(&s_pps_ctx, start_hdl); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/platform_reset.c b/src/bluetooth-fw/da1468x/controller/main/src/platform_reset.c new file mode 100644 index 00000000..9a13a4b7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/platform_reset.c @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#include "die.h" +#include "reboot_reason.h" + +#include +#include +#include + +//! Reset function provided for Dialog ROM to call into. +void platform_reset_func(uint32_t error) { + RebootReason reason = { + .code = RebootReasonCode_DialogPlatformReset, + .extra = error, + }; + reboot_reason_set(&reason); + + printf("PLF RST %"PRIx32"\n", error); + reset_due_to_software_failure(); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/power.c b/src/bluetooth-fw/da1468x/controller/main/src/power.c new file mode 100644 index 00000000..343a490d --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/power.c @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#include "system/logging.h" +#include "system/passert.h" + +// Dialog SDK: +#include "hw_cpm.h" +#include "osal.h" +#include "sdk_defs.h" +#include "sys_power_mgr.h" + +#if RELEASE +static OS_TIMER s_dbg_disable_timer; + +#define DISABLE_DEBUG_DELAY_MS (15 * 1000) + +static void prv_disable_debugger_cb(OS_TIMER timer) { + PBL_LOG(LOG_LEVEL_DEBUG, "Disabling debugger now to save power..."); + hw_cpm_disable_debugger(); + OS_TIMER_DELETE(s_dbg_disable_timer, OS_QUEUE_FOREVER); + s_dbg_disable_timer = NULL; +} + +static void prv_disable_debugger_with_delay(void) { + s_dbg_disable_timer = OS_TIMER_CREATE("dbg", OS_MS_2_TICKS(DISABLE_DEBUG_DELAY_MS), + false /* repeat */, NULL, prv_disable_debugger_cb); + PBL_ASSERTN(s_dbg_disable_timer != NULL); + PBL_ASSERTN(OS_TIMER_START(s_dbg_disable_timer, 0) == OS_TIMER_SUCCESS); +} +#endif + +void power_init(void) { + hw_cpm_enable_debugger(); +#if RELEASE + prv_disable_debugger_with_delay(); +#endif + + pm_set_wakeup_mode(true); + pm_set_sleep_mode(pm_mode_extended_sleep); +} + +// Note: Once called, sleep mode will be disabled +void power_inhibit_sleep(void) { + pm_set_sleep_mode(pm_mode_idle); +} + +void power_enter_hibernation(void) { + // Just this call alone goes a long way, because it will cause the system to enter hibernation + // as soon as there are no more runnable tasks. + pm_set_sleep_mode(pm_mode_hibernation); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/ppogatt_emulated_server_wa.c b/src/bluetooth-fw/da1468x/controller/main/src/ppogatt_emulated_server_wa.c new file mode 100644 index 00000000..27076bf7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/ppogatt_emulated_server_wa.c @@ -0,0 +1,496 @@ +/* + * 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. + */ + +#include "ppogatt_emulated_server_wa.h" + +#include "ble_task.h" +#include "bonding_flags.h" +#include "connection.h" +#include "gatt_wrapper.h" +#include "hc_protocol/hc_endpoint_discovery.h" +#include "hc_protocol/hc_endpoint_gatt.h" +#include "hc_protocol/hc_protocol.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" +#include "system/logging.h" +#include "system/passert.h" + +#include +#include +#include +#include +#include +#include +#include + +// Dialog SDK + +#include "ble_gap.h" +#include "ble_gatt.h" +#include "ble_gattc.h" +#include "ble_gatts.h" +#include "ble_service.h" +#include "ble_uuid.h" +#include "storage.h" + +#include +#include + +// Hack Alert +// +// There are some really sad Android phones/versions out there where publishing an Gatt Server does +// not work. To workaround this issue, we need to switch to using the watch as the PPoGATT Server +// and the phone as the PPoGATT client. On the main MCU side of things, our abstraction assumes +// that PPoGATT will always be a client. In the interest of not shuffling around this abstraction +// days before we are supposed to ship a final PRF, we can just emulate that the PPoGATT Server +// exists on the remote to the main MCU. The PPoGATT protocol itself is pretty symmetric, so all we +// really need to do is emulate a couple responses and remap Gatt Writes with Gatt Notifications +// +// Note: As part of this workaround, we do need to publish a new service but I think this should be +// harmless to watches which do not need the workaround + +// Not foolproof but chose a high service handle that is unlikely to be occupied by a different +// service on the phone. +#define EMULATED_PPOGATT_SERVICE_HANDLE_MSB 0xE0 +#define EMULATED_PPOGATT_SERVICE_HANDLE_LSB 0x00 +#define EMULATED_PPOGATT_SERVICE_HDL \ + ((EMULATED_PPOGATT_SERVICE_HANDLE_MSB) << 8 | (EMULATED_PPOGATT_SERVICE_HANDLE_LSB)) + +#define EMULATED_META_CHAR_OFFSET 0x02 +#define EMULATED_DATA_CHAR_OFFSET 0x04 +#define EMULATED_DATA_CCCD_OFFSET 0x05 + +#define EMULATED_DATA_CHAR_HANDLE (EMULATED_PPOGATT_SERVICE_HDL + EMULATED_DATA_CHAR_OFFSET) + +#define EMULATED_PPOGATT_OFFSET 0xFF + +// An emulated GATTService blob for a PPoGATT server which would usually be on the phone +static const uint8_t s_emulated_ppogatt_gatt_service[] = { + // GATTService + // UUID + 0x10, 0x00, 0x00, 0x00, 0x32, 0x8e, 0x0f, 0xbb, 0xc6, 0x42, 0x1a, 0xa6, 0x69, 0x9b, 0xda, 0xda, + // Discovery Generation + 0x00, + // RSVD + 0x00, + // Size Bytes + 0x51, 0x00, + // Att Handle + EMULATED_PPOGATT_SERVICE_HANDLE_LSB, EMULATED_PPOGATT_SERVICE_HANDLE_MSB, + // Num characteristics + 0x02, + // Num Descriptors + 0x01, + // RSVD + 0x00, + //// GATTCharacteristic + //// UUID + 0x10, 0x00, 0x00, 0x02, 0x32, 0x8e, 0x0f, 0xbb, 0xc6, 0x42, 0x1a, 0xa6, 0x69, 0x9b, 0xda, 0xda, + //// Att Handle Offset + EMULATED_META_CHAR_OFFSET, + //// Properties + 0x02, + //// Num Descriptors + 0x00, + //// GATTCharacteristic + 0x10, 0x00, 0x00, 0x01, 0x32, 0x8e, 0x0f, 0xbb, 0xc6, 0x42, 0x1a, 0xa6, 0x69, 0x9b, 0xda, 0xda, + //// Offset + EMULATED_DATA_CHAR_OFFSET, + //// Properties + 0x14, + //// Num Descriptors + 0x01, + ////// Data descriptor + 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb, + ///// Offset + EMULATED_DATA_CCCD_OFFSET, + 0x00 +}; + +typedef struct { + ble_service_t svc; + + //! ATT handles + struct { + uint16_t data; + uint16_t data_wr; + uint16_t meta; + uint16_t cccd; + } att_hdl; +} PPoGattServiceCtx; + +static PPoGattServiceCtx s_ppogatt_ctx; + +// Copy/Pasta +typedef enum { + PPoGATTSessionType_InferredFromUuid = 0x00, + PPoGATTSessionType_Hybrid = 0x01, + PPoGATTSessionTypeCount, +} PPoGATTSessionType; + +typedef struct PACKED { + uint8_t ppogatt_min_version; + uint8_t ppogatt_max_version; + Uuid app_uuid; + PPoGATTSessionType pp_session_type:8; +} PPoGATTMetaV1; + +typedef struct PPoGATTWorkAroundState { + bool remote_did_subscribe; + HcGattWriteRespData *pending_response_data; + size_t pending_data_size; + bool ppogatt_server_found_on_phone; +} PPoGATTWorkAroundState; + +static PPoGATTWorkAroundState *prv_get_state(Connection *conn) { + // We're accessing data that's stored in Connection and we're not having extra locks, make sure + // we only access from a single task (ble_task): + ble_task_assert_is_executing_on_ble_task(); + + PPoGATTWorkAroundState *state = connection_get_ppogatt_wa_state(conn); + if (!state) { + state = (PPoGATTWorkAroundState *)kernel_zalloc_check(sizeof(PPoGATTWorkAroundState)); + connection_set_ppogatt_wa_state(conn, state); + } + return state; +} + +void ppogatt_destroy_state(PPoGATTWorkAroundState *state) { + if (state->pending_response_data) { + kernel_free(state->pending_response_data); + } + kernel_free(state); +} + +static bool prv_is_emulated_server_wa_requested(uint16_t conn_idx) { + bool enabled = false; + storage_acquire(); + device_t *dev = find_device_by_conn_idx(conn_idx); + if (dev) { + // Only enable the work-around if the bit is present in the bonding list, in other words, it + // had been set *before pairing*. Don't look at the Connection flag, because this can be set + // at any time. This way bad apps can't just write the PPS bit to enable the WA server after + // pairing w/o the bit set. + enabled = (dev->flags & BleBondingFlag_IsReversedPPoGATTEnabled); + } + storage_release(); + return enabled; +} + +void ppogatt_emulated_notify_phone_ppogatt_server_found(Connection *conn) { + if (prv_is_emulated_server_wa_requested(connection_get_idx(conn))) { + PPoGATTWorkAroundState *state = prv_get_state(conn); + state->ppogatt_server_found_on_phone = true; + } +} + +bool ppogatt_emulated_server_wa_enabled(uint16_t conn_idx) { + if (!prv_is_emulated_server_wa_requested(conn_idx)) { + return false; + } + + // Disable the workaround if a PPoGATT Server was found on the phone. This will allow the phone + // app to easily disable the WA if it decides it's no longer necessary (after something like an OS + // update, for example) + Connection *connection = connection_by_idx_check(conn_idx); + if (prv_get_state(connection)->ppogatt_server_found_on_phone) { + return false; + } + + return true; +} + +static void prv_build_write_complete_payload(const HcGattHdr *hdr, uint16_t att_hdl, + PPoGATTWorkAroundState *state) { + if (state->pending_response_data) { + PBL_LOG(LOG_LEVEL_DEBUG, "WOAH"); + kernel_free(state->pending_response_data); + } + const uint32_t alloc_size = sizeof(HcGattWriteRespData); + state->pending_data_size = alloc_size; + HcGattWriteRespData *data = kernel_zalloc_check(alloc_size); + *data = (HcGattWriteRespData) { + .status = BLEGATTErrorSuccess, + .hdr.addr = hdr->addr, + .att_handle = att_hdl, + .context_ref = ((HcGattReadData *)hdr)->context_ref, + }; + state->pending_response_data = data; +} + +static void prv_enqueue_write_complete_if_ready(PPoGATTWorkAroundState *state) { + if (!state->remote_did_subscribe) { + return; // Gotta wait for remote to subscribe. + } + if (!state->pending_response_data) { + return; // Gotta wait for FW to subscribe / write the emulated CCCD. + } + + PBL_LOG(LOG_LEVEL_ALWAYS, "Phone & Watch have hit CCCD, initiate PPoGatt WA!"); + hc_protocol_enqueue_with_payload(HcEndpointID_Gatt, HcMessageID_Gatt_WriteCompleted, + (uint8_t *)state->pending_response_data, + state->pending_data_size); + kernel_free(state->pending_response_data); + state->pending_response_data = NULL; +} + +static void prv_handle_cccd_write(Connection *conn, bool subscribed) { + PPoGATTWorkAroundState *state = prv_get_state(conn); + state->remote_did_subscribe = subscribed; + prv_enqueue_write_complete_if_ready(state); +} + +static void prv_ppogatt_handle_write_request(ble_service_t *svc, + const ble_evt_gatts_write_req_t *evt) { + // Dialog requires this even if its a Write without response: + ble_gatts_write_cfm(evt->conn_idx, evt->handle, ATT_ERROR_OK); + + if (!ppogatt_emulated_server_wa_enabled(evt->conn_idx)) { + return; + } + + BTDeviceInternal addr; + Connection *conn = connection_by_idx_check(evt->conn_idx); + if ((evt->handle == s_ppogatt_ctx.att_hdl.data) || + (evt->handle == s_ppogatt_ctx.att_hdl.data_wr)) { + // Pretend the write request is actually a notification and fwd that to the MCU! + connection_get_address(conn, &addr); + hc_endpoint_gatt_send_notification(&addr, EMULATED_DATA_CHAR_HANDLE, evt->length, evt->value); + } else if (evt->handle == s_ppogatt_ctx.att_hdl.cccd) { + if (evt->offset == 0) { + uint16_t value; + memcpy(&value, evt->value, sizeof(value)); + bool subscribed = (GATT_CCC_NOTIFICATIONS == value); + prv_handle_cccd_write(conn, subscribed); + PBL_LOG(LOG_LEVEL_DEBUG, "Wrote reversed PPoGATT CCCD: %"PRIu16, subscribed); + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "Invalid CCCD write offset %"PRIu16, evt->offset); + } + } +} + +static void prv_ppogatt_handle_read_request(ble_service_t *svc, + const ble_evt_gatts_read_req_t *evt) { + PBL_LOG(LOG_LEVEL_DEBUG, "Unexpected PPoGATT Service Read: 0x%"PRIx16, evt->handle); + + // In the future, we might actually return something useful. For now, just uncoditionally + // respond to everything with the same payload + uint8_t response = 0xAA; + ble_error_t e = ble_gatts_read_cfm(evt->conn_idx, evt->handle, ATT_ERROR_OK, + sizeof(response), &response); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "ble_gatts_read_cfm: %u", e); + } +} + +static bool prv_handle_emulated_meta_read(const HcProtocolMessage *msg) { + PBL_ASSERTN(msg->command_id == HcMessageID_Gatt_Read); + HcGattHdr *hdr = (HcGattHdr *)&msg->payload[0]; + + PBL_LOG(LOG_LEVEL_DEBUG, "MR"); + + // First ack the read request itself + BTErrno rv = BTErrnoOK; + hc_protocol_enqueue_response(msg, (uint8_t *)&rv, sizeof(rv)); // ack the read + + + // Assume an app using this emulated workaround is Android and supports V1 + PPoGATTMetaV1 emulated_result = { + .ppogatt_min_version = 1, + .ppogatt_max_version = 1, + .pp_session_type = PPoGATTSessionType_Hybrid + }; + + const uint32_t alloc_size = sizeof(HcGattReadRespData) + sizeof(emulated_result); + HcGattReadRespData *data = kernel_zalloc_check(alloc_size); + *data = (HcGattReadRespData) { + .status = BLEGATTErrorSuccess, + .hdr.addr = hdr->addr, + .att_handle = ((HcGattReadData *)hdr)->att_handle, + .value_length = sizeof(emulated_result), + .context_ref = ((HcGattReadData *)hdr)->context_ref + }; + memcpy(data->value, &emulated_result, sizeof(emulated_result)); + + hc_protocol_enqueue_with_payload(HcEndpointID_Gatt, HcMessageID_Gatt_ReadCompleted, + (uint8_t *)data, alloc_size); + + kernel_free(data); + return true; +} + +static bool prv_handle_emulated_cccd_write(const HcProtocolMessage *msg, + PPoGATTWorkAroundState *state) { + PBL_ASSERTN(msg->command_id == HcMessageID_Gatt_Write); + HcGattHdr *hdr = (HcGattHdr *)&msg->payload[0]; + + BTErrno rv = BTErrnoOK; + hc_protocol_enqueue_response(msg, (uint8_t *)&rv, sizeof(rv)); // ack the write + + prv_build_write_complete_payload(hdr, ((HcGattWriteData *)hdr)->att_handle, state); + prv_enqueue_write_complete_if_ready(state); + + return true; +} + +static bool prv_handle_emulated_write(uint16_t conn_idx, const HcProtocolMessage *msg) { + PBL_ASSERTN(msg->command_id == HcMessageID_Gatt_WriteNoResponse); + + // Remap a data write to a data notification + HcGattWriteData *data = (HcGattWriteData *)&msg->payload[0]; + ble_error_t e = ble_gatts_send_event( + conn_idx, s_ppogatt_ctx.att_hdl.data, GATT_EVENT_NOTIFICATION, data->value_length, + &data->value[0]); + if (e != BLE_STATUS_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Emulated write failed: %d", (int)e); + } + + return true; +} + +bool ppogatt_emulated_server_handle_msg(uint16_t conn_idx, Connection *conn, + const HcProtocolMessage *msg) { + if (!ppogatt_emulated_server_wa_enabled(conn_idx)) { + return false; + } + + HcGattHdr *hdr = (HcGattHdr *)&msg->payload[0]; + uint16_t att_hdl = (msg->command_id == HcMessageID_Gatt_Read) ? + ((HcGattReadData *)hdr)->att_handle : ((HcGattWriteData *)hdr)->att_handle; + + if (att_hdl < EMULATED_PPOGATT_SERVICE_HDL) { + goto unhandled; + } + uint8_t offset = att_hdl - EMULATED_PPOGATT_SERVICE_HDL; + + switch (offset) { + case EMULATED_META_CHAR_OFFSET: + return prv_handle_emulated_meta_read(msg); + case EMULATED_DATA_CHAR_OFFSET: + return prv_handle_emulated_write(conn_idx, msg); + case EMULATED_DATA_CCCD_OFFSET: { + PPoGATTWorkAroundState *state = prv_get_state(conn); + return prv_handle_emulated_cccd_write(msg, state); + } + default: + goto unhandled; + } + +unhandled: + return false; +} + +void ppogatt_inject_emulated_ppogatt_service_if_needed(uint16_t conn_idx) { + if (!ppogatt_emulated_server_wa_enabled(conn_idx)) { + return; + } + + uint32_t payload_size = sizeof(BTDeviceInternal) + sizeof(s_emulated_ppogatt_gatt_service); + HcProtocolDiscoveryServiceFoundPayload *payload = kernel_zalloc_check(payload_size); + + Connection *connection = connection_by_idx_check(conn_idx); + connection_get_address(connection, &payload->address); + + memcpy(&payload->service, &s_emulated_ppogatt_gatt_service, + sizeof(s_emulated_ppogatt_gatt_service)); + hc_endpoint_discovery_send_service_found(payload, payload_size); + + kernel_free(payload); +} + +// +// Real PPoGATT Service Registration Code +// + +// Yes, a straight copy from pebble_pairing_service.c +static void prv_convert_uuid_to_little_endian(const uint8_t *buf, att_uuid_t *uuid) { + uint8_t *reverse_uuid = (uint8_t *)&UuidMakeFromLEBytes(buf); + ble_uuid_from_buf(reverse_uuid, uuid); +} + +void ppogatt_service_init(void) { + memset(&s_ppogatt_ctx.svc, 0, sizeof(s_ppogatt_ctx.svc)); + + // Register for callbacks with the dispatcher -- this only needs to happen once + ble_service_add(&s_ppogatt_ctx.svc); +} + +void ppogatt_service_register(uint16_t start_hdl) { + PPoGattServiceCtx *ctx = &s_ppogatt_ctx; + + *ctx = (PPoGattServiceCtx) { + .svc = { + .read_req = prv_ppogatt_handle_read_request, + .write_req = prv_ppogatt_handle_write_request, + }, + }; + + att_uuid_t uuid; + + const uint16_t num_included_services = 0; + const uint16_t num_characteristics = 3; + const uint16_t num_descriptors = 1; + const uint16_t num_attr = ble_gatts_get_num_attr(num_included_services, num_characteristics, + num_descriptors); + + // RivieraWaves caches values internally and only calls back once if we don't set this: + const uint8_t enable_read_cb_flag = GATTS_FLAG_CHAR_READ_REQ; + + // Just use the max allowed size, no buffers get allocated because of GATTS_FLAG_CHAR_READ_REQ. + const uint16_t data_wr_size = 512; + + prv_convert_uuid_to_little_endian((const uint8_t[]){PEBBLE_BT_UUID_EXPAND( + PEBBLE_BT_PPOGATT_WATCH_SERVER_SERVICE_UUID_32BIT)}, &uuid); + PBL_ASSERTN(ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr, start_hdl) == 0); + + prv_convert_uuid_to_little_endian((const uint8_t[]){PEBBLE_BT_UUID_EXPAND( + PEBBLE_BT_PPOGATT_WATCH_SERVER_DATA_CHARACTERISTIC_UUID_32BIT)}, &uuid); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_WRITE_NO_RESP | GATT_PROP_NOTIFY, + ATT_PERM_WRITE, + data_wr_size, + enable_read_cb_flag, NULL, + &ctx->att_hdl.data) == BLE_STATUS_OK); + + ble_uuid_create16(UUID_GATT_CLIENT_CHAR_CONFIGURATION, &uuid); + PBL_ASSERTN(ble_gatts_add_descriptor(&uuid, ATT_PERM_RW, sizeof(uint16_t), enable_read_cb_flag, + &ctx->att_hdl.cccd) == BLE_STATUS_OK); + + prv_convert_uuid_to_little_endian((const uint8_t[]){PEBBLE_BT_UUID_EXPAND( + PEBBLE_BT_PPOGATT_WATCH_SERVER_META_CHARACTERISTIC_UUID_32BIT)}, &uuid); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_READ, ATT_PERM_READ, + sizeof(PPoGATTMetaV1), + enable_read_cb_flag, NULL, + &ctx->att_hdl.meta) == BLE_STATUS_OK); + + prv_convert_uuid_to_little_endian((const uint8_t[]){PEBBLE_BT_UUID_EXPAND( + PEBBLE_BT_PPOGATT_WATCH_SERVER_DATA_WR_CHARACTERISTIC_UUID_32BIT)}, &uuid); + PBL_ASSERTN(ble_gatts_add_characteristic(&uuid, GATT_PROP_WRITE_NO_RESP, ATT_PERM_WRITE, + data_wr_size, + enable_read_cb_flag, NULL, + &ctx->att_hdl.data_wr) == BLE_STATUS_OK); + + + PBL_ASSERTN(ble_gatts_register_service(&ctx->svc.start_h, + &ctx->att_hdl.data, + &ctx->att_hdl.cccd, + &ctx->att_hdl.meta, + &ctx->att_hdl.data_wr, + NULL) == BLE_STATUS_OK); + + ctx->svc.end_h = ctx->svc.start_h + num_attr; + + PBL_ASSERTN(ctx->svc.start_h == start_hdl); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/pra_generate.c b/src/bluetooth-fw/da1468x/controller/main/src/pra_generate.c new file mode 100644 index 00000000..096fa0f4 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/pra_generate.c @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#include "pra_generate.h" + +#include "aes.h" +#include "system/hexdump.h" +#include "system/logging.h" + +// Dialog SDK: +#include "ble_mgr.h" + +#include +#include +#include + +#include +#include + +// For some reason, the AES bytes seem to be reversed within the BLE spec? +// See for example BT Spec v4.2, Vol 2. Part E, 7.8.22 "HCI LE Encrypt": +// "The most significant octet of the key corresponds to key[0] using the notation specified +// in FIPS 197." +static void prv_reverse_byte_order(uint8_t block[AES_128_BLOCK_SIZE]) { + for (uint32_t i = 0; i < AES_128_BLOCK_SIZE / 2; ++i) { + uint8_t *a = &block[i]; + uint8_t *b = &block[AES_128_BLOCK_SIZE - i - 1]; + uint8_t temp = *a; + *a = *b; + *b = temp; + } +} + +static bool prv_emulate_hci_le_encrypt(const SM128BitKey *key, + const uint8_t plain_text_block[AES_128_BLOCK_SIZE], + uint8_t cipher_text_block_out[AES_128_BLOCK_SIZE]) { + const SM128BitKey key_reversed = *key; + prv_reverse_byte_order((uint8_t *)&key_reversed); + + uint8_t plain_text_block_reversed[AES_128_BLOCK_SIZE]; + memcpy(plain_text_block_reversed, plain_text_block, sizeof(plain_text_block_reversed)); + prv_reverse_byte_order(plain_text_block_reversed); + + bool rv = aes_128_encrypt_block((const uint8_t *)&key_reversed, plain_text_block_reversed, + cipher_text_block_out); + + prv_reverse_byte_order(cipher_text_block_out); + return rv; +} + +void pra_generate(BTDeviceAddress *address_out) { + SMIdentityResolvingKey irk; + const ble_dev_params_t *dev_params = ble_mgr_dev_params_acquire(); + memcpy(irk.data, dev_params->irk.key, sizeof(irk.data)); + ble_mgr_dev_params_release(); + + // rand() returns between 0 and RAND_MAX, but only the 24 LSBits are used anyway. + const int prand = rand(); + + pra_generate_with_prand_and_irk(address_out, prand, &irk); +} + +void pra_generate_with_prand_and_irk(BTDeviceAddress *address_out, + uint32_t prand, const SMIdentityResolvingKey *irk) { + // See Core v4.2, Vol 6, Part B, "1.3.2.2 Private Device Address Generation" + + const size_t HASH_LEN_BYTES = (24 / 8); + const size_t PRAND_LEN_BYTES = (24 / 8); + + uint8_t prand_bytes[AES_128_BLOCK_SIZE]; + memset(prand_bytes, 0, sizeof(prand_bytes)); + // Copy the 24 LSBits of prand: + memcpy(prand_bytes, &prand, PRAND_LEN_BYTES); + // Assign the 2 MSBits of the prand, these are used to indicate the address is a PRA. + prand_bytes[PRAND_LEN_BYTES - 1] &= 0b00111111; + prand_bytes[PRAND_LEN_BYTES - 1] |= 0b01000000; + + uint8_t hash_bytes[AES_128_BLOCK_SIZE]; + prv_emulate_hci_le_encrypt(irk, prand_bytes, hash_bytes); + + // Use 24 LSBits of the cipher text as 24 LSBits of the address: + memcpy(&address_out->octets[0], hash_bytes, HASH_LEN_BYTES); + // Copy the prand to the 24 MSBits of the address: + memcpy(&address_out->octets[HASH_LEN_BYTES], prand_bytes, PRAND_LEN_BYTES); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/reboot_reason.c b/src/bluetooth-fw/da1468x/controller/main/src/reboot_reason.c new file mode 100644 index 00000000..2a19c18d --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/reboot_reason.c @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#include "reboot_reason.h" + +#include +#include +#include + +#include "util/attributes.h" + +static RebootReason s_reboot_reason SECTION("reboot_reason"); + +static const uint32_t COOKIE = 0xdeadbeef; + +void reboot_reason_set(RebootReason *reason) { + reason->cookie = COOKIE; + s_reboot_reason = *reason; +} + +bool reboot_reason_get(RebootReason *reason) { + *reason = s_reboot_reason; + return (reason->cookie == COOKIE); +} + +void reboot_reason_clear(void) { + s_reboot_reason = (RebootReason){}; +} + +uint32_t reboot_reason_get_crash_lr(void) { + RebootReason reason; + if (!reboot_reason_get(&reason)) { + return 0; + }; + + switch (reason.code) { + case RebootReasonCode_Assert: + case RebootReasonCode_HardFault: + case RebootReasonCode_RomError: + case RebootReasonCode_Watchdog: + return reason.extra; + default: + return 0; + } +} + +RebootReasonCode reboot_reason_get_last_reboot_reason(void) { + RebootReason reason; + if (!reboot_reason_get(&reason)) { + return RebootReasonCode_Unknown; + }; + return reason.code; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/service_changed.c b/src/bluetooth-fw/da1468x/controller/main/src/service_changed.c new file mode 100644 index 00000000..ba44cc73 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/service_changed.c @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include "service_changed.h" + +// Dialog SDK: + +#include "ble_gap.h" +#include "ble_gatt.h" // for GATT_CCC_INDICATIONS +#include "ble_gatts.h" +#include "ble_storage.h" +#include "storage.h" // for STORAGE_KEY_SVC_CHANGED_CCC + +#include + +void service_changed_send_indication_to_all(uint16_t start_handle, uint16_t end_handle) { + gap_device_t devices[BLE_GAP_MAX_CONNECTED]; + size_t length = ARRAY_LENGTH(devices); + ble_gap_get_devices(GAP_DEVICE_FILTER_CONNECTED, NULL, &length, devices); + + for (int i = 0; i < (int)length; ++i) { + const gap_device_t *const device = &devices[i]; + uint16_t svc_chg_ccc = 0; + ble_storage_get_u16(device->conn_idx, STORAGE_KEY_SVC_CHANGED_CCC, &svc_chg_ccc); + const bool is_subscribed_to_service_changed = !!(svc_chg_ccc & GATT_CCC_INDICATIONS); + if (is_subscribed_to_service_changed) { + ble_gatts_service_changed_ind(device->conn_idx, start_handle, end_handle); + } + } +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/tasks.c b/src/bluetooth-fw/da1468x/controller/main/src/tasks.c new file mode 100644 index 00000000..9acd74a9 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/tasks.c @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#include "tasks.h" +#include "system/logging.h" + +#include "mcu/interrupts.h" + +OS_TASK DialogTaskList[DialogTask_Last] = { 0, }; + +DialogTask task_to_dialogtask(OS_TASK task) { + for (int index = 0; index < DialogTask_Last; ++index) { + if (task == DialogTaskList[index]) { + return index; + } + } + return DialogTask_Error; +} + +void tasks_dump_free_space(void) { + for (int i = 0; i < DialogTask_Last; i++) { + OS_TASK task = DialogTaskList[i]; + if (task != 0) { + PBL_LOG(LOG_LEVEL_DEBUG, "Task %s stack has %d bytes free", pcTaskGetTaskName(task), + ((int)uxTaskGetStackHighWaterMark(task) * sizeof(StackType_t))); + } + } +} + +DialogTask task_get_dialogtask(void) { + if (mcu_state_is_isr()) { + return DialogTask_ISR; + } + return task_to_dialogtask(xTaskGetCurrentTaskHandle()); +} + +void task_unregister_task(OS_TASK task) { + DialogTask index = task_to_dialogtask(task); + if (index >= DialogTask_Last) { + return; + } + DialogTaskList[index] = NULL; +} + +void task_unregister_dialogtask(DialogTask task) { + if (task >= DialogTask_Last) { + return; + } + DialogTaskList[task] = NULL; +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/util.c b/src/bluetooth-fw/da1468x/controller/main/src/util.c new file mode 100644 index 00000000..56809c2b --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/util.c @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include + +#include "util/attributes.h" +#include "system/logging.h" +#include "system/passert.h" + +extern void __assert_func(char const *fname, int line_num, const char *foo); + +NORETURN util_assertion_failed(const char *filename, int line) { + const uintptr_t lr = (uintptr_t) __builtin_return_address(0); + PBL_ASSERTN_LR(0, lr); +} diff --git a/src/bluetooth-fw/da1468x/controller/main/src/version.c b/src/bluetooth-fw/da1468x/controller/main/src/version.c new file mode 100644 index 00000000..eb4c52b9 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/src/version.c @@ -0,0 +1,31 @@ +/* + * 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. + */ + +#include "pebbleos/firmware_metadata.h" + +// Include generated files from the FW build. This should be fixed in WAF. +#include "../../src/fw/git_version.auto.h" + +const FirmwareMetadata TINTIN_METADATA SECTION(".pbl_fw_version") = { + .version_timestamp = GIT_TIMESTAMP, + .version_tag = GIT_TAG, + .version_short = GIT_REVISION, + .is_recovery_firmware = FIRMWARE_METADATA_IS_RECOVERY_FIRMWARE, + .is_ble_firmware = true, + .reserved = 0, + .hw_platform = FIRMWARE_METADATA_HW_PLATFORM, + .metadata_version = FW_METADATA_CURRENT_STRUCT_VERSION, +}; diff --git a/src/bluetooth-fw/da1468x/controller/main/wscript b/src/bluetooth-fw/da1468x/controller/main/wscript new file mode 100644 index 00000000..a0b31c24 --- /dev/null +++ b/src/bluetooth-fw/da1468x/controller/main/wscript @@ -0,0 +1,240 @@ +import sys +from subprocess import Popen, PIPE + +import waftools.objcopy +import waftools.insert_text_section_crc + +from resources.types.resource_definition import ResourceDefinition +from resources.types.resource_object import ResourceObject + +CUSTOM_CONFIG_H_PATH = 'config/custom_config_main.h' + + +def _export_dialog_fw_includes(bld): + fw_includes = ['include', '../common/include', '../../include', 'config'] + bld(export_includes=fw_includes, name='dialog_fw_includes') + + +def build(bld): + bld.env.append_value('DEFINES', ['BLE_MAIN_FW=1']) + + sys.path.append(bld.path.find_node('../..').abspath()) + from dialog_waf import get_sdk_node, collect_sdk_sources, generate_mem_ld + + mem_ld_node = generate_mem_ld(bld, CUSTOM_CONFIG_H_PATH) + + # Collect source files: + source_dirs = ['src', '../../common'] + sources = sum([bld.path.find_node(d).ant_glob('**/*.c') + for d in source_dirs], []) + sdk_sources = [ + 'sdk/bsp/startup/config.c', + 'sdk/bsp/startup/startup_ARMCM0.S', + 'sdk/bsp/startup/system_ARMCM0.c', + 'sdk/bsp/startup/vector_table.S', + + # FreeRTOS: + 'sdk/bsp/free_rtos/event_groups.c', + 'sdk/bsp/free_rtos/light_mutex.c', + 'sdk/bsp/free_rtos/list.c', + 'sdk/bsp/free_rtos/portable/GCC/ARM_CM0/port.c', + 'sdk/bsp/free_rtos/queue.c', + 'sdk/bsp/free_rtos/tasks.c', + 'sdk/bsp/free_rtos/timers.c', + + # Core/Peripheral APIs: + 'sdk/bsp/adapters/src/ad_gpadc.c', + 'sdk/bsp/adapters/src/ad_rf.c', + 'sdk/bsp/adapters/src/ad_spi.c', + 'sdk/bsp/adapters/src/ad_temp_sens.c', + 'sdk/bsp/osal/resmgmt.c', + 'sdk/bsp/peripherals/src/hw_aes_hash.c', + 'sdk/bsp/peripherals/src/hw_cpm.c', + 'sdk/bsp/peripherals/src/hw_dma.c', + 'sdk/bsp/peripherals/src/hw_gpadc.c', + 'sdk/bsp/peripherals/src/hw_gpio.c', + 'sdk/bsp/peripherals/src/hw_otpc.c', + 'sdk/bsp/peripherals/src/hw_qspi.c', # optimization opportunity + 'sdk/bsp/peripherals/src/hw_rf.c', + 'sdk/bsp/peripherals/src/hw_spi.c', + 'sdk/bsp/peripherals/src/hw_tempsens.c', + 'sdk/bsp/peripherals/src/hw_timer0.c', + 'sdk/bsp/peripherals/src/hw_timer1.c', + 'sdk/bsp/peripherals/src/hw_trng.c', + 'sdk/bsp/peripherals/src/hw_uart.c', + 'sdk/bsp/peripherals/src/hw_usb_charger.c', # optimization opportunity + 'sdk/bsp/peripherals/src/hw_watchdog.c', + 'sdk/bsp/peripherals/src/hw_wkup.c', + 'sdk/bsp/peripherals/src/sys_tcs.c', + 'sdk/bsp/system/sys_man/sys_clock_mgr.c', + 'sdk/bsp/system/sys_man/sys_power_mgr.c', + 'sdk/bsp/system/sys_man/sys_rtc.c', + 'sdk/bsp/system/sys_man/sys_watchdog.c', + + # BLE Services: + 'sdk/interfaces/ble_services/src/ble_service.c', + 'sdk/interfaces/ble_services/src/dis.c', + 'sdk/interfaces/ble_services/src/hrs.c', + ] + sources.extend(collect_sdk_sources(bld, sdk_sources)) + + # BLE API sources: + ble_sources = get_sdk_node(bld).ant_glob( + 'sdk/interfaces/ble/**/*.c', + # This file is deprecated and also drags in Dialog's list.c, which + # conflicts with libutil's list.c, so skip them: + excl='sdk/interfaces/ble/src/ble_attribdb.c ' + 'sdk/interfaces/ble/src/util/list.c') + + sources.extend(ble_sources) + + def add_wrap(linkflags, func_name): + linkflags += ['-Wl,--wrap=%s' % func_name, + '-Wl,--undefined=__wrap_%s' % func_name] + + # LDFLAGS: + linkflags = ['-Wl,-Map,bt_da14681_main.map', + '-Wl,--undefined=uxTopUsedPriority', + '-Wl,--build-id=sha1', + ] + + # Wrappers (Jay-Z, Tupac, etc): + add_wrap(linkflags, 'ble_gap_address_set') + add_wrap(linkflags, 'ble_gap_device_name_set') + add_wrap(linkflags, 'ble_gap_appearance_set') + add_wrap(linkflags, 'ble_gap_per_pref_conn_params_set') + add_wrap(linkflags, 'ble_gap_role_set') + add_wrap(linkflags, 'ble_gap_mtu_size_set') + add_wrap(linkflags, 'patch_rom_functions') + + # Dialog main FW Includes (used by libOS): + _export_dialog_fw_includes(bld) + + variant = None + libble_stack_name = None + rom_symbols_path = None + if bld.env.bt_controller == 'da14681-00': + variant = 'DA14681-00-Debug' + libble_stack_name = 'ble_stack_da14681_00' + rom_symbols_path = 'sdk/bsp/misc/da14681_00_rom.symbols' + elif bld.env.bt_controller == 'da14681-01': + variant = 'DA14681-01-Debug' + libble_stack_name = 'ble_stack_da14681_01' + rom_symbols_path = 'sdk/bsp/misc/da14681_01_rom.symbols' + # Sadly, it appears not all remote devices support packet length extension. We ran into a + # pretty serious bug with the iPhone 7 (PBL-42011) that would prevent the watch from being + # able to pair/communicate with the phone. Hopefully, in the future we can turn this back + # on to harness the throughput improvements gained. + bld.env.append_value('DEFINES', ['SUPPORTS_PACKET_LENGTH_EXTENSION=0']) + else: + raise Exception("Unknown bt_controller '%s'" % bld.env.bt_controller) + + # Link libble_stack.a: + libble_stack_node = get_sdk_node(bld).find_node( + 'sdk/interfaces/ble_stack/%s' % variant) + bld.read_stlib(libble_stack_name, paths=[libble_stack_node]) + + # Link ROM symbols: + # waf doesn't recognize the .symbols file, so just pass it + # as a linker 'flag': + rom_symbols_path = get_sdk_node(bld).find_node(rom_symbols_path).abspath() + linkflags.append(rom_symbols_path) + + # Link to a temporary elf (no_crc) + no_crc_node = bld.path.get_bld().make_node('bt_da14681_main.no_crc') + bld.program(features='c asm cprogram', + source=sources, + target=no_crc_node, + lib=['gcc'], + linkflags=linkflags, + ldscript=mem_ld_node, + inject_include_files=[CUSTOM_CONFIG_H_PATH], + # Note: Dialog SDK also includes a "util/list.h" header. + # Put 'libutil_includes' before 'dialog_sdk_includes' to + # make sure libutil's "util/list.h" gets picked first: + use=['dialog_fw_includes', + 'libbtutil_includes', + 'libbtutil-cm0', + 'libutil_includes', + 'libos_includes', + 'dialog_board_main', + 'dialog_sdk_includes', + libble_stack_name, + 'pblibc-cm0', + 'libos-dialog', + 'libutil-cm0', + 'root_includes'], + # Hack to prevent waf from compiling the shared sdk files twice + # into the same place. I think it's a waf bug but one of the + # maintainers disagrees with me: + # https://github.com/waf-project/waf/issues/1661 + idx=3000) + + bld.add_manual_dependency(no_crc_node, mem_ld_node) + + # Update the .text_crc32 section with the correct CRC32. + elf_node = no_crc_node.change_ext('.elf') + bld(rule=waftools.insert_text_section_crc.wafrule, source=no_crc_node, target=elf_node) + bld.add_manual_dependency(elf_node, no_crc_node) + + # Create the log_strings .elf and check the format specifier rules + if 'PBL_LOGS_HASHED' in bld.env.DEFINES: + bt_loghash_node = bld.path.get_bld().make_node('bt_da14681_loghash_dict.json') + + # Create the log_strings .elf and check the format specifier rules + bld(rule=waftools.generate_log_strings_json.wafrule, + source=elf_node, target=bt_loghash_node) + + bld.LOGHASH_DICTS.append(bt_loghash_node) + + # Note: We play a couple tricks here. When the main image is loaded onto + # the BLE chip, we have a bootloader running in RAM. We place the main + # image heap in this region so that nothing needs to be copied over to the + # region code is executing from. However, the vector table must always + # reside at the beginning of RAM. We relocate the vector table using + # objcopy such that it falls right after the .infoblob section. The main MCU + # firmware uses this to figure out where to load things to. This makes all the + # sections contiguous and prevents objcopy from creating a larger bin + # padded with a bunch of extra 0s. The main firmware has a special command + # to deal with loading the vector table into the correct location + bin_node = elf_node.change_ext('.bin') + + def _generate_bt_patch_rule(task): + # Get the address of the region we should move the vector table to + # TODO: Extend nm_generator to easily parse for stuff like this + readelf_proc = Popen(['arm-none-eabi-readelf', '-S', task.inputs[0].abspath()], stdout=PIPE) + readelf_output = readelf_proc.communicate()[0] + for line in readelf_output.splitlines(): + if 'vt_stash_region' not in line: + continue + line = line[6:] + addr = int(line.split()[2], 16) + break + + waftools.objcopy.objcopy(task, 'binary', extra_args=('--change-section-address .vt=0x%x' % addr)) + + bld(rule=_generate_bt_patch_rule, source=elf_node, target=bin_node) + + storage = bld.get_bluetooth_fw_storage() + + def _create_bt_patch_resource(task): + bin_data = task.inputs[0].read('rb') + reso = ResourceObject(ResourceDefinition('raw', 'BT_FW_IMAGE', None, + storage=storage), bin_data) + reso.dump(task.outputs[0]) + + reso_node = bin_node.change_ext('.bin.reso') + bld(rule=_create_bt_patch_resource, source=bin_node, target=reso_node) + + bld.DYNAMIC_RESOURCES.append(reso_node) + + +def configure(conf): + args = [] + + if conf.options.save_temps: + args += ['-save-temps=obj'] + + conf.env.append_value('CFLAGS', args) + +# vim:filetype=python diff --git a/src/bluetooth-fw/da1468x/dialog_waf.py b/src/bluetooth-fw/da1468x/dialog_waf.py new file mode 100644 index 00000000..bea64f51 --- /dev/null +++ b/src/bluetooth-fw/da1468x/dialog_waf.py @@ -0,0 +1,60 @@ +# 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. + +""" +Shared helper functions that are used in both boot/wscript and main/wscript. +FIXME: This file being full of poor practices and that the contents of this file +should not be taken as an example of the proper way to do anything in waf. +""" + +import os +import waftools.c_preprocessor + + +def get_sdk_node(bld): + return bld.path.find_node('../../vendor/bt-dialog-sdk') + + +def collect_sdk_sources(bld, sources_list): + sdk_node = get_sdk_node(bld) + return [sdk_node.find_node(s) for s in sources_list] + + +def generate_mem_ld(bld, config_h_path): + mem_ld_node = bld.path.get_bld().make_node('mem.ld') + mem_ld_h_path = bld.path.find_node('ldscripts/mem.ld.h') + mem_map_h_path = bld.path.find_node('../../include/') + mem_map_node = mem_map_h_path.find_node('da1468x_mem_map.h') + + cflags = [] + include_node = bld.path.find_node(config_h_path) + include_path = include_node.abspath() + cflags.append('-include%s' % include_path) + + sdk_config_include_path = get_sdk_node(bld).find_node( + 'sdk/bsp/config').abspath() + cflags.append('-I%s' % sdk_config_include_path) + cflags.append('-I%s' % mem_map_h_path.abspath()) + + dialog_ic_step_define = bld.get_dialog_ic_step_define() + cflags.append('-D%s' % dialog_ic_step_define) + + bld(rule=waftools.c_preprocessor.c_preproc, + source=mem_ld_h_path, target=mem_ld_node, cflags=cflags) + + bld.add_manual_dependency(mem_ld_node, include_node) + bld.add_manual_dependency(mem_ld_node, mem_map_node) + bld.add_manual_dependency(mem_ld_node, dialog_ic_step_define) + + return mem_ld_node diff --git a/src/bluetooth-fw/da1468x/host/.gitignore b/src/bluetooth-fw/da1468x/host/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/src/bluetooth-fw/da1468x/host/adv_reconnect.c b/src/bluetooth-fw/da1468x/host/adv_reconnect.c new file mode 100644 index 00000000..fee762aa --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/adv_reconnect.c @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include "comm/ble/gap_le_advert.h" + +#include +#include + +const GAPLEAdvertisingJobTerm *bt_driver_adv_reconnect_get_job_terms(size_t *num_terms_out) { + static const GAPLEAdvertisingJobTerm s_advert_terms[] = { + // When starting to advertise for reconnection, burst for 25 seconds: + [0] = { + .duration_secs = 25, + .min_interval_slots = 244, // 152.5 ms -- Apple-recommended advertising interval + .max_interval_slots = 256, // 160.0 ms + }, + + // For the remainder of the advertising for reconnection, cycle between 5 second burst and + // 20 seconds of low-duty cycle advertising. The Dialog chip is pretty power-efficient, so + // no need to have completely silent periods like we have with the TI CC2564. + // When Android does a 100% duty cycle scan, it should pick up the low-duty cycle advertising + // reasonably quickly. + [1] = { + .duration_secs = 5, + .min_interval_slots = 244, // 152.5 ms -- Apple-recommended advertising interval + .max_interval_slots = 256, // 160.0 ms + }, + [2] = { + .duration_secs = 20, + .min_interval_slots = 1636, // 1022.5 ms -- Apple-recommended advertising interval + .max_interval_slots = 1656, // 1035.0 ms + }, + [3] = { + .duration_secs = GAPLE_ADVERTISING_DURATION_LOOP_AROUND, + .loop_around_index = 1, + }, + }; + *num_terms_out = ARRAY_LENGTH(s_advert_terms); + return s_advert_terms; +} diff --git a/src/bluetooth-fw/da1468x/host/advert.c b/src/bluetooth-fw/da1468x/host/advert.c new file mode 100644 index 00000000..91ba64a8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/advert.c @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#include +#include + +#include +#include + +#include "advert_state.h" +#include "hc_protocol/hc_endpoint_advert.h" +#include "hc_protocol/hc_protocol.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include "ble_common.h" + +static bool prv_finish_request_response(HcProtocolMessage *request, HcProtocolMessage *resp, + bool should_free_request) { + bool rv = false; + ble_error_t error = BLE_ERROR_FAILED; + if (resp) { + if (request->command_id == HcMessageID_Advert_Enable) { + const HcAdvertEnableResponseData *data = (const HcAdvertEnableResponseData *)resp->payload; + error = data->error; + if (data->current_state != AdvertState_Running) { + PBL_LOG(LOG_LEVEL_INFO, "State was 0x%x after advert enable", data->current_state); + } + } else { + const ble_error_t *error_ptr = (const ble_error_t *)resp->payload; + error = *error_ptr; + } + if (error == BLE_STATUS_OK) { + rv = true; + } else { + PBL_LOG(LOG_LEVEL_ERROR, "Advert error: 0x%"PRIx8" for cmd: 0x%"PRIx8, + error, request->command_id); + } + } else { + PBL_LOG(LOG_LEVEL_ERROR, "Advert timeout for cmd: 0x%"PRIx8, request->command_id); + } + if (should_free_request) { + kernel_free(request); + } + kernel_free(resp); + return rv; +} + +void bt_driver_advert_advertising_disable(void) { + HcProtocolMessage request = { + .message_length = sizeof(request), + .endpoint_id = HcEndpointID_Advert, + .command_id = HcMessageID_Advert_Disable, + }; + HcProtocolMessage *resp = hc_protocol_enqueue_and_expect(&request); + prv_finish_request_response(&request, resp, false /* should_free_request */); +} + +bool bt_driver_advert_client_get_tx_power(int8_t *tx_power) { + // TODO PBL-34354: Must use ble_read_tx_power, but we don't know the connection index here. + // Will have to figure out how to store and keep track of them. + *tx_power = 0; + return true; +} + +void bt_driver_advert_set_advertising_data(const BLEAdData *ad_data) { + const uint32_t ad_data_len = + sizeof(BLEAdData) + ad_data->ad_data_length + ad_data->scan_resp_data_length; + + const uint32_t alloc_size = sizeof(HcProtocolMessage) + ad_data_len; + HcProtocolMessage *request = kernel_malloc_check(alloc_size); + *request = (HcProtocolMessage) { + .message_length = alloc_size, + .endpoint_id = HcEndpointID_Advert, + .command_id = HcMessageID_Advert_SetAdvData, + }; + memcpy(request->payload, ad_data, ad_data_len); + + HcProtocolMessage *resp = hc_protocol_enqueue_and_expect(request); + prv_finish_request_response(request, resp, true /* should_free_request */); +} + +bool bt_driver_advert_advertising_enable(uint32_t min_interval_ms, uint32_t max_interval_ms, + bool enable_scan_resp) { + const uint32_t alloc_size = sizeof(HcProtocolMessage) + sizeof(HcAdvertEnableData); + HcProtocolMessage *request = kernel_malloc_check(alloc_size); + *request = (HcProtocolMessage) { + .message_length = alloc_size, + .endpoint_id = HcEndpointID_Advert, + .command_id = HcMessageID_Advert_Enable, + }; + HcAdvertEnableData *request_data = (HcAdvertEnableData *)&request->payload[0]; + *request_data = (HcAdvertEnableData) { + .min_interval_ms = min_interval_ms, + .max_interval_ms = max_interval_ms, + }; + + HcProtocolMessage *resp = hc_protocol_enqueue_and_expect(request); + return prv_finish_request_response(request, resp, true /* should_free_request */); +} + +// These are essentially stubs that were needed for bugs in another Bluetooth stack. +bool bt_driver_advert_is_connectable(void) { + return true; +} + +bool bt_driver_advert_client_has_cycled(void) { + return true; +} + +void bt_driver_advert_client_set_cycled(bool has_cycled) { + // nothing +} + +bool bt_driver_advert_should_not_cycle(void) { + return false; +} diff --git a/src/bluetooth-fw/da1468x/host/analytics.c b/src/bluetooth-fw/da1468x/host/analytics.c new file mode 100644 index 00000000..3ba2d3bc --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/analytics.c @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_analytics.h" + +#include "comm/ble/gap_le_connection.h" + +#include +#include + +#include +#include + +bool bt_driver_analytics_get_connection_quality(const BTDeviceInternal *address, + uint8_t *link_quality_out, int8_t *rssi_out) { + // Link Quality not implemented in Dialog SDK + *link_quality_out = 0; + return hc_endpoint_analytics_get_connection_quality(address, rssi_out); +} + +bool bt_driver_analytics_collect_ble_parameters(const BTDeviceInternal *addr, + LEChannelMap *le_chan_map_res) { + return hc_endpoint_analytics_collect_ble_parameters(le_chan_map_res); +} + +void bt_driver_analytics_external_collect_chip_specific_parameters(void) { +} + +void bt_driver_analytics_external_collect_bt_chip_heartbeat(void) { + hc_endpoint_analytics_collect_heartbeat_data(); +} + +//! Returns true iff there are connection event stats to report +bool bt_driver_analytics_get_conn_event_stats(SlaveConnEventStats *stats) { + return hc_endpoint_analytics_get_conn_event_stats(stats); +} diff --git a/src/bluetooth-fw/da1468x/host/bonding_sync.c b/src/bluetooth-fw/da1468x/host/bonding_sync.c new file mode 100644 index 00000000..12001712 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/bonding_sync.c @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_bonding_sync.h" + +#include "comm/ble/gatt_client_discovery.h" +#include "bonding_flags.h" + +#include +#include + +#include "system/hexdump.h" +#include "system/logging.h" + +static void prv_dump_bonding(const BleBonding *bonding) { +#ifndef RELEASE + PBL_LOG(LOG_LEVEL_INFO, "Dumping pairing info (LTK, Rand, Ediv, IRK):"); + PBL_HEXDUMP(LOG_LEVEL_INFO, (const uint8_t *)&bonding->pairing_info.local_encryption_info.ltk, + sizeof(bonding->pairing_info.local_encryption_info.ltk)); + PBL_HEXDUMP(LOG_LEVEL_INFO, (const uint8_t *)&bonding->pairing_info.local_encryption_info.rand, + sizeof(bonding->pairing_info.local_encryption_info.rand)); + PBL_HEXDUMP(LOG_LEVEL_INFO, (const uint8_t *)&bonding->pairing_info.local_encryption_info.ediv, + sizeof(bonding->pairing_info.local_encryption_info.ediv)); + PBL_HEXDUMP(LOG_LEVEL_INFO, (const uint8_t *)&bonding->pairing_info.irk, + sizeof(bonding->pairing_info.irk)); +#endif +} + +void bt_driver_handle_host_added_bonding(const BleBonding *bonding) { + hc_endpoint_bonding_sync_add(bonding); + prv_dump_bonding(bonding); +} + +void bt_driver_handle_host_removed_bonding(const BleBonding *bonding) { + hc_endpoint_bonding_sync_remove(bonding); +} + +void bonding_sync_handle_hc_add(const BleBonding *bonding) { + // By this time, Dialog is using the identity address to refer to the connection + // (instead of the actual connection address): + const BTDeviceInternal *address = &bonding->pairing_info.identity; + bt_driver_cb_handle_create_bonding(bonding, &address->address); + prv_dump_bonding(bonding); + + if (bonding->flags & BleBondingFlag_IsReversedPPoGATTEnabled) { + // Trigger GATT service rediscovery to find the emulated PPoGATT server + gatt_client_discovery_discover_all(address); + } +} + +void bonding_sync_handle_hc_remove(const BleBonding *bonding) { + PBL_LOG(LOG_LEVEL_ERROR, "Controller isn't supposed to remove bondings!"); +} diff --git a/src/bluetooth-fw/da1468x/host/bt_classic_stubs.c b/src/bluetooth-fw/da1468x/host/bt_classic_stubs.c new file mode 100644 index 00000000..ddd0ea2a --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/bt_classic_stubs.c @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#include + +void bt_driver_classic_disconnect(const BTDeviceAddress* address) { +} + +bool bt_driver_classic_is_connected(void) { + return false; +} + +bool bt_driver_classic_copy_connected_address(BTDeviceAddress* address) { + return false; +} + +bool bt_driver_classic_copy_connected_device_name(char nm[BT_DEVICE_NAME_BUFFER_SIZE]) { + return false; +} diff --git a/src/bluetooth-fw/da1468x/host/bt_conn_mgr.c b/src/bluetooth-fw/da1468x/host/bt_conn_mgr.c new file mode 100644 index 00000000..25ba4f9e --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/bt_conn_mgr.c @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include "comm/bt_conn_mgr.h" + +void conn_mgr_set_bt_classic_conn_response_time_ext( + struct Remote *remote, BtConsumer consumer, ResponseTimeState state, + uint16_t max_period_secs, ResponsivenessGrantedHandler granted_handler) { +} + +//! Same as conn_mgr_set_bt_classic_conn_response_time_ext, but without granted_handler.s +void conn_mgr_set_bt_classic_conn_response_time( + struct Remote *remote, BtConsumer consumer, ResponseTimeState state, + uint16_t max_period_secs) { +} diff --git a/src/bluetooth-fw/da1468x/host/bt_test.c b/src/bluetooth-fw/da1468x/host/bt_test.c new file mode 100644 index 00000000..fbe65c0c --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/bt_test.c @@ -0,0 +1,309 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "comm/ble/gap_le_connection.h" +#include "console/console_internal.h" +#include "console/prompt.h" +#include "console/serial_console.h" +#include "drivers/accessory.h" +#include "hc_protocol/hc_endpoint_chip_id.h" +#include "hc_protocol/hc_endpoint_hci.h" +#include "hc_protocol/hc_endpoint_test.h" +#include "kernel/pbl_malloc.h" +#include "services/common/system_task.h" +#include "system/logging.h" + +#include +#include + +static bool s_hci_passthrough_enabled = false; +static bool s_test_mode_enabled = false; +static BTDriverResponseCallback s_response_callback = NULL; + +void bt_driver_test_start(void) { + // we shouldn't need any special config for tests + BTDriverConfig config = { }; + bt_driver_start(&config); + + s_test_mode_enabled = true; +} + +void bt_driver_test_stop(void) { + bt_driver_stop(); + s_test_mode_enabled = false; +} + +void bt_driver_test_enter_hci_passthrough(void) { + s_hci_passthrough_enabled = true; +} + +bool bt_driver_test_enter_rf_test_mode(void) { + return true; +} + +void bt_driver_test_set_spoof_address(const BTDeviceAddress *addr) { + prompt_send_response("NYI!"); +} + +#define HCI_CMD_HEADER_LEN (1 /* hci packet type */ + 2 /* op code */ + 1 /* param len */) +#define MAX_SUPPORTED_HCI_PARAM_LEN 25 +#define MAX_SUPPORTED_HCI_CMD_LEN (MAX_SUPPORTED_HCI_PARAM_LEN + HCI_CMD_HEADER_LEN) + +typedef struct { + bool found_cmd_start; + uint8_t param_len; + uint8_t write_offset; + uint8_t data_buf[MAX_SUPPORTED_HCI_CMD_LEN]; // holds one HCI cmd +} HciCmdInfo; + +static void prv_handle_hci_cmd(void *hci_data) { + uint8_t *data = hci_data; + uint8_t payload_len = data[0]; + uint8_t *hci_cmd = &data[1]; + hc_endpoint_enqueue_hci_cmd(hci_cmd, payload_len); + kernel_free(hci_data); +} + +//! A barebones HCI CMD parser. Looks for an HCI start byte (0x1) and then +//! makes sure the param length is less than the static buffer we have for +//! holding a single command. If anything goes wrong, the state machine resets +//! and starts looking for 0x1 again. If we are not in the process of decoding +//! a command a ctrl-d will exit passthrough mode. +void bt_driver_test_handle_hci_passthrough_character( + char c, bool *should_context_switch) { + static HciCmdInfo s_hci_cmd_info = {}; + + if (!s_hci_cmd_info.found_cmd_start) { + if (c == 0x4) { // Exit bypass mode sequence + serial_console_set_state(SERIAL_CONSOLE_STATE_LOGGING); + s_hci_passthrough_enabled = false; + return; + } + + if (c != 0x1) { // HCI cmd should start with 0x1 + return; + } + + s_hci_cmd_info.found_cmd_start = true; + } + + if (s_hci_cmd_info.write_offset == 3) { + s_hci_cmd_info.param_len = (uint8_t)c; + + if (s_hci_cmd_info.param_len > MAX_SUPPORTED_HCI_PARAM_LEN) { + PBL_LOG(LOG_LEVEL_WARNING, "Longer HCI CMD than expected!"); + s_hci_cmd_info = (HciCmdInfo) { }; // reset + return; + } + } + + // We've received an expected byte, so store it + s_hci_cmd_info.data_buf[s_hci_cmd_info.write_offset] = c; + s_hci_cmd_info.write_offset++; + + if (s_hci_cmd_info.write_offset == (HCI_CMD_HEADER_LEN + s_hci_cmd_info.param_len)) { + // We have found an HCI command, forward to the dialog chip + uint8_t payload_len = s_hci_cmd_info.write_offset; + uint8_t buf_len = payload_len + 1; + uint8_t *buf = kernel_zalloc_check(buf_len); + buf[0] = payload_len; + memcpy(&buf[1], &s_hci_cmd_info.data_buf[0], payload_len); + + bool should_context_switch; + system_task_add_callback_from_isr(prv_handle_hci_cmd, buf, &should_context_switch); + + // reset our state machine + s_hci_cmd_info = (HciCmdInfo) { }; + } +} + +bool bt_driver_test_selftest(void) { + DialogChipID chip_id; + if (!hc_endpoint_chip_id_query_chip_info(&chip_id)) { + return false; + } + // Sanity check the timestamp that should always be programmed: + return (chip_id.info.timestamp > DIALOG_CHIP_ID_MIN_TIMESTAMP); +} + +bool bt_driver_test_mfi_chip_selftest(void) { + return false; +} + +// A simple parser for HCI Command Complete events. If we are not in HCI +// passthrough mode, this is the only type of command we expect to receive + +#define HCI_CMD_START 0x1 +#define HCI_EVT_START 0x4 + +#define HCI_LE_RX_TEST_OCF 0x1D +#define HCI_LE_TX_TEST_OCF 0x1E +#define HCI_LE_TEST_STOP_OCF 0x1F + +#define HCI_CMD_COMPLETE_CODE 0xe +#define OPCODE_TEST_CMDS_MSB 0x20 // LE Controller Commands OGF=0x8 + +static void prv_accessory_putstr(char *data) { +#if CAPABILITY_HAS_ACCESSORY_CONNECTOR + accessory_send_data((const uint8_t *)data, strlen(data)); +#endif +} + +static void prv_dbgserial_putstr(char *data) { + while (*data) { + dbgserial_putchar(*data); + data++; + } +} + +bool bt_test_hci_event_handled(const uint8_t *hci_msg, uint16_t payload_len) { + // Don't intercept anything if we are in passthrough mode + if (s_hci_passthrough_enabled) { + return false; + } + + // we expect to handle a "command complete" event so check for it + if ((hci_msg[0] != HCI_EVT_START) || (hci_msg[1] != HCI_CMD_COMPLETE_CODE)) { + return false; + } + + typedef struct { + uint8_t param_length; + uint8_t num_cmd_pkts; + uint8_t lsb_opcode; + uint8_t msb_opcode; + uint8_t payload[]; + } HciCmdCompleteEvt; + + HciCmdCompleteEvt *e = (HciCmdCompleteEvt *)&hci_msg[2]; + + // simple sanity check on payload + if ((e->param_length == 0) || (e->msb_opcode != OPCODE_TEST_CMDS_MSB)) { + return false; + } + + void (*flush_data)(char *) = serial_console_is_prompt_enabled() ? + prv_dbgserial_putstr : prv_accessory_putstr; + + char buf[40]; + uint8_t status = e->payload[0]; + snprintf(buf, sizeof(buf), "%s: cmd = 0x%x Status = 0x%x\r\n", + (status == HciStatusCode_Success) ? "Success!" : "Failure", + (int)e->lsb_opcode, (int)status); + flush_data(buf); + + snprintf(buf, sizeof(buf), "HCI Event:"); + flush_data(buf); + + for (int i = 0; i < payload_len; i++) { + snprintf(buf, sizeof(buf), " 0x%02x", hci_msg[i]); + flush_data(buf); + } + + flush_data("\r\n"); + + if (s_response_callback) { + s_response_callback(status, e->payload); + } + + return true; +} + +bool bt_test_chip_in_test_mode(void) { + return s_test_mode_enabled; +} + +static bool prv_in_test_mode(void) { + if (!s_test_mode_enabled) { + char buf[80]; + prompt_send_response_fmt( + buf, sizeof(buf), "Not in test mode, run 'bt test start' first!"); + return false; + } + return true; +} + +void bt_driver_le_transmitter_test( + uint8_t tx_channel, uint8_t tx_packet_length, uint8_t packet_payload_type) { + if (!prv_in_test_mode()) { + return; + } + + uint8_t tx_test_cmd[] = { + HCI_CMD_START, + HCI_LE_TX_TEST_OCF, + OPCODE_TEST_CMDS_MSB, + 0x3, // param length + tx_channel, + tx_packet_length, + packet_payload_type + }; + + hc_endpoint_enqueue_hci_cmd(tx_test_cmd, sizeof(tx_test_cmd)); +} + +void bt_driver_le_receiver_test(uint8_t rx_channel) { + if (!prv_in_test_mode()) { + return; + } + + uint8_t rx_test_cmd[] = { + HCI_CMD_START, + HCI_LE_RX_TEST_OCF, + OPCODE_TEST_CMDS_MSB, + 0x1, // param length + rx_channel + }; + hc_endpoint_enqueue_hci_cmd(rx_test_cmd, sizeof(rx_test_cmd)); +} + +void bt_driver_le_test_end(void) { + if (!prv_in_test_mode()) { + return; + } + + uint8_t test_end_cmd[] = { + HCI_CMD_START, + HCI_LE_TEST_STOP_OCF, + OPCODE_TEST_CMDS_MSB, + 0x0 // param length + }; + hc_endpoint_enqueue_hci_cmd(test_end_cmd, sizeof(test_end_cmd)); +} + +void bt_driver_register_response_callback(BTDriverResponseCallback callback) { + s_response_callback = callback; +} + +void bt_driver_start_unmodulated_tx(uint8_t tx_channel) { + if (!prv_in_test_mode()) { + return; + } + + hc_endpoint_test_unmodulated_tx_test_start(tx_channel); +} + +void bt_driver_stop_unmodulated_tx(void) { + if (!prv_in_test_mode()) { + return; + } + + hc_endpoint_test_unmodulated_tx_test_stop(); +} diff --git a/src/bluetooth-fw/da1468x/host/comm.c b/src/bluetooth-fw/da1468x/host/comm.c new file mode 100644 index 00000000..fc31274d --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/comm.c @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#include "bluetooth/bt_driver_comm.h" +#include "kernel/event_loop.h" + +#include + +static void prv_send_job(void *data) { + CommSession *session = (CommSession *)data; + bt_driver_run_send_next_job(session, true); +} + +bool bt_driver_comm_schedule_send_next_job(CommSession *session) { + launcher_task_add_callback(prv_send_job, session); + return true; // we croak if a task cannot be scheduled on KernelMain +} + +bool bt_driver_comm_is_current_task_send_next_task(void) { + return launcher_task_is_current_task(); +} diff --git a/src/bluetooth-fw/da1468x/host/connectability.c b/src/bluetooth-fw/da1468x/host/connectability.c new file mode 100644 index 00000000..6637917f --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/connectability.c @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#include + +void bt_driver_classic_update_connectability(void) { + // No-op, da1468x doesn't speak classic... +} diff --git a/src/bluetooth-fw/da1468x/host/core_dump.c b/src/bluetooth-fw/da1468x/host/core_dump.c new file mode 100644 index 00000000..852a4567 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/core_dump.c @@ -0,0 +1,321 @@ +/* + * 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. + */ + +#include "core_dump.h" +#include "dialog_spi.h" + +#include "FreeRTOS.h" +#include "semphr.h" + +#include "drivers/flash.h" +#include "kernel/core_dump.h" +#include "kernel/core_dump_private.h" +#include "kernel/pbl_malloc.h" +#include "kernel/util/sleep.h" +#include "services/common/bluetooth/bluetooth_ctl.h" +#include "system/logging.h" +#include "util/attributes.h" +#include "util/build_id.h" +#include "util/crc32.h" +#include "util/math.h" + +#include + +#if PLATFORM_ROBERT +#define CORE_DUMP_RX_BUFFER_SIZE (16 * 1024) +// Place in DTCM on Robert +static unsigned char DMA_BSS DMA_READ_BSS s_rx_buffer[CORE_DUMP_RX_BUFFER_SIZE]; +#else +#define CORE_DUMP_RX_BUFFER_SIZE (1 * 1024) +static unsigned char *s_rx_buffer; +#endif +static uint32_t DMA_BSS DMA_READ_BSS s_rx_crc; + +static SemaphoreHandle_t s_spi_dma_semph; +static SemaphoreHandle_t s_spi_int_semph; + + +static void prv_handshake_int_exti_cb(bool *should_context_switch) { + // Ignore Int -- not needed during initial handshaking + *should_context_switch = false; +} + + +static bool prv_handshake(void) { + uint8_t buffer[sizeof(core_dump_connect_response)]; + + PBL_LOG(LOG_LEVEL_ERROR, "BLE Hang/Crash ... attempt to connect"); + // Attempt to handshake with the controller + for (int count = 0; count < 100; ++count) { + + psleep(10); + dialog_spi_transmit_data_no_wait((uint8_t *)core_dump_connect_ping, + sizeof(core_dump_connect_ping)); + psleep(10); + dialog_spi_receive_data_no_wait(buffer, sizeof(buffer)); + if (memcmp(buffer, core_dump_connect_response, sizeof(core_dump_connect_response)) == 0) { + PBL_LOG(LOG_LEVEL_INFO, "Core Dump connect response received"); + return true; + } + } + PBL_LOG(LOG_LEVEL_ERROR, "Failed to connect with BLE chip to collect coredump"); + return false; +} + +static void prv_int_exti_cb(bool *should_context_switch) { + xSemaphoreGiveFromISR(s_spi_int_semph, NULL); +} + +static void prv_dma_cb(bool *should_context_switch) { + portBASE_TYPE was_higher_task_woken = false; + xSemaphoreGiveFromISR(s_spi_dma_semph, &was_higher_task_woken); + *should_context_switch = (was_higher_task_woken != false); +} + +static bool prv_spi_send_and_receive_dma( + const uint8_t *tx_buffer, uint8_t *rx_buffer, size_t length) { + // arbitrary timeout that should give us plenty of time to receieve a response within + const uint32_t tick_timeout = milliseconds_to_ticks(6000); + + bool success = false; + if (xSemaphoreTake(s_spi_int_semph, tick_timeout) == pdTRUE) { + dialog_spi_send_and_receive_dma(tx_buffer, rx_buffer, length, prv_dma_cb); + success = (xSemaphoreTake(s_spi_dma_semph, tick_timeout) == pdTRUE); + } + + return success; +} + +static bool prv_spi_transaction(CoreDumpSPICmd *cmd, uint8_t *buffer, size_t length) { + // The protocol works thustly: 1) wait for EXTI INT, which the controller asserts when it's ready + // to receive. 2) Send/receive data. 3) Wait for DMA to complete. + // The overall transaction is: 1) send cmd 2) receive data, 3) receive CRC, 4) confirm CRC. + + cmd->crc32 = crc32(CRC32_INIT, cmd, sizeof(CoreDumpSPICmd) - sizeof(cmd->crc32)); + + if (!prv_spi_send_and_receive_dma((uint8_t *)cmd, NULL, sizeof(CoreDumpSPICmd))) { + return false; + } + + if (length) { + if (!prv_spi_send_and_receive_dma(NULL, buffer, length) || + !prv_spi_send_and_receive_dma(NULL, (uint8_t *)&s_rx_crc, sizeof(uint32_t))) { + return false; + } + + uint32_t crc = crc32(CRC32_INIT, buffer, length); + if (crc != s_rx_crc) { + return false; // TODO: At least one memory region will crc fail because of changing counter + // variables in the crc32/SPI code. We'll need to move the core dump code to its own region. + } + } + + return true; +} + +static void prv_core_dump_main(void) { + CoreDumpSPICmd cmd; + CoreDumpChunkHeader chunk_hdr; + + uint16_t text_crc_matches; // This can't be a bool (1-byte). The Dialog SPI/DMA Tx will break. + uint8_t build_id[BUILD_ID_TOTAL_EXPECTED_LEN]; + +#if !PLATFORM_ROBERT + s_rx_buffer = kernel_malloc_check(CORE_DUMP_RX_BUFFER_SIZE); +#endif + const size_t region_buf_size = sizeof(MemoryRegion[MemoryRegionTagCount]); + MemoryRegion *region_buf = (MemoryRegion *)kernel_malloc_check(region_buf_size); + + // Does the .text section crc match? + cmd = (CoreDumpSPICmd) { .cmd = CoreDumpCmd_GetTextCRC }; + if (!prv_spi_transaction(&cmd, s_rx_buffer, sizeof(text_crc_matches))) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to get Text CRC"); + goto spi_failed; + } + memcpy(&text_crc_matches, s_rx_buffer, sizeof(text_crc_matches)); + + // Get the Build ID + cmd = (CoreDumpSPICmd) { .cmd = CoreDumpCmd_GetBuildID }; + if (!prv_spi_transaction(&cmd, s_rx_buffer, sizeof(build_id))) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to get Build ID"); + goto spi_failed; + } + memcpy(build_id, s_rx_buffer, sizeof(build_id)); + + // Get the memory region table + cmd = (CoreDumpSPICmd) { .cmd = CoreDumpCmd_ReadRegionTable }; + if (!prv_spi_transaction(&cmd, s_rx_buffer, region_buf_size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to get Region Table"); + goto spi_failed; + } + memcpy(region_buf, s_rx_buffer, region_buf_size); + + // Dump the memory region table + for (int index = 0; index < MemoryRegionTagCount; ++index) { + PBL_LOG(LOG_LEVEL_ERROR, "Memory Region %d: %p - 0x%lx", index, region_buf[index].start, + region_buf[index].length); + } + + // Get a core dump flash memory slot + uint32_t flash_addr, max_size; + if (!core_dump_reserve_ble_slot(&flash_addr, &max_size, (ElfExternalNote *)build_id)) { + PBL_LOG(LOG_LEVEL_ERROR, "Can't reserve slot for BLE core dump"); + goto powerdown; + } + uint32_t flash_addr_max = flash_addr + max_size; + PBL_LOG(LOG_LEVEL_DEBUG, "Using flash slot %lx %lx", flash_addr, flash_addr_max); + + // Read every region from the region table in blocks of 16k. + cmd.cmd = CoreDumpCmd_ReadMemory; + for (int region = 0; region < MemoryRegionTagCount; ++region) { + if (region == MemoryRegionTag_Text && text_crc_matches) { + continue; + } + + // Write out a chunk header + chunk_hdr.key = CORE_DUMP_CHUNK_KEY_MEMORY; + chunk_hdr.size = region_buf[region].length + sizeof(CoreDumpMemoryHeader); + if (flash_addr_max < (flash_addr + sizeof(chunk_hdr) + chunk_hdr.size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Insufficient space for BLE core dump -- RAM"); + goto powerdown; + } + flash_write_bytes((const uint8_t *)&chunk_hdr, flash_addr, sizeof(chunk_hdr)); + flash_addr += sizeof(chunk_hdr); + + // Write out a memory chunk header + CoreDumpMemoryHeader mem_hdr; + mem_hdr.start = (uint32_t)region_buf[region].start; + flash_write_bytes((const uint8_t *)&mem_hdr, flash_addr, sizeof(mem_hdr)); + flash_addr += sizeof(mem_hdr); + + size_t rx_len_remaining = region_buf[region].length; + uint32_t curr_rx_offset = 0; + while (rx_len_remaining) { + const size_t rx_len = MIN(rx_len_remaining, CORE_DUMP_RX_BUFFER_SIZE); + + cmd.len = rx_len; + cmd.addr = (uint8_t *)region_buf[region].start + curr_rx_offset; + if (!prv_spi_transaction(&cmd, s_rx_buffer, rx_len)) { + // TODO goto spi_failed; -- don't download the active controller SPI loop variables! + } + + flash_write_bytes(s_rx_buffer, flash_addr, rx_len); + flash_addr += rx_len; + + curr_rx_offset += rx_len; + rx_len_remaining -= rx_len; + } + } + + // Read the Thread Info + chunk_hdr.key = CORE_DUMP_CHUNK_KEY_THREAD; + chunk_hdr.size = sizeof(CoreDumpThreadInfo); + if (flash_addr_max < (flash_addr + sizeof(chunk_hdr) + chunk_hdr.size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Insufficient space for BLE core dump -- TIB"); + goto powerdown; + } + cmd.cmd = CoreDumpCmd_ReadRunningThreadInfo; + if (!prv_spi_transaction(&cmd, s_rx_buffer, sizeof(CoreDumpThreadInfo))) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to get Thread Info"); + goto spi_failed; + } + flash_write_bytes((const uint8_t *)&chunk_hdr, flash_addr, sizeof(chunk_hdr)); + flash_addr += sizeof(chunk_hdr); + flash_write_bytes(s_rx_buffer, flash_addr, sizeof(CoreDumpThreadInfo)); + flash_addr += sizeof(CoreDumpThreadInfo); + + // Read the Extra Thread Info + CoreDumpExtraRegInfo chunk_extra; + + chunk_hdr.key = CORE_DUMP_CHUNK_KEY_EXTRA_REG; + chunk_hdr.size = sizeof(chunk_extra); + if (flash_addr_max < (flash_addr + sizeof(chunk_hdr) + chunk_hdr.size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Insufficient space for BLE core dump -- ExtraTI"); + goto powerdown; + } + cmd.cmd = CoreDumpCmd_ReadExtraThreadInfo; + if (!prv_spi_transaction(&cmd, s_rx_buffer, sizeof(CoreDumpExtraRegInfo))) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to get Extra Thread Info"); + goto spi_failed; + } + flash_write_bytes((const uint8_t *)&chunk_hdr, flash_addr, sizeof(chunk_hdr)); + flash_addr += sizeof(chunk_hdr); + flash_write_bytes(s_rx_buffer, flash_addr, sizeof(CoreDumpExtraRegInfo)); + flash_addr += sizeof(CoreDumpExtraRegInfo); + + // Terminate the core dump + chunk_hdr.key = CORE_DUMP_CHUNK_KEY_TERMINATOR; + chunk_hdr.size = 0; + if (flash_addr_max < (flash_addr + sizeof(chunk_hdr) + chunk_hdr.size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Insufficient space for BLE core dump terminator"); + goto powerdown; + } + flash_write_bytes((const uint8_t *)&chunk_hdr, flash_addr, sizeof(chunk_hdr)); + +powerdown: + // Power down the chip + cmd = (CoreDumpSPICmd) { .cmd = CoreDumpCmd_LowPowerMode }; + if (!prv_spi_transaction(&cmd, NULL, 0)) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to power down"); + goto spi_failed; + } + +spi_failed: +#if !PLATFORM_ROBERT + kernel_free(s_rx_buffer); +#endif + kernel_free(region_buf); + + PBL_LOG(LOG_LEVEL_INFO, "BLE Core Dump complete"); +} + +// NB: this function runs on the host_transport thread (BTTrans). +static void prv_core_dump(void) { + // Disable host-transport's use of SPI & interrupts + dialog_spi_deinit(); + + s_spi_dma_semph = xSemaphoreCreateBinary(); + s_spi_int_semph = xSemaphoreCreateBinary(); + + // Enable SPI. Use our own interrupt handler. + dialog_spi_init(prv_handshake_int_exti_cb); + if (prv_handshake()) { + dialog_spi_deinit(); + dialog_spi_init(prv_int_exti_cb); + prv_core_dump_main(); + } + dialog_spi_deinit(); + + vSemaphoreDelete(s_spi_dma_semph); + vSemaphoreDelete(s_spi_int_semph); +} + +void core_dump_and_reset_or_reboot(void) { + prv_core_dump(); +#if REBOOT_ON_BT_CRASH + RebootReason reboot_reason = { + .code = RebootReasonCode_BtCoredump, + }; + + reboot_reason_set(&reboot_reason); + + // Just reset the board, we don't want to wind up adding a normal coredump for the MCU + system_hard_reset(); +#else + // Core dump has completed. Trigger a stack reset. + bt_ctl_reset_bluetooth(); +#endif +} diff --git a/src/bluetooth-fw/da1468x/host/dialog_bootrom.c b/src/bluetooth-fw/da1468x/host/dialog_bootrom.c new file mode 100644 index 00000000..ecdea0cc --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/dialog_bootrom.c @@ -0,0 +1,308 @@ +/* + * 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. + */ + +#include "board/board.h" +#include "drivers/accessory.h" +#include "drivers/gpio.h" +#include "drivers/periph_config.h" +#include "drivers/uart.h" +#include "kernel/util/delay.h" +#include "resource/resource.h" +#include "resource/resource_ids.auto.h" +#include "system/logging.h" +#include "system/passert.h" +#include "util/math.h" + +#include +#include + +// The dialog BLE chip has a baked in bootloader capable of talking serial +// protocols. The bootloader allows a contiguous memory region to be loaded +// into RAM on the dialog chip. Early revisions of the DA1468x only support +// UART so we use that interface to accomodate this limitation. For more info +// on the interface itself see: +// +// http://support.dialog-semiconductor.com/resource/b-001-da14580-booting-serial-interfaces + +#define DIALOG_BOOTROM_UART_BAUDRATE 57600 // Baudrate support baked into Dialog bootROM +#define DIALOG_BOOTROM_MAX_IMAGE_LOAD_SIZE UINT16_MAX + +// Empirically, it seems to take 24ms for the revAD silicon to come out of +// reset and send the start message. This is way longer than its going to +// take to get a response to other bootROM messages so let's assume that's +// the max worst case wait time. To account for some variance, let's wait +// 100ms to play it safe. +#define DIALOG_BOOTROM_DEFAULT_CHAR_TIMEOUT_MS (100) +// For the revAE silicon, usually it takes ~220ms from deasserting RST to getting the +// first chip ID character over the UART RX, but I've observed it taking ~400ms at other times. +#define DIALOG_BOOTROM_FIRST_CHAR_TIMEOUT_MS (600) + +// Some platform use two different UART peripherals! This means things need +// to be initialized and torn down seperately! +static bool prv_platform_uses_two_uart_peripherals(void) { + return (BT_RX_BOOTROM_UART != BT_TX_BOOTROM_UART); +} + +static void prv_init_dialog_bootrom_interface(void) { + periph_config_acquire_lock(); + + if (prv_platform_uses_two_uart_peripherals()) { + uart_init_rx_only(BT_RX_BOOTROM_UART); + uart_init_tx_only(BT_TX_BOOTROM_UART); + uart_set_baud_rate(BT_RX_BOOTROM_UART, DIALOG_BOOTROM_UART_BAUDRATE); + uart_set_baud_rate(BT_TX_BOOTROM_UART, DIALOG_BOOTROM_UART_BAUDRATE); + } else { + uart_init(BT_RX_BOOTROM_UART); + uart_set_baud_rate(BT_RX_BOOTROM_UART, DIALOG_BOOTROM_UART_BAUDRATE); + } + + periph_config_release_lock(); +} + +static void prv_deinit_dialog_bootrom_interface(void) { + periph_config_acquire_lock(); + if (prv_platform_uses_two_uart_peripherals()) { + uart_deinit(BT_RX_BOOTROM_UART); + uart_deinit(BT_TX_BOOTROM_UART); + } else { + uart_deinit(BT_RX_BOOTROM_UART); + } + periph_config_release_lock(); +} + +static void prv_setup_dialog_reset_gpio(void) { + periph_config_acquire_lock(); + + // configure the GPIO for RST: + gpio_output_init(&BOARD_CONFIG_BT_COMMON.reset, GPIO_OType_PP, GPIO_Speed_25MHz); + + periph_config_release_lock(); +} + +static void prv_enter_reset(void) { + gpio_output_set(&BOARD_CONFIG_BT_COMMON.reset, true); + delay_us(100); +} + +static void prv_exit_reset(void) { + gpio_output_set(&BOARD_CONFIG_BT_COMMON.reset, false); +} + +static void prv_send_char(uint8_t c) { + uart_write_byte(BT_TX_BOOTROM_UART, c); + uart_wait_for_tx_complete(BT_TX_BOOTROM_UART); +} + +static bool prv_bootrom_wait_for_char_with_timeout(char *char_received, uint32_t wait_time_ticks) { + const uint32_t end_ticks = rtc_get_ticks() + wait_time_ticks; + + do { + UARTRXErrorFlags errors = uart_has_errored_out(BT_RX_BOOTROM_UART); + if (errors.error_mask != 0) { + uint16_t dr = uart_read_byte(BT_RX_BOOTROM_UART); + + // The Dialog bootROM attempts to talk several serial protocols. While doing this it will + // reconfigure gpios as inputs and outputs and change what data it is pushing on the lines. + // For some architectures, this can result in framing errors getting detected. Therefore, if + // it's a frame error, let's carry on and see if the data we read matches what we expect + // anyway + if (!errors.framing_error) { + PBL_LOG(LOG_LEVEL_WARNING, "UART ERROR: 0x%x 0x%d", (int)errors.error_mask, (int)dr); + return false; + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "Dialog bootrom framing error, continuing onward"); + } + } + + if (uart_is_rx_ready(BT_RX_BOOTROM_UART)) { + *char_received = uart_read_byte(BT_RX_BOOTROM_UART); + return true; + } + } while (rtc_get_ticks() <= end_ticks); + + PBL_LOG(LOG_LEVEL_ERROR, "Timed out waiting for char"); + return false; +} + +static bool prv_bootrom_wait_for_char(char *char_received) { + const uint32_t wait_time_ticks = (DIALOG_BOOTROM_DEFAULT_CHAR_TIMEOUT_MS * RTC_TICKS_HZ) / 1000; + return prv_bootrom_wait_for_char_with_timeout(char_received, wait_time_ticks); +} + +static bool prv_download_patch_from_system_resources(uint16_t bootloader_size) { + uint8_t running_xor = 0; + uint32_t curr_offset = 0; + + while (curr_offset < bootloader_size) { + const size_t bytes_left = bootloader_size - curr_offset; + + uint8_t data_buf[256] = { 0 }; + const size_t read_len = MIN(bytes_left, sizeof(data_buf)); + + if (!resource_load_byte_range_system(SYSTEM_APP, RESOURCE_ID_BT_BOOT_IMAGE, curr_offset, + data_buf, read_len)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to load patch from system resources"); + return false; + } + + for (uint32_t i = 0; i < read_len; i++) { + running_xor ^= data_buf[i]; + prv_send_char(data_buf[i]); + } + + curr_offset += read_len; + } + + // Note: As soon as we send the last byte, the bootROM will send the crc which is + // simply an xor of the bytes transmitted + + char c; + if (!prv_bootrom_wait_for_char(&c)) { + PBL_LOG(LOG_LEVEL_ERROR, "Error waiting for CRC response"); + return false; + } + if (c == running_xor) { + prv_send_char(0x06); + return true; + } + + PBL_LOG(LOG_LEVEL_ERROR, "CRC mismatch! expected=0x%"PRIx8" vs recv'd=0x%"PRIx8, running_xor, c); + return false; +} + +static bool prv_send_header(uint16_t bootloader_size) { + // Send the header + prv_send_char(0x01); // Start of Header: 0x01 + prv_send_char(bootloader_size & 0xff); // LEN_LSB + prv_send_char((bootloader_size >> 8) & 0xff); // LEN_MSB + + char c; + if (!prv_bootrom_wait_for_char(&c)) { + PBL_LOG(LOG_LEVEL_ERROR, "Error waiting for header response"); + return false; + } + if (c != 0x06) { + PBL_LOG(LOG_LEVEL_ERROR, "Bootrom did not accept header (0x%x)", c); + return false; + } + return true; +} + +static bool prv_load_second_stage_bootloader(void) { + bool success = true; + + // Find the image we are going to be downloading + uint32_t bootloader_size = resource_size(SYSTEM_APP, RESOURCE_ID_BT_BOOT_IMAGE); + PBL_ASSERTN(bootloader_size < DIALOG_BOOTROM_MAX_IMAGE_LOAD_SIZE); + + prv_setup_dialog_reset_gpio(); + + // we don't know what kind of state the DA1468x is in so put the chip in reset + // before we start listening on the UART for messages + prv_enter_reset(); + +#if ACCESSORY_UART_IS_SHARED_WITH_BT + // We are sharing a UART with the accessory port, so we need to block it from being used while we + // are using it and then unblock at the end. + accessory_block(); +#endif + + // Configure GPIOs & peripherals + prv_init_dialog_bootrom_interface(); + + // UART is configured and listening for the start message, so exit reset + prv_exit_reset(); + + // Read until 0x02 start message is found: + // Rev "AD" and before do not send out their chip ID before the 0x02 start message. + // Rev "AE" and later send out their chip ID *twice* ("DA14681-01" or "DA14681AE \n\r") + // before the 0x02 start message. + char c; + // 32 chars is more than needed, but let's add a bit of slack in case they extend it. + const uint32_t max_chip_id_len = 32; + uint32_t wait_time_ms = DIALOG_BOOTROM_FIRST_CHAR_TIMEOUT_MS; + for (uint32_t i = 0; i < max_chip_id_len; ++i) { + const uint32_t wait_time_ticks = (wait_time_ms * RTC_TICKS_HZ) / 1000; + if (!prv_bootrom_wait_for_char_with_timeout(&c, wait_time_ticks)) { + PBL_LOG(LOG_LEVEL_ERROR, "Error waiting for chip ID / start message (i=%"PRIu32")", i); + success = false; + goto cleanup; + } + if (c == 0x02) { + // Got start message! + break; + } else if (c == 'D') { // First valid character we expect to receive for AE silicon + wait_time_ms = DIALOG_BOOTROM_DEFAULT_CHAR_TIMEOUT_MS; + } + } + + if (c != 0x02) { + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected start message: 0x%02x", c); + success = false; + goto cleanup; + } + + // Got the start message so send the header + if (!prv_send_header(bootloader_size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to send header"); + success = false; + goto cleanup; + } + + // Transmit SW code bytes, check received CRC, and ack if correct + if (!prv_download_patch_from_system_resources(bootloader_size)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to load second stage BLE bootloader"); + success = false; + goto cleanup; + } + +cleanup: + // We have loaded the image, disable the peripheral + prv_deinit_dialog_bootrom_interface(); +#if ACCESSORY_UART_IS_SHARED_WITH_BT + // We are sharing a UART with the accessory port, so we need to block it from being used while we + // are using it and then unblock at the end. + accessory_unblock(); +#endif + + if (success) { + PBL_LOG(LOG_LEVEL_DEBUG, "Succesfully loaded second stage BLE bootloader!"); + } + return success; +} + +bool dialog_bootrom_load_second_stage_bootloader(void) { + // Note: It seems that the UART bootloader has a timeout between each + // character receieved. If it does not get a response in time, it will + // restart the bootrom sequence, searching for the device to talk to + // resulting in USART OREs. While we could use a critical section this would + // wind up blocking INTs for a little under a second which is too + // long. During boot up, other things should not be running yet, but if we + // need to reset the chip while everything is booted it's possible we don't + // service a message before the timeout. Thus, retry a couple times to bring + // the BLE chip out of reset before admitting defeat. + int retries = 0; + const int max_retries = 3; + while (retries++ < max_retries) { + PBL_LOG(LOG_LEVEL_DEBUG, "Starting BLE Second Stage Bootloader, attempt %d", retries); + if (prv_load_second_stage_bootloader()) { + PBL_LOG(LOG_LEVEL_INFO, "BLE Second Stage Bootloader loaded in %d attempts!", retries); + return true; + } + } + + return false; +} diff --git a/src/bluetooth-fw/da1468x/host/dialog_bootrom.h b/src/bluetooth-fw/da1468x/host/dialog_bootrom.h new file mode 100644 index 00000000..54d9cadb --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/dialog_bootrom.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +#include + +bool dialog_bootrom_load_second_stage_bootloader(void); diff --git a/src/bluetooth-fw/da1468x/host/dialog_spi.c b/src/bluetooth-fw/da1468x/host/dialog_spi.c new file mode 100644 index 00000000..73ce5c9f --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/dialog_spi.c @@ -0,0 +1,264 @@ +/* + * 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. + */ + +#include "dialog_spi.h" + +#include "board/board.h" +#include "drivers/dma.h" +#include "drivers/gpio.h" +#include "drivers/periph_config.h" +#include "drivers/spi.h" +#include "drivers/spi_dma.h" +#include "kernel/util/delay.h" +#include "kernel/util/stop.h" +#include "system/passert.h" +#include "util/units.h" + +#define STM32F2_COMPATIBLE +#define STM32F4_COMPATIBLE +#define STM32F7_COMPATIBLE +#include + +#include "FreeRTOS.h" + +#define DIALOG_INT_CONFIG_PTR (&BOARD_CONFIG_BT_COMMON.wakeup.int_gpio) +#define DIALOG_EXTI (BOARD_CONFIG_BT_COMMON.wakeup.int_exti) + +static DialogSpiDmaTransferCompletedCallback s_dma_transfer_complete_cb; +static bool s_dma_pending_transfer; + +// +// INT line EXTI Handler +// +// FIXME: Pretty naive at the moment. Just busy loops waiting for INT +// to fire and could get stuck in an infinite loop if it never fires +// + +// NB: this function is used only by the bootloader. +static volatile int s_int_fired; +void dialog_spi_wait_for_int(void) { + while (s_int_fired == 0) { + delay_us(1000); + } + __disable_irq(); + s_int_fired--; + __enable_irq(); +} + +// NB: this function is used only by the bootloader. +static void prv_exti_cb(bool *should_context_switch) { + s_int_fired++; + + if (s_int_fired > 1) { + dbgserial_putstr("BLE Chip multiple INTS ?"); + } +} + +static void prv_init_exti(ExtiHandlerCallback int_exti_cb) { + gpio_input_init(DIALOG_INT_CONFIG_PTR); + if (gpio_input_read(DIALOG_INT_CONFIG_PTR)) { + int_exti_cb(NULL); + } + + exti_configure_pin(DIALOG_EXTI, ExtiTrigger_Rising, int_exti_cb); + exti_enable(DIALOG_EXTI); +} + +void dialog_spi_set_pending_int(void) { + exti_set_pending(DIALOG_EXTI); +} + +void dialog_spi_indicate_data_ready_to_tx(void) { + // Note: Double purposing the CS as an INT and SPI CS confused the SPI + // driver. For now just treat it as a normal gpio when indicating data is + // ready to be sent + gpio_output_set(&BOARD_CONFIG_BT_SPI.cs, true); +} + +void dialog_spi_assert_chip_select(bool asserted) { + if (asserted) { + spi_ll_slave_scs_assert(DIALOG_SPI); + } else { + spi_ll_slave_scs_deassert(DIALOG_SPI); + } +} + +// +// Low Level SPI TX/RX config +// + +static void prv_enable_spi_clock(void) { + spi_ll_slave_acquire(DIALOG_SPI); + spi_ll_slave_drive_clock(DIALOG_SPI, false); +} + +static void prv_disable_spi_clock(void) { + // When the SPI peripheral is disabled, the CLK line floats. Let's + // reconfigure the CLK GPIO to be active low so the Dialog BT controller + // doesn't see a false clock edge + spi_ll_slave_drive_clock(DIALOG_SPI, true); + spi_ll_slave_release(DIALOG_SPI); +} + +static void prv_init_spi(void) { + spi_slave_port_init(DIALOG_SPI); +} + +// +// DMA +// + +static void prv_cleanup_after_dma_transfer(void) { + dialog_spi_assert_chip_select(false); + + // FIXME: PBL-32865: The "OVR" (overrun) status flag gets set at some point, not sure why because + // the data arrives OK. Need to reset it, otherwise the next transfer will get a 0x00 byte + // prepended to the beginning... + // From the F7 Spec: "When the SPI is used only to transmit data, it is possible to enable only + // the SPI Tx DMA channel. In this case, the OVR flag is set because the data received is not + // read." I assume this will only happen if more than 4 (FIFO size) bytes are read. NB: They will + // likely remain in the RX FIFO, ready to mess up your next transfer. + + spi_ll_slave_clear_errors(DIALOG_SPI); + + spi_ll_slave_spi_disable(DIALOG_SPI); + prv_disable_spi_clock(); + stop_mode_enable(InhibitorBluetooth); +} + +static bool prv_dma_handler(const SPISlavePort *slave, void *context) { + bool should_context_switch = false; + if (!s_dma_pending_transfer) { + return should_context_switch; + } + s_dma_pending_transfer = false; + + // If the SPI frequency is set low (for debugging) for example 800KHz, it is possible that the + // DMA transfer complete interrupts have fired, but the SPI peripheral is still busy clocking the + // data in/out. With 3.3MHz SPI clock frequency, this is not going to loop at all. + spi_slave_wait_until_idle_blocking(DIALOG_SPI); + + prv_cleanup_after_dma_transfer(); + + if (s_dma_transfer_complete_cb) { + s_dma_transfer_complete_cb(&should_context_switch); + } + return should_context_switch; +} + +void dialog_spi_send_and_receive_dma(const void *tx_buffer, void *rx_buffer, size_t size, + DialogSpiDmaTransferCompletedCallback done_isr_callback) { + PBL_ASSERTN(tx_buffer || rx_buffer); + + // FIXME: PBL-32864: Ugh, can't do this from ISR +// periph_config_acquire_lock(); + + s_dma_transfer_complete_cb = done_isr_callback; + + prv_enable_spi_clock(); + // Client code is supposed to ensure no transfer gets set up, + // while the previous one is still on-going: + PBL_ASSERTN(!spi_ll_slave_dma_in_progress(DIALOG_SPI)); + spi_ll_slave_spi_disable(DIALOG_SPI); + + stop_mode_disable(InhibitorBluetooth); + + // We're about to run the SPI clock, enable CS: + dialog_spi_assert_chip_select(true); + + s_dma_pending_transfer = true; + + spi_ll_slave_spi_enable(DIALOG_SPI); + spi_ll_slave_read_write_dma_start(DIALOG_SPI, tx_buffer, rx_buffer, size, prv_dma_handler, NULL); + + // Don't do any set up after this point, chances are the DMA transfer complete interrupt fires + // before any code that you've put here finishes... + + // FIXME: PBL-32864: Ugh, can't do this from ISR +// periph_config_release_lock(); +} + +void dialog_spi_start_cmd(void) { + prv_enable_spi_clock(); + dialog_spi_assert_chip_select(true); +} + +void dialog_spi_end_cmd(void) { + dialog_spi_assert_chip_select(false); + prv_disable_spi_clock(); +} + +uint8_t dialog_spi_send_and_receive_byte(uint8_t byte) { + return spi_ll_slave_read_write(DIALOG_SPI, byte); +} + +void dialog_spi_transmit_data_no_wait(uint8_t *send, size_t buf_len) { + dialog_spi_start_cmd(); + + for (uint32_t i = 0; i < buf_len; i++) { + dialog_spi_send_and_receive_byte(send[i]); + } + + dialog_spi_end_cmd(); +} + +// NB: this function is used only by the bootloader. +void dialog_spi_transmit_data(uint8_t *send, size_t buf_len) { + // Don't tx until dialog says its ready! + dialog_spi_wait_for_int(); + dialog_spi_transmit_data_no_wait(send, buf_len); +} + +void dialog_spi_receive_data_no_wait(uint8_t *receive_buf, size_t buf_len) { + dialog_spi_start_cmd(); + + for (size_t i = 0; i < buf_len; i++) { + receive_buf[i] = dialog_spi_send_and_receive_byte(0); + } + + dialog_spi_end_cmd(); +} + +// NB: this function is used only by the bootloader. +void dialog_spi_receive_data(uint8_t *receive_buf, size_t buf_len) { + // Dialog will always signal when it has data ready to send + dialog_spi_wait_for_int(); + dialog_spi_receive_data_no_wait(receive_buf, buf_len); +} + +void dialog_spi_init(ExtiHandlerCallback int_exti_cb) { + // reset the interrupt count + s_int_fired = 0; + s_dma_pending_transfer = false; + + periph_config_acquire_lock(); + prv_init_spi(); + periph_config_release_lock(); + + prv_init_exti(int_exti_cb ?: prv_exti_cb); +} + +void dialog_spi_deinit(void) { + exti_disable(DIALOG_EXTI); + + if (s_dma_pending_transfer) { + spi_ll_slave_read_write_dma_stop(DIALOG_SPI); + prv_cleanup_after_dma_transfer(); + s_dma_pending_transfer = false; + } + + spi_slave_port_deinit(DIALOG_SPI); +} diff --git a/src/bluetooth-fw/da1468x/host/dialog_spi.h b/src/bluetooth-fw/da1468x/host/dialog_spi.h new file mode 100644 index 00000000..66c52b0e --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/dialog_spi.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#pragma once + +#include "drivers/exti.h" + +#include +#include + +void dialog_spi_init(ExtiHandlerCallback int_exti_cb); + +void dialog_spi_transmit_data(uint8_t *send, size_t buf_len); +void dialog_spi_transmit_data_no_wait(uint8_t *send, size_t buf_len); + +void dialog_spi_receive_data(uint8_t *receive_buf, size_t buf_len); +void dialog_spi_receive_data_no_wait(uint8_t *receive_buf, size_t buf_len); + +void dialog_spi_wait_for_int(void); + +void dialog_spi_set_pending_int(void); + +void dialog_spi_assert_chip_select(bool asserted); + +void dialog_spi_indicate_data_ready_to_tx(void); + +void dialog_spi_start_cmd(void); + +uint8_t dialog_spi_send_and_receive_byte(uint8_t byte); + +typedef void (*DialogSpiDmaTransferCompletedCallback)(bool *should_context_switch); + +//! @note Safe to call from ISR! +void dialog_spi_send_and_receive_dma(const void *tx_buffer, void *rx_buffer, size_t size, + DialogSpiDmaTransferCompletedCallback done_isr_callback); + +void dialog_spi_cancel_dma(void); + +void dialog_spi_end_cmd(void); + +void dialog_spi_deinit(void); diff --git a/src/bluetooth-fw/da1468x/host/dialog_spi_bootloader.c b/src/bluetooth-fw/da1468x/host/dialog_spi_bootloader.c new file mode 100644 index 00000000..44dea6d8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/dialog_spi_bootloader.c @@ -0,0 +1,246 @@ +/* + * 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. + */ + +#include +#include + +#include "dialog_spi.h" + +#include "kernel/pbl_malloc.h" +#include "resource/resource.h" +#include "resource/resource_ids.auto.h" +#include "system/logging.h" +#include "util/attributes.h" +#include "util/crc32.h" +#include "util/math.h" +#include "util/units.h" + +// +// SPI bootloader protocol +// + +static bool prv_load_data(uint32_t address, const uint8_t *data, size_t data_len) { + // PBL_LOG(LOG_LEVEL_DEBUG, "Loading %d bytes start at 0x%x", (int)data_len, (int)address); + + uint8_t mem_write[] = {'L', 'D'}; + dialog_spi_transmit_data(mem_write, sizeof(mem_write)); + + struct __attribute__((packed)) { + uint32_t copy_address; + uint32_t length; + } send_data_cmd_payload = { + .copy_address = address, + .length = data_len + }; + + dialog_spi_transmit_data((uint8_t *)&send_data_cmd_payload, sizeof(send_data_cmd_payload)); + + dialog_spi_wait_for_int(); + dialog_spi_start_cmd(); + uint32_t expected_crc = CRC32_INIT; + for (size_t i = 0; i < send_data_cmd_payload.length; i++) { + // This could be made faster by using DMA if we needed + expected_crc = crc32(expected_crc, &data[i], 1); + dialog_spi_send_and_receive_byte(data[i]); + } + dialog_spi_end_cmd(); + + uint32_t received_crc = 0; + dialog_spi_receive_data((uint8_t *)&received_crc, sizeof(received_crc)); + + if (received_crc != expected_crc) { + PBL_LOG(LOG_LEVEL_ERROR, "Load Data Crc. Expected: 0x%x Received CRC: 0x%x", + (int)expected_crc, (int)received_crc); + } + return (received_crc == expected_crc); +} + +static bool prv_send_vt(const uint8_t *vt, uint32_t vt_size_bytes) { + uint8_t vt_write[] = {'V', 'T'}; + dialog_spi_transmit_data(vt_write, sizeof(vt_write)); + + uint8_t entries = vt_size_bytes / 4; + dialog_spi_transmit_data((uint8_t *)&entries, sizeof(entries)); + + dialog_spi_wait_for_int(); + dialog_spi_start_cmd(); + uint32_t expected_crc = CRC32_INIT; + for (size_t i = 0; i < vt_size_bytes; i++) { + // This could be made faster by using DMA if we needed + expected_crc = crc32(expected_crc, &vt[i], 1); + dialog_spi_send_and_receive_byte(vt[i]); + } + dialog_spi_end_cmd(); + + uint32_t received_crc = 0; + dialog_spi_receive_data((uint8_t *)&received_crc, sizeof(received_crc)); + + PBL_LOG(LOG_LEVEL_DEBUG, "VT Crc. Expected: 0x%x Received CRC: 0x%x", + (int)expected_crc, (int)received_crc); + return (received_crc == expected_crc); +} + +static bool prv_send_hi_cmd(void) { + uint8_t send[] = {'H', 'I'}; + dialog_spi_transmit_data(send, sizeof(send)); + + uint8_t response[4] = {}; + dialog_spi_receive_data(response, sizeof(response)); + + uint8_t expected_response[] = {'H', 'E', 'R', 'E'}; + + if (memcmp(expected_response, response, sizeof(response)) == 0) { + return true; + } + + PBL_LOG(LOG_LEVEL_WARNING, "Got unexpected response, { 0x%x 0x%x 0x%x 0x%x }", + (int)response[0], (int)response[1], (int)response[2], (int)response[3]); + + return false; +} + +// This struct is packed at the beginning of the binary blob +// See main/ldscripts/mem.ld.h for more info +typedef struct PACKED { + uint32_t load_start_address; + uint32_t vector_table_size; +} ObjectBinInfo; + +static bool prv_load_and_transfer_vector_table(uint32_t vector_table_size) { + bool success = false; + + uint8_t *vt_buf = kernel_malloc_check(vector_table_size); + if (!resource_load_byte_range_system( + SYSTEM_APP, RESOURCE_ID_BT_FW_IMAGE, sizeof(ObjectBinInfo), + vt_buf, vector_table_size)) { + goto cleanup; + } + + if (!prv_send_vt(vt_buf, vector_table_size)) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to send VT!"); + goto cleanup; + } + + success = true; +cleanup: + kernel_free(vt_buf); + return success; +} + +static bool prv_transfer_main_image_code_and_data( + uint32_t image_size, ObjectBinInfo *info) { + bool success = false; + + int vt_and_info_blob_size = info->vector_table_size + sizeof(ObjectBinInfo); + image_size -= vt_and_info_blob_size; + + const uint32_t data_buf_size = 4096; + uint8_t *data_buf = kernel_malloc_check(data_buf_size); + + uint32_t curr_send_offset = 0; + while (image_size != 0) { + const size_t send_len = MIN(image_size, data_buf_size); + + if (!resource_load_byte_range_system( + SYSTEM_APP, RESOURCE_ID_BT_FW_IMAGE, + curr_send_offset + vt_and_info_blob_size, + data_buf, send_len)) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to read fw image data"); + goto cleanup; + } + + if (!prv_load_data(info->load_start_address + curr_send_offset, data_buf, + send_len)) { + PBL_LOG(LOG_LEVEL_WARNING, "SPI transfer to BLE chip not successful"); + goto cleanup; + } + + curr_send_offset += send_len; + image_size -= send_len; + } + + success = true; + +cleanup: + kernel_free(data_buf); + return success; +} + +static bool prv_transfer_main_image(void) { + // TODO: Would be nice to just leverage memory mapping to avoid the double + // copy in the future + + // Get the basic info about the image we are going to transfer + uint32_t image_size = resource_size(SYSTEM_APP, RESOURCE_ID_BT_FW_IMAGE); + ObjectBinInfo info; + if (!resource_load_byte_range_system(SYSTEM_APP, RESOURCE_ID_BT_FW_IMAGE, + 0x0, (uint8_t *)&info, sizeof(info))) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to receive object bin info"); + return false; + } + + PBL_LOG(LOG_LEVEL_INFO, "Loading BT FW image to 0x%x. VT Size: %"PRIu32, + (int)info.load_start_address, info.vector_table_size); + + if (!prv_load_and_transfer_vector_table(info.vector_table_size)) { + return false; + } + + if (!prv_transfer_main_image_code_and_data(image_size, &info)) { + return false; + } + + return true; +} + +static void prv_issue_bt_reboot(void) { + uint8_t reboot[] = {'R', 'T'}; + dialog_spi_transmit_data(reboot, sizeof(reboot)); +} + +static bool prv_load_bt_image_over_spi(void) { + PBL_LOG(LOG_LEVEL_DEBUG, "Bringing up the BT Stack"); + + if (!prv_send_hi_cmd()) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to load firmware onto BLE Chip"); + return false; // TODO: This should probably eventually insta-PRF + } + + if (!prv_transfer_main_image()) { + return false; + } + + prv_issue_bt_reboot(); + + PBL_LOG(LOG_LEVEL_INFO, "BLE FW loaded!"); + return true; +} + +// +// Exported routines +// + +bool dialog_spi_bootloader_load_image(void) { + dialog_spi_init(NULL); + const bool result = prv_load_bt_image_over_spi(); + dialog_spi_deinit(); + + return result; +} + +void dialog_test_cmds(void) { + dialog_spi_bootloader_load_image(); +} diff --git a/src/bluetooth-fw/da1468x/host/dialog_spi_bootloader.h b/src/bluetooth-fw/da1468x/host/dialog_spi_bootloader.h new file mode 100644 index 00000000..1f6f0d85 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/dialog_spi_bootloader.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#pragma once + +bool dialog_spi_bootloader_load_image(void); diff --git a/src/bluetooth-fw/da1468x/host/features.c b/src/bluetooth-fw/da1468x/host/features.c new file mode 100644 index 00000000..7c9b97c4 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/features.c @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#include + +bool bt_driver_supports_bt_classic(void) { + return false; +} diff --git a/src/bluetooth-fw/da1468x/host/gap_le_connect.c b/src/bluetooth-fw/da1468x/host/gap_le_connect.c new file mode 100644 index 00000000..032b3819 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/gap_le_connect.c @@ -0,0 +1,22 @@ +/* + * 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. + */ + +#include "bluetooth/gap_le_connect.h" +#include "hc_protocol/hc_endpoint_gap_le_connect.h" + +int bt_driver_gap_le_disconnect(const BTDeviceInternal *peer_address) { + return hc_endpoint_gap_le_disconnect(peer_address); +} diff --git a/src/bluetooth-fw/da1468x/host/gap_le_device_name.c b/src/bluetooth-fw/da1468x/host/gap_le_device_name.c new file mode 100644 index 00000000..2116d9c3 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/gap_le_device_name.c @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include + +#include "bluetooth/gap_le_device_name.h" +#include "hc_protocol/hc_endpoint_gap_service.h" + + +void bt_driver_gap_le_device_name_request(const BTDeviceInternal *address) { + hc_endpoint_gap_service_device_name_request(address); +} + +void bt_driver_gap_le_device_name_request_all(void) { + hc_endpoint_gap_service_device_name_request_all(); +} diff --git a/src/bluetooth-fw/da1468x/host/gap_le_scan.c b/src/bluetooth-fw/da1468x/host/gap_le_scan.c new file mode 100644 index 00000000..a1ad9fcb --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/gap_le_scan.c @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#include "bluetooth/gap_le_scan.h" + +bool bt_driver_start_le_scan(bool active_scan, bool use_white_list_filter, bool filter_dups, + uint16_t scan_interval_ms, uint16_t scan_window_ms) { + return true; +} + + +bool bt_driver_stop_le_scan(void) { + return true; +} diff --git a/src/bluetooth-fw/da1468x/host/gatt.c b/src/bluetooth-fw/da1468x/host/gatt.c new file mode 100644 index 00000000..52d9bae7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/gatt.c @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gatt.h" +#include "hc_protocol/hc_protocol.h" + +#include +#include + +void bt_driver_gatt_respond_read_subscription(uint32_t transaction_id, uint16_t response_code) { +} + +void bt_driver_gatt_send_changed_indication(uint32_t connection_id, const ATTHandleRange *data) { +} diff --git a/src/bluetooth-fw/da1468x/host/gatt_client_discovery.c b/src/bluetooth-fw/da1468x/host/gatt_client_discovery.c new file mode 100644 index 00000000..5c25c505 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/gatt_client_discovery.c @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_discovery.h" + +#include +#include +#include + +#include + +// ------------------------------------------------------------------------------------------------- +// Gatt Client Discovery API calls + +//! Assumes bt_lock is held +BTErrno bt_driver_gatt_start_discovery_range( + const GAPLEConnection *connection, const ATTHandleRange *data) { + return hc_endpoint_discovery_start(data, &connection->device) ? BTErrnoOK : BTErrnoOther; +} + +//! Assumes bt_lock is held +BTErrno bt_driver_gatt_stop_discovery(GAPLEConnection *connection) { + return hc_endpoint_discovery_stop(&connection->device) ? BTErrnoOK : BTErrnoOther; +} + +void bt_driver_gatt_handle_discovery_abandoned(void) { +} diff --git a/src/bluetooth-fw/da1468x/host/gatt_client_operations.c b/src/bluetooth-fw/da1468x/host/gatt_client_operations.c new file mode 100644 index 00000000..b2bbcfd5 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/gatt_client_operations.c @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gatt.h" + +#include +#include +#include + +#include +#include +#include + +BTErrno bt_driver_gatt_write_without_response(GAPLEConnection *connection, + const uint8_t *value, + size_t value_length, + uint16_t att_handle) { + BTDeviceInternal *device = &connection->device; + const bool resp_required = false; + // FIXME PBL-34465: Need to translate between Dialog errors and BTErrno + int16_t ret_val = hc_endpoint_gatt_write(device, att_handle, value, value_length, + resp_required, NULL); + return (BTErrno)ret_val; +} + +BTErrno bt_driver_gatt_write(GAPLEConnection *connection, + const uint8_t *value, + size_t value_length, + uint16_t att_handle, + void *context) { + BTDeviceInternal *device = &connection->device; + const bool resp_required = true; + // FIXME PBL-34465: Need to translate between Dialog errors and BTErrno + int16_t ret_val = hc_endpoint_gatt_write(device, att_handle, value, value_length, + resp_required, context); + return (BTErrno)ret_val; +} + +BTErrno bt_driver_gatt_read(GAPLEConnection *connection, + uint16_t att_handle, + void *context) { + BTDeviceInternal *device = &connection->device; + // FIXME PBL-34465: Need to translate between Dialog errors and BTErrno + int16_t ret_val = hc_endpoint_gatt_read(device, att_handle, context); + return (BTErrno)ret_val; +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_advert.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_advert.c new file mode 100644 index 00000000..372bfaf1 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_advert.c @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_advert.h" +#include "hc_protocol/hc_protocol.h" + +#include "advert_state.h" + +#include + +#include "system/logging.h" + + +void hc_endpoint_advert_resp_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Advert_Enable: + PBL_LOG(LOG_LEVEL_DEBUG, "Advert Enable: %"PRIu8, msg->payload[0]); + break; + case HcMessageID_Advert_SetAdvData: + PBL_LOG(LOG_LEVEL_DEBUG, "Advert SetAdvData: %"PRIu8, msg->payload[0]); + break; + case HcMessageID_Advert_Disable: + // NYI + break; + default: + PBL_LOG(LOG_LEVEL_DEBUG, "HcAdvert Response: unhandled message id: %d", msg->command_id); + } +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_analytics.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_analytics.c new file mode 100644 index 00000000..6ac2bf64 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_analytics.c @@ -0,0 +1,142 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_analytics.h" + +#include "comm/bluetooth_analytics.h" +#include "kernel/pbl_malloc.h" +#include "services/common/analytics/analytics.h" +#include "services/common/system_task.h" +#include "system/logging.h" + +#include + +#include + +bool hc_endpoint_analytics_collect_ble_parameters(LEChannelMap *le_chan_map_res) { + HcProtocolMessage *msg = hc_protocol_enqueue_with_payload_and_expect( + HcEndpointID_Analytics, HcMessageID_Analytics_CollectBLEParameters, NULL, 0); + + if (!msg) { + // Should be a basic log for timed out expect in hc_protocol layer + return false; + } + + HcAnalyticsCollectBleParameters *data = (HcAnalyticsCollectBleParameters *)&msg->payload[0]; + const bool rv = data->success; + if (rv) { + *le_chan_map_res = data->le_chan_map_res; + } + + kernel_free(msg); + return rv; +} + +bool hc_endpoint_analytics_get_connection_quality(const BTDeviceInternal *device, + int8_t *rssi_out) { + HcProtocolMessage *msg = hc_protocol_enqueue_with_payload_and_expect( + HcEndpointID_Analytics, HcMessageID_Analytics_GetConnectionQuality, + (uint8_t *)device, sizeof(*device)); + + if (!msg) { + // Should be a basic log for timed out expect in hc_protocol layer + return false; + } + + HcAnalyticsGetConnectionQuality *data = (HcAnalyticsGetConnectionQuality *)&msg->payload[0]; + const bool rv = data->success; + if (rv) { + *rssi_out = data->rssi; + } + + kernel_free(msg); + return rv; +} + +static AnalyticsMetric prv_get_fw_metric(DialogAnalyticsMetric metric) { + switch (metric) { + // insert future DialogAnalyticsMetric's here + case DialogAnalyticMetric_Test: + case DialogAnalyticMetric_Count: + return ANALYTICS_METRIC_INVALID; + // no default so that any DialogAnalyticsMetric's that get added cause an error if they aren't + // included in the switch statement + } + return metric; +} + +void hc_endpoint_analytics_collect_heartbeat_data(void) { + HcProtocolMessage *msg = hc_protocol_enqueue_with_payload_and_expect( + HcEndpointID_Analytics, HcMessageID_Analytics_GetHeartbeatData, NULL, 0); + + if (!msg) { + // Should be a basic log for timed out expect in hc_protocol layer + return; + } + + HcAnalyticsHeartbeatData *data = (HcAnalyticsHeartbeatData *)&msg->payload[0]; + for (uint32_t i = 0; i < data->count; i++) { + SerializedAnalytic analytic = data->analytics[i]; + const AnalyticsMetric metric = prv_get_fw_metric(analytic.metric); + if (metric != ANALYTICS_METRIC_INVALID) { + analytics_set(metric, analytic.value, AnalyticsClient_System); + } + } + kernel_free(msg); +} + +bool hc_endpoint_analytics_get_conn_event_stats(SlaveConnEventStats *stats) { + HcProtocolMessage *msg = hc_protocol_enqueue_with_payload_and_expect( + HcEndpointID_Analytics, HcMessageID_Analytics_GetConnEventStats, + NULL, 0); + + if (!msg) { + // Should be a basic log for timed out expect in hc_protocol layer + return false; + } + + memcpy(stats, &msg->payload[0], sizeof(*stats)); + kernel_free(msg); + return true; +} + +static void prv_analytics_bt_chip_boot_cb(void *context) { + HcAnalyticsRebootInfo *info = context; + analytics_event_bt_chip_boot(info->build_id, info->last_crash_lr, info->reboot_reason_code); + kernel_free(info); +} + +void hc_endpoint_analytics_host_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Analytics_LogRebootInfo: { + HcAnalyticsRebootInfo *info = (HcAnalyticsRebootInfo *) &msg->payload[0]; + HcAnalyticsRebootInfo *copy = kernel_malloc(sizeof(*info)); + if (copy) { + memcpy(copy, info, sizeof(*info)); + // We put to system task because analytics need to be init'ed first + system_task_add_callback(prv_analytics_bt_chip_boot_cb, copy); + } + break; + } + case HcMessageID_Analytics_LogBleMicErrorEvent: { + HcAnalyticsLogBleMicErrorEvent *info = (HcAnalyticsLogBleMicErrorEvent *)&msg->payload[0]; + bluetooth_analytics_ble_mic_error(info->num_subsequent_mic_errors); + break; + } + default: + break; + } +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_chip_id.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_chip_id.c new file mode 100644 index 00000000..22efd5f6 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_chip_id.c @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_chip_id.h" + +#include "dialog_chip_id.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include + +void hc_endpoint_chip_id_handler(const HcProtocolMessage *msg) { + PBL_LOG(LOG_LEVEL_ERROR, "Unknown cmd ID: 0x%"PRIx8, msg->command_id); +} + +bool hc_endpoint_chip_id_query_chip_info(DialogChipID *chip_id_out) { + HcProtocolMessage request = { + .message_length = sizeof(request), + .endpoint_id = HcEndpointID_Id, + .command_id = HcMessageID_Id_ChipInfo, + }; + HcProtocolMessage *response = hc_protocol_enqueue_and_expect(&request); + if (!response) { + return false; + } + if (response->message_length < sizeof(HcProtocolMessage) + sizeof(DialogChipID)) { + PBL_LOG(LOG_LEVEL_ERROR, "Received error response."); + return false; + } + memcpy(chip_id_out, response->payload, sizeof(*chip_id_out)); + kernel_free(response); + return true; +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_ctl.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_ctl.c new file mode 100644 index 00000000..4c4b5081 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_ctl.c @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_ctl.h" + +#include "kernel/util/sleep.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include + +static bool prv_send_ctl_cmd(HcMessageID_Ctl cmd_id, + const uint8_t *payload, size_t payload_len) { + HcProtocolMessage *response = + hc_protocol_enqueue_with_payload_and_expect(HcEndpointID_Ctl, cmd_id, payload, payload_len); + if (!response) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to receive response to ctl command %"PRIu8, cmd_id); + return false; + } + + PBL_LOG(LOG_LEVEL_INFO, "Received ctl response for command %"PRIu8, cmd_id); + kernel_free(response); + return true; +} + +bool hc_endpoint_ctl_init_sync(const BTDriverConfig *config) { + return prv_send_ctl_cmd(HcMessageID_Ctl_Init, (const uint8_t *)config, sizeof(*config)); +} + +bool hc_endpoint_ctl_shutdown_sync(void) { + const bool rv = prv_send_ctl_cmd(HcMessageID_Ctl_Shutdown, NULL, 0); + PBL_LOG(LOG_LEVEL_INFO, "Got confirmation, BT Chip should be going into a deep sleep..."); + return rv; +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_discovery.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_discovery.c new file mode 100644 index 00000000..0d060c1b --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_discovery.c @@ -0,0 +1,108 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_discovery.h" + +#include "comm/ble/gap_le_connection.h" +#include "comm/bt_lock.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include +#include +#include + +#include +#include + +bool hc_endpoint_discovery_start(const ATTHandleRange *range, const BTDeviceInternal *address) { + HcProtocolDiscoveryStartPayload payload = { + .address = *address, + .range = *range, + }; + + return hc_protocol_enqueue_with_payload( + HcEndpointID_Discovery, HcMessageID_Discovery_Start, (uint8_t *)&payload, sizeof(payload)); +} + +bool hc_endpoint_discovery_stop(const BTDeviceInternal *address) { + return hc_protocol_enqueue_with_payload( + HcEndpointID_Discovery, HcMessageID_Discovery_Stop, (uint8_t *)address, sizeof(*address)); +} + +static void prv_handle_new_service_found(const HcProtocolDiscoveryServiceFoundPayload *payload) { + GAPLEConnection *connection = gap_le_connection_by_device(&payload->address); + if (connection == NULL) { + PBL_LOG(LOG_LEVEL_WARNING, "Service found but no device connected?"); + return; + } + + uint32_t gatt_service_size = payload->service.size_bytes; + GATTService *service_copy = kernel_malloc(gatt_service_size); + BTErrno e = BTErrnoNotEnoughResources; + if (service_copy) { + e = BTErrnoOK; + memcpy(service_copy, &payload->service, gatt_service_size); + } + + bt_driver_cb_gatt_client_discovery_handle_indication(connection, service_copy, e); +} + +static void prv_handle_discovery_complete(const HcProtocolDiscoveryCompletePayload *payload) { + PBL_LOG(LOG_LEVEL_DEBUG, "Discovery Complete, status = 0x%x", payload->status); + GAPLEConnection *connection = gap_le_connection_by_device(&payload->address); + if (connection == NULL) { + PBL_LOG(LOG_LEVEL_WARNING, "Discovery complete but no device connected?"); + return; + } + + BTErrno e = (payload->status == HciStatusCode_Success) ? BTErrnoOK : + (BTErrnoInternalErrorBegin + payload->status); + + bt_driver_cb_gatt_client_discovery_complete(connection, e); +} + +static void prv_handle_discover_service_changed_handle( + HcProtocolDiscoveryServiceChangedHandlePayload *payload) { + GAPLEConnection *connection = gap_le_connection_by_device(&payload->address); + if (connection == NULL) { + PBL_LOG(LOG_LEVEL_WARNING, "Discovery service_changed_handle but no device connected?"); + return; + } + + bt_driver_cb_gatt_client_discovery_handle_service_changed(connection, payload->handle); +} + +void hc_endpoint_discovery_handler(const HcProtocolMessage *msg) { + bt_lock(); + { + switch (msg->command_id) { + case HcMessageID_Discovery_Service_Found: + prv_handle_new_service_found((HcProtocolDiscoveryServiceFoundPayload *)msg->payload); + break; + case HcMessageID_Discovery_Complete: + prv_handle_discovery_complete((HcProtocolDiscoveryCompletePayload *)msg->payload); + break; + case HcMessageID_Discovery_Service_Changed_Handle: + prv_handle_discover_service_changed_handle( + (HcProtocolDiscoveryServiceChangedHandlePayload *)msg->payload); + break; + default: + PBL_LOG(LOG_LEVEL_WARNING, "Unexpected command: 0x%x", (int)msg->command_id); + } + } + bt_unlock(); +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gap_le_connect.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gap_le_connect.c new file mode 100644 index 00000000..9f2a527a --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gap_le_connect.c @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gap_le_connect.h" + +#include "kernel/pbl_malloc.h" +#include "comm/bluetooth_analytics.h" + +#include +#include + +// Dumb layer that passes payload directly to bt_driver. The payload are types from +// "gap_le_connect.h" +void hc_endpoint_gap_le_connect_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_GapLEConnect_ConnectionComplete: { + const HcGapLeConnectionData *e = (HcGapLeConnectionData *)msg->payload; + bt_driver_handle_le_connection_complete_event(&e->connection_complete_event); + const GattDeviceConnectionEvent gatt_event = { + .dev_address = e->connection_complete_event.peer_address.address, + .connection_id = 0, /* FIXME: Bluetopia-only construct */ + .mtu = e->mtu + }; + bt_driver_cb_gatt_handle_connect(&gatt_event); + bluetooth_analytics_handle_connection_disconnection_event( + AnalyticsEvent_BtLeConnectionComplete, e->connection_complete_event.status, NULL); + break; + } + case HcMessageID_GapLEConnect_DisconnectionComplete: { + const BleDisconnectionCompleteEvent *e = (BleDisconnectionCompleteEvent *)msg->payload; + GattDeviceDisconnectionEvent gatt_event = { + .dev_address = e->peer_address.address, + }; + bt_driver_cb_gatt_handle_disconnect(&gatt_event); + bt_driver_handle_le_disconnection_complete_event(e); + break; + } + case HcMessageID_GapLEConnect_EncryptionChange: { + const BleEncryptionChange *e = (BleEncryptionChange *)msg->payload; + bt_driver_handle_le_encryption_change_event(e); + break; + } + case HcMessageID_GapLEConnect_UpdateAddressAndIRK: { + const BleAddressAndIRKChange *e = (BleAddressAndIRKChange *)msg->payload; + bt_driver_handle_le_connection_handle_update_address_and_irk(e); + break; + } + case HcMessageID_GapLEConnect_PeerVersionInfo: { + const BleRemoteVersionInfoReceivedEvent *e = + (BleRemoteVersionInfoReceivedEvent *)msg->payload; + bt_driver_handle_peer_version_info_event(e); + break; + } + } +} + +int hc_endpoint_gap_le_disconnect(const BTDeviceInternal *address) { + HcProtocolMessage *resp = hc_protocol_enqueue_with_payload_and_expect( + HcEndpointID_GapLEConnect, HcMessageID_GapLEConnect_Disconnect, + (const uint8_t *)address, sizeof(BTDeviceInternal)); + uint8_t rv = resp->payload[0]; + kernel_free(resp); + return rv; +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gap_service.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gap_service.c new file mode 100644 index 00000000..59d5eb06 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gap_service.c @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gap_service.h" + +#include "comm/ble/gap_le_connection.h" +#include "kernel/pbl_malloc.h" +#include "services/common/system_task.h" +#include "system/logging.h" +#include "system/passert.h" +#include "util/math.h" + +#include +#include +#include +#include + +#include +#include + +static void prv_mtu_changed(const HcProtocol_GapServiceMtuChanged *evt) { + GattDeviceMtuUpdateEvent mtu_resp = { + .dev_address = evt->addr.address, + .mtu = evt->mtu + }; + bt_driver_cb_gatt_handle_mtu_update(&mtu_resp); +} + +static void prv_handle_dev_name_response(const HcProtocol_GapDeviceNameResponseHeader *resp) { + GAPLEConnection *connection = gap_le_connection_by_device(&resp->addr); + if (!connection) { + PBL_LOG(LOG_LEVEL_INFO, "HcGap: no connection for "BT_DEVICE_ADDRESS_FMT, + BT_DEVICE_ADDRESS_XPLODE(resp->addr.address)); + return; + } + + // The value is a non-zero-terminated UTF-8 string + int device_name_buffer_length = MIN(resp->name_length + 1, BT_DEVICE_NAME_BUFFER_SIZE); + char *device_name = (char *)kernel_zalloc_check(device_name_buffer_length); + if (!device_name) { + return; + } + memcpy(device_name, resp->name, device_name_buffer_length - 1); + + if (connection->device_name) { + kernel_free(connection->device_name); + } + connection->device_name = device_name; + + BTDeviceAddress *addr = kernel_zalloc_check(sizeof(BTDeviceAddress)); + memcpy(addr, &resp->addr, sizeof(BTDeviceAddress)); + system_task_add_callback(bt_driver_store_device_name_kernelbg_cb, addr); +} + +void hc_endpoint_gap_service_resp_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageId_GapService_MtuChanged: + prv_mtu_changed((const HcProtocol_GapServiceMtuChanged *)&msg->payload[0]); + break; + case HcMessageID_GapService_DeviceNameRequest: + prv_handle_dev_name_response((const HcProtocol_GapDeviceNameResponseHeader *)msg->payload); + break; + default: + PBL_LOG(LOG_LEVEL_INFO, "HcGap: unhandled message id: %d\n", msg->command_id); + break; + } +} + +void hc_endpoint_gap_service_set_dev_name(const char *name) { + hc_protocol_enqueue_with_payload(HcEndpointID_GapService, HcMessageID_GapService_SetName, + (const uint8_t *)name, strlen(name) + 1); +} + +void hc_endpoint_gap_service_device_name_request(const BTDeviceInternal *device) { + hc_protocol_enqueue_with_payload(HcEndpointID_GapService, + HcMessageID_GapService_DeviceNameRequest, + (const uint8_t *)device, + sizeof(BTDeviceInternal)); +} + +void hc_endpoint_gap_service_device_name_request_all(void) { + hc_protocol_enqueue_with_payload(HcEndpointID_GapService, + HcMessageID_GapService_DeviceNameRequest_All, NULL, 0); +} + +void hc_endpoint_gap_service_set_local_address(bool allow_cycling, + const BTDeviceAddress *pinned_address) { + HcProtocol_GapServiceSetLocalAddress payload = {}; + payload.allow_cycling = allow_cycling; + if (!allow_cycling) { + PBL_ASSERTN(!bt_device_address_is_invalid(pinned_address)); + payload.pinned_addr = *pinned_address; + } + hc_protocol_enqueue_with_payload(HcEndpointID_GapService, + HcMessageID_GapService_SetLocalAddress, + (const uint8_t *)&payload, sizeof(payload)); +} + +bool hc_endpoint_gap_service_generate_private_resolvable_address(BTDeviceAddress *address_out) { + bool success = false; + const HcMessageID_GapService msg_id = HcMessageID_GapService_GeneratePrivateResolvable_address; + HcProtocolMessage *response = + hc_protocol_enqueue_with_payload_and_expect(HcEndpointID_GapService, msg_id, NULL, 0); + if (response) { + HcProtocol_GapServiceGeneratePrivateResolvableAddressResponse *payload = + (HcProtocol_GapServiceGeneratePrivateResolvableAddressResponse *) response->payload; + *address_out = payload->address; + success = true; + } + kernel_free(response); + return success; +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gatt.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gatt.c new file mode 100644 index 00000000..8637430c --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_gatt.c @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_gatt.h" +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include "kernel/pbl_malloc.h" +#include "system/logging.h" +#include "system/hexdump.h" + +#include +#include +#include +#include + +void hc_endpoint_gatt_handler_host(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Gatt_ReadCompleted: { + HcGattReadRespData *r_data = (HcGattReadRespData *)&msg->payload[0]; + PBL_LOG(LOG_LEVEL_DEBUG, "Gatt: Got a read complete event: handle=%d, len=%d", + r_data->att_handle, r_data->value_length); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, r_data->value, r_data->value_length); + GattClientOpReadReponse r_resp = { + .hdr = { + .type = GattClientOpResponseRead, + .error_code = r_data->status, + .context = (void *)r_data->context_ref, + }, + .value_length = r_data->value_length, + .value = r_data->value, + }; + bt_driver_cb_gatt_client_operations_handle_response(&r_resp.hdr); + break; + } + case HcMessageID_Gatt_WriteCompleted: { + HcGattWriteRespData *w_data = (HcGattWriteRespData *) &msg->payload[0]; + PBL_LOG(LOG_LEVEL_DEBUG, "Gatt: Got a write complete event: handle=%d", w_data->att_handle); + GattClientOpWriteReponse w_resp = { + .hdr = { + .type = GattClientOpResponseWrite, + .error_code = w_data->status, + .context = (void *) w_data->context_ref, + }, + }; + bt_driver_cb_gatt_client_operations_handle_response(&w_resp.hdr); + break; + } + case HcMessageID_Gatt_Notification: + case HcMessageID_Gatt_Indication: { + HcGattNotifIndicData *ni_data = (HcGattNotifIndicData *)&msg->payload[0]; + GattServerNotifIndicEvent evt = { + .dev_address = ni_data->hdr.addr.address, + .attr_handle = ni_data->att_handle, + .attr_val_len = ni_data->value_length, + .attr_val = ni_data->value, + }; + if (msg->command_id == HcMessageID_Gatt_Notification) { + bt_driver_cb_gatt_handle_notification(&evt); + } else { + bt_driver_cb_gatt_handle_indication(&evt); + } + break; + } + default: + PBL_LOG(LOG_LEVEL_ERROR, "Unhandled command id: %d", msg->command_id); + } +} + +static BTErrno prv_enqueue_and_get_rv( + HcCommandID cmd_id, const uint8_t *data, size_t data_len, bool resp_required) { + BTErrno rv; + if (resp_required) { + HcProtocolMessage *resp = hc_protocol_enqueue_with_payload_and_expect(HcEndpointID_Gatt, cmd_id, + data, data_len); + + rv = (resp != NULL) ? resp->payload[0] : BTErrnoPairingTimeOut; + kernel_free(resp); + } else { + rv = hc_protocol_enqueue_with_payload(HcEndpointID_Gatt, cmd_id, data, data_len) ? + BTErrnoOK : BTErrnoNotEnoughResources; + } + return rv; +} + +BTErrno hc_endpoint_gatt_read(const BTDeviceInternal *addr, uint16_t att_handle, void *context) { + HcGattReadData data = { + .hdr.addr = *addr, + .context_ref = (uintptr_t)context, + .att_handle = att_handle, + }; + return prv_enqueue_and_get_rv(HcMessageID_Gatt_Read, (uint8_t *)&data, sizeof(data), true); +} + +BTErrno hc_endpoint_gatt_write(const BTDeviceInternal *addr, uint16_t att_handle, + const uint8_t *value, uint16_t value_length, + bool resp_required, void *context) { + const uint32_t alloc_size = sizeof(HcGattWriteData) + value_length; + HcGattWriteData *data = kernel_malloc_check(alloc_size); + *data = (HcGattWriteData) { + .hdr.addr = *addr, + .context_ref = (uintptr_t)context, + .att_handle = att_handle, + .value_length = value_length, + }; + memcpy(&data->value[0], value, value_length); + + const HcCommandID cmd_id = (resp_required) ? HcMessageID_Gatt_Write + : HcMessageID_Gatt_WriteNoResponse; + BTErrno rv = prv_enqueue_and_get_rv(cmd_id, (uint8_t *)data, alloc_size, resp_required); + kernel_free(data); + return rv; +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_hci.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_hci.c new file mode 100644 index 00000000..e45258eb --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_hci.c @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_hci.h" +#include "hc_protocol/hc_protocol.h" +#include "system/hexdump.h" +#include "system/logging.h" +#include "system/passert.h" + +#include +#include +#include +#include + +// This routine should only ever be called by the passthrough event handler. It +// returns true if the msg was consumed +extern bool bt_test_hci_event_handled(const uint8_t *hci_msg, uint16_t payload_len); + +void hc_endpoint_hci_handler(const HcProtocolMessage *msg) { + if (msg->command_id != HcMessageID_Hci_Evt) { + PBL_LOG(LOG_LEVEL_WARNING, "Unsupported HCI message: 0x%x", (int)msg->command_id); + return; + } + + int payload_len = msg->message_length - sizeof(*msg); + PBL_ASSERTN(payload_len > 0); + + // PBL_HEXDUMP(LOG_LEVEL_DEBUG, &msg->payload[0], payload_len); + + const char *payload = (const char *)&msg->payload[0]; + + if (bt_test_hci_event_handled((const uint8_t *)payload, payload_len)) { + // We are done, don't dump to response dbgserial + return; + } + + for (int i = 0; i < payload_len; i++) { + // TODO: Add support for accessory port + dbgserial_putchar_lazy(payload[i]); + } +} + +void hc_endpoint_enqueue_hci_cmd(const uint8_t *hci_buf, uint8_t payload_len) { + // skip the HCI_CMD byte since that's already encoded in the message + hci_buf++; + payload_len -= 1; + + hc_protocol_enqueue_with_payload(HcEndpointID_Hci, HcMessageID_Hci_Cmd, + hci_buf, payload_len); +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_hrm.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_hrm.c new file mode 100644 index 00000000..8fa09856 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_hrm.c @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_hrm.h" +#include "hc_protocol/hc_protocol.h" + +#include "kernel/pbl_malloc.h" + +#include + +#include + +void hc_endpoint_hrm_handler(const HcProtocolMessage *msg) { +#if !RECOVERY_FW && CAPABILITY_HAS_BUILTIN_HRM + switch (msg->command_id) { + case HcMessageID_HRM_UpdateSubscription: { + const HcHrmSubscription *subscription = (const HcHrmSubscription *)msg->payload; + bt_driver_cb_hrm_service_update_subscription(&subscription->device, + subscription->is_subscribed); + break; + } + + default: + break; + } +#endif +} + +void hc_endpoint_hrm_send_measurement(const BleHrmServiceMeasurement *measurement, + const BTDeviceInternal *permitted_devices, + size_t num_permitted_devices) { +#if !RECOVERY_FW && CAPABILITY_HAS_BUILTIN_HRM + const size_t msg_size = (sizeof(HcProtocolMessage) + sizeof(HcHrmMeasurement) + + (num_permitted_devices * sizeof(BTDeviceInternal))); + HcProtocolMessage *const msg = kernel_zalloc_check(msg_size); + msg->message_length = msg_size; + msg->endpoint_id = HcEndpointID_HRM; + msg->command_id = HcMessageID_HRM_Measurement; + HcHrmMeasurement *const payload = (HcHrmMeasurement *) msg->payload; + payload->bpm = measurement->bpm; + payload->is_on_wrist = measurement->is_on_wrist; + payload->num_devices = num_permitted_devices; + memcpy(payload->devices, permitted_devices, sizeof(BTDeviceInternal) * num_permitted_devices); + hc_protocol_enqueue(msg); + kernel_free(msg); +#endif +} + + +void hc_endpoint_hrm_enable(bool enable) { +#if !RECOVERY_FW + const HcHrmEnableCmd enable_cmd = { + .enable = enable, + }; + hc_protocol_enqueue_with_payload(HcEndpointID_HRM, HcMessageID_HRM_Enable, + (const uint8_t *)&enable_cmd, sizeof(enable_cmd)); +#endif +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_logging.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_logging.c new file mode 100644 index 00000000..5fab39d7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_logging.c @@ -0,0 +1,141 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_logging.h" + +#include "kernel/pbl_malloc.h" +#include "system/logging.h" +#include "logging/binary_logging.h" + +static void prv_log_string_v1(BinLogMessage_String_v1 *msg); +#if PBL_LOGS_HASHED +static void prv_log_param_v1(BinLogMessage_Param_v1 *msg); +#else +static void prv_log_unhashed_v1(BinLogMessage_Unhashed_v1 *msg); +#endif + +void hc_endpoint_logging_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Logging_LogMsg: { + BinLogMessage_Header *header = (BinLogMessage_Header *)msg->payload; + switch (header->version) { + case BINLOGMSG_VERSION_STRING_V1: + prv_log_string_v1((BinLogMessage_String_v1 *)header); + break; + case BINLOGMSG_VERSION_PARAM_V1: +#if PBL_LOGS_HASHED + prv_log_param_v1((BinLogMessage_Param_v1 *)header); +#else + PBL_LOG(LOG_LEVEL_ERROR, "HcLog: received hashed log in unhashed build"); +#endif + break; + case BINLOGMSG_VERSION_UNHASHED_V1: +#if PBL_LOGS_HASHED + PBL_LOG(LOG_LEVEL_ERROR, "HcLog: received unhashed log in hashed build"); +#else + prv_log_unhashed_v1((BinLogMessage_Unhashed_v1 *)header); +#endif + break; + default: + PBL_LOG(LOG_LEVEL_ERROR, "HcLog: unexpected log header version %d", header->version); + break; + } + } + break; + default: + PBL_LOG(LOG_LEVEL_INFO, "HcLog: unhandled message id: %d\n", msg->command_id); + break; + } +} + +void hc_endpoint_logging_set_level(uint8_t level) { + hc_protocol_enqueue_with_payload(HcEndpointID_Logging, HcMessageID_Logging_SetLevel, + &level, sizeof(level)); +} + +bool hc_endpoint_logging_get_level(uint8_t *level) { + HcProtocolMessage request = { + .message_length = sizeof(request), + .endpoint_id = HcEndpointID_Logging, + .command_id = HcMessageID_Logging_GetLevel, + }; + HcProtocolMessage *response = hc_protocol_enqueue_and_expect(&request); + if (!response) { + return false; + } + if (response->message_length < sizeof(HcProtocolMessage) + sizeof(uint8_t)) { + PBL_LOG(LOG_LEVEL_ERROR, "Received error response."); + return false; + } + *level = response->payload[0]; + kernel_free(response); + return true; +} + +static void prv_log_string_v1(BinLogMessage_String_v1 *msg) { + PBL_LOG(LOG_LEVEL_ALWAYS, "%s", msg->string); +} + +#if PBL_LOGS_HASHED + +static void prv_log_param_v1(BinLogMessage_Param_v1 *msg) { + // HACK ALERT: we want to call a var-args function but we don't want to create our own stack + // frame. That would be ugly. Since we can include more arguments than the callee uses, we'll just + // include the maximum number of parameters (limited to 7 by New Logging) and ignore the + // overhead. TODO: once the OS switches to binary logging, this will simply be a packet copy with + // no further watch-side processing. + uint32_t args[7] = { 0 }; + + // Unpack the arguments + uint32_t *arg = msg->body.payload; + unsigned int arg_index = 0; + + while ((uint8_t *)arg < (uint8_t *)msg + msg->header.length) { + if (((msg->body.msgid.str_index_1 != 0) && (arg_index + 1 == (msg->body.msgid.str_index_1))) || + ((msg->body.msgid.str_index_2 != 0) && (arg_index + 1 == (msg->body.msgid.str_index_2)))) { + // String. TODO: the string is null terminated for now. + BinLogMessage_StringParam *str_param = (BinLogMessage_StringParam *)arg; + // Send the string pointer! + args[arg_index] = (uint32_t)str_param->string; + arg += (str_param->length + 1 + (sizeof(uint32_t) - 1)) / sizeof(uint32_t); + } else { + // Integer + args[arg_index] = *arg; + arg++; + } + arg_index++; + } + + // Fixup the hash (add the #args, ignore the task id for now, leave the level 0) TODO. + uint32_t hash = ((msg->body.msgid.msg_id & MSGID_STR_AND_HASH_MASK) | + ((arg_index & PACKED_NUM_FMT_MASK) << PACKED_NUM_FMT_OFFSET)); + // The core must be in the correct position for pbl_log_hashed_core. + uint32_t core = (msg->body.msgid.msg_id & (PACKED_CORE_MASK << PACKED_CORE_OFFSET)); + + pbl_log_hashed_core(core, hash, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); +} + +#else // PBL_LOGS_HASHED + +static void prv_log_unhashed_v1(BinLogMessage_Unhashed_v1 *msg) { + // Force NULL termination on the filename + char filename[17] = { 0 }; + memcpy(filename, msg->body.filename, 16); + + pbl_log(msg->body.level, filename, msg->body.line_number, (const char *)msg->body.string); +} + +#endif // PBL_LOGS_HASHED diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_pairing.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_pairing.c new file mode 100644 index 00000000..2b9e63c8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_pairing.c @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_pairing.h" +#include "hc_protocol/hc_protocol.h" + +#include "pairing_confirm_impl.h" + +#include "system/logging.h" + +#include + +void hc_endpoint_pairing_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Pairing_PairingRequest: { + const HcProtocolMessagePairingRequestPayload *request = + (const HcProtocolMessagePairingRequestPayload *)msg->payload; + pairing_confirm_handle_request(&request->device); + break; + } + + case HcMessageID_Pairing_PairingComplete: { + const HcProtocolMessagePairingCompletePayload *request = + (const HcProtocolMessagePairingCompletePayload *)msg->payload; + pairing_confirm_handle_complete(&request->device, request->status); + break; + } + + default: + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected cmd ID: %d", msg->command_id); + break; + } +} + +void hc_endpoint_pairing_send_pairing_response(const BTDeviceInternal *device, bool is_confirmed) { + const HcProtocolMessagePairingResponsePayload payload = { + .device = *device, + .is_confirmed = is_confirmed, + }; + hc_protocol_enqueue_with_payload(HcEndpointID_Pairing, HcMessageID_Pairing_PairingResponse, + (const uint8_t *)&payload, sizeof(payload)); +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_pebble_pairing_service.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_pebble_pairing_service.c new file mode 100644 index 00000000..603cb58d --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_pebble_pairing_service.c @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_pebble_pairing_service.h" + +#include "comm/bt_lock.h" +#include "system/logging.h" +#include "comm/ble/gap_le_connection.h" + +#include + +void hc_endpoint_pebble_pairing_service_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_PebblePairingServiceiOSAppTerminationDetected: + bt_driver_cb_pebble_pairing_service_handle_ios_app_termination_detected(); + break; + + case HcMessageID_PebblePairingServiceFoundGateway: { + bt_lock(); + { + BTDeviceInternal *device = (BTDeviceInternal *)&msg->payload[0]; + GAPLEConnection *connection = gap_le_connection_by_device(device); + if (connection) { + gap_le_connection_set_gateway(connection, true); + } + } + bt_unlock(); + break; + } + + case HcMessageID_PebblePairingServiceConnParams: { + const HcPpsConnParamsPayload *payload = (const HcPpsConnParamsPayload *)&msg->payload[0]; + size_t params_length = msg->message_length - offsetof(HcPpsConnParamsPayload, conn_params); + bt_driver_cb_pebble_pairing_service_handle_connection_parameter_write( + &payload->device, &payload->conn_params, params_length); + break; + } + + default: + PBL_LOG(LOG_LEVEL_ERROR, + "Unknown hc_endpoint_pebble_pairing_service_handler cmdid %d", msg->command_id); + break; + } +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_responsiveness_request_update.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_responsiveness_request_update.c new file mode 100644 index 00000000..742fccdb --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_responsiveness_request_update.c @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_responsiveness.h" +#include "bluetooth/responsiveness.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +#include "ble_common.h" + +bool hc_endpoint_responsiveness_request_update( + const BTDeviceInternal *addr, const BleConnectionParamsUpdateReq *params) { + uint8_t message_len = sizeof(HcProtocolMessageResponsivenessPayload) + sizeof(HcProtocolMessage); + uint8_t buf[message_len]; + + HcProtocolMessage *msg = (HcProtocolMessage *)&buf[0]; + + *msg = (HcProtocolMessage) { + .message_length = message_len, + .endpoint_id = HcEndpointID_Responsiveness, + .command_id = HcMessageID_Id_ConnParamUpdateReq, + }; + + HcProtocolMessageResponsivenessPayload *payload = + (HcProtocolMessageResponsivenessPayload *)msg->payload; + *payload = (HcProtocolMessageResponsivenessPayload) { + .address = *addr, + .params = *params, + }; + + return hc_protocol_enqueue(msg); +} + +void hc_endpoint_responsiveness_handler(const HcProtocolMessage *msg) { + switch (msg->command_id) { + case HcMessageID_Id_ConnParamUpdateResponse: + bt_driver_handle_le_conn_params_update_event( + (BleConnectionUpdateCompleteEvent *)&msg->payload); + break; + default: + PBL_LOG(LOG_LEVEL_WARNING, "Unexpected command: 0x%x", (int)msg->command_id); + } +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_test.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_test.c new file mode 100644 index 00000000..22cee483 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_endpoint_test.c @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_test.h" +#include "hc_protocol/hc_protocol.h" +#include "kernel/pbl_malloc.h" +#include "system/logging.h" + +void bt_driver_le_test_pa(BtlePaConfig option) { + hc_protocol_enqueue_with_payload(HcEndpointID_Test, HcMessageID_Test_Config_PA, + &option, sizeof(option)); +} + +void hc_endpoint_test_unmodulated_tx_test_start(uint8_t tx_channel) { + HcTestUnmodTxStart tx_test_payload = { + .tx_channel = tx_channel + }; + hc_protocol_enqueue_with_payload( + HcEndpointID_Test, HcMessageID_Test_UnmodulatedTxStart, (uint8_t *)&tx_test_payload, + sizeof(tx_test_payload)); +} + +void hc_endpoint_test_unmodulated_tx_test_stop(void) { + hc_protocol_enqueue_with_payload(HcEndpointID_Test, HcMessageID_Test_UnmodulatedTxStop, NULL, 0); +} + +void bt_driver_core_dump(BtleCoreDump type) { + uint8_t option = (uint8_t)type; + hc_protocol_enqueue_with_payload(HcEndpointID_Test, HcMessageID_Test_Core_Dump, + &option, sizeof(option)); +} + +void bt_driver_send_sleep_test_cmd(bool force_ble_sleep) { + HcTestSleep args = { + .force_sleep = force_ble_sleep + }; + + HcProtocolMessage *resp = hc_protocol_enqueue_with_payload_and_expect( + HcEndpointID_Test, HCMessageID_Test_Sleep, (uint8_t *)&args, sizeof(args)); + + // Nothing useful encoded in the response, just forcing some extra SPI traffic for the purposes + // of this test + kernel_free(resp); +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_protocol_cb_dispatcher.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_protocol_cb_dispatcher.c new file mode 100644 index 00000000..9fd4d48d --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_protocol_cb_dispatcher.c @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_protocol.h" + +#include "kernel/pbl_malloc.h" +#include "kernel/pebble_tasks.h" +#include "kernel/util/sleep.h" +#include "system/passert.h" + +#include "FreeRTOS.h" +#include "semphr.h" +#include "task.h" + +#include +#include + +// Note: This thread is soley responsible for executing asynchronous callbacks for +// data received from the BT Controller (via BTTrans). We do this to prevent deadlocks +// which can result if BTTrans handles both synchronous and asynchronous callbacks. + +typedef struct HcProtocolCbDispatcher { + volatile TaskHandle_t task; + QueueHandle_t job_queue; +} HcProtocolCbDispatcher; + +static HcProtocolCbDispatcher s_dispatcher = {}; + +typedef struct { + HcProtocolMessageHandler cb; + HcProtocolMessage *msg; +} HcProtocolMessageHandlerEvent; + +static void prv_terminate_dispatcher_cb(const HcProtocolMessage *unused) { + portBASE_TYPE result; + do { + HcProtocolMessageHandlerEvent e; + result = xQueueReceive(s_dispatcher.job_queue, &e, 0); + if (result == pdTRUE) { + kernel_free(e.msg); + } + } while (result == pdTRUE); + + vQueueDelete(s_dispatcher.job_queue); + s_dispatcher.job_queue = NULL; + s_dispatcher.task = NULL; + pebble_task_unregister(PebbleTask_BTCallback); +} + +static void prv_dispatcher_main(void *unused) { + HcProtocolMessageHandlerEvent e; + do { + portBASE_TYPE result = xQueueReceive(s_dispatcher.job_queue, &e, portMAX_DELAY); + PBL_ASSERTN(result); + + e.cb(e.msg); + kernel_free(e.msg); + } while (e.cb != prv_terminate_dispatcher_cb); + + vTaskDelete(NULL); +} + +void hc_protocol_cb_dispatch_handler( + const HcProtocolMessageHandler handler, HcProtocolMessage *message, bool *should_free) { + HcProtocolMessage *msg_copy = NULL; + + bool buffer_allocated = *should_free; + if (!buffer_allocated) { + msg_copy = (HcProtocolMessage *)kernel_malloc_check(message->message_length); + memcpy(msg_copy, message, message->message_length); + } else { + msg_copy = message; + } + + *should_free = false; + + HcProtocolMessageHandlerEvent event = { + .cb = handler, + .msg = msg_copy, + }; + xQueueSendToBack(s_dispatcher.job_queue, &event, portMAX_DELAY); +} + +void hc_protocol_cb_dispatcher_init(void) { + static const int HC_PROTOCOL_CB_DISPATCHER_MAX_JOBS_QUEUED = 15; + + s_dispatcher = (HcProtocolCbDispatcher) { + .job_queue = xQueueCreate( + HC_PROTOCOL_CB_DISPATCHER_MAX_JOBS_QUEUED, sizeof(HcProtocolMessageHandlerEvent)), + }; + + TaskParameters_t task_params = { + .pvTaskCode = prv_dispatcher_main, + .pcName = "HcDispatcher", + .usStackDepth = 3092 / sizeof( StackType_t ), // TODO: Figure out stack size we want + .uxPriority = (tskIDLE_PRIORITY + 3) | portPRIVILEGE_BIT, + .puxStackBuffer = NULL, // TODO: Do we want the stack to live in a particular place? + }; + + pebble_task_create(PebbleTask_BTCallback, &task_params, (TaskHandle_t *)&s_dispatcher.task); +} + +void hc_protocol_cb_dispatcher_deinit(void) { + // Signal the dispatcher task to shutdown + HcProtocolMessageHandlerEvent event = { + .cb = prv_terminate_dispatcher_cb, + .msg = NULL, + }; + // Send the kill job to the front of the queue so its processed before any + // other events already pended + xQueueSendToFront(s_dispatcher.job_queue, &event, portMAX_DELAY); + + while (s_dispatcher.task) { + // Trigger context switches & give low priority tasks a chance to run until + // the dispatcher thread wakes and exits + psleep(2); + } +} diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol/hc_protocol_endpoints_table.c b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_protocol_endpoints_table.c new file mode 100644 index 00000000..acf4182a --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol/hc_protocol_endpoints_table.c @@ -0,0 +1,53 @@ +/* + * 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. + */ + + +#include "hc_protocol/hc_endpoint_advert.h" +#include "hc_protocol/hc_endpoint_analytics.h" +#include "hc_protocol/hc_endpoint_bonding_sync.h" +#include "hc_protocol/hc_endpoint_chip_id.h" +#include "hc_protocol/hc_endpoint_discovery.h" +#include "hc_protocol/hc_endpoint_gap_le_connect.h" +#include "hc_protocol/hc_endpoint_gap_service.h" +#include "hc_protocol/hc_endpoint_gatt.h" +#include "hc_protocol/hc_endpoint_hci.h" +#include "hc_protocol/hc_endpoint_hrm.h" +#include "hc_protocol/hc_endpoint_logging.h" +#include "hc_protocol/hc_endpoint_pairing.h" +#include "hc_protocol/hc_endpoint_pebble_pairing_service.h" +#include "hc_protocol/hc_endpoint_responsiveness.h" +#include "hc_protocol/hc_protocol.h" + +#include + +// These are the handlers that get called when a message is received on the FW MCU side +const HcProtocolMessageHandler g_hc_protocol_endpoints_table[HcEndpointIDCount] = { + [HcEndpointID_Invalid] = NULL, + [HcEndpointID_Hci] = hc_endpoint_hci_handler, + [HcEndpointID_GapService] = hc_endpoint_gap_service_resp_handler, + [HcEndpointID_Id] = hc_endpoint_chip_id_handler, + [HcEndpointID_Advert] = hc_endpoint_advert_resp_handler, + [HcEndpointID_Responsiveness] = hc_endpoint_responsiveness_handler, + [HcEndpointID_GapLEConnect] = hc_endpoint_gap_le_connect_handler, + [HcEndpointID_Gatt] = hc_endpoint_gatt_handler_host, + [HcEndpointID_Discovery] = hc_endpoint_discovery_handler, + [HcEndpointID_BondingSync] = hc_endpoint_bonding_sync_handler, + [HcEndpointID_PebblePairingService] = hc_endpoint_pebble_pairing_service_handler, + [HcEndpointID_Pairing] = hc_endpoint_pairing_handler, + [HcEndpointID_Logging] = hc_endpoint_logging_handler, + [HcEndpointID_Analytics] = hc_endpoint_analytics_host_handler, + [HcEndpointID_HRM] = hc_endpoint_hrm_handler, +}; diff --git a/src/bluetooth-fw/da1468x/host/hc_protocol_cb_handler.h b/src/bluetooth-fw/da1468x/host/hc_protocol_cb_handler.h new file mode 100644 index 00000000..62f91d39 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hc_protocol_cb_handler.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#pragma once + +void hc_protocol_cb_dispatcher_init(void); +void hc_protocol_cb_dispatcher_deinit(void); diff --git a/src/bluetooth-fw/da1468x/host/host_transport.c b/src/bluetooth-fw/da1468x/host/host_transport.c new file mode 100644 index 00000000..5b6a7591 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/host_transport.c @@ -0,0 +1,620 @@ +/* + * 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. + */ + +#include "host_transport.h" +#include "host_transport_protocol.h" + +#include "dialog_spi.h" +#include "hc_protocol/hc_protocol.h" +#include "hc_protocol_cb_handler.h" +#include "core_dump.h" +#include "drivers/periph_config.h" +#include "drivers/timer.h" +#include "kernel/pbl_malloc.h" +#include "kernel/pebble_tasks.h" +#include "kernel/util/sleep.h" +#include "kernel/util/stop.h" +#include "services/common/analytics/analytics.h" +#include "services/common/analytics/analytics_logging.h" +#include "services/common/bluetooth/bluetooth_ctl.h" +#include "services/common/system_task.h" +#include "system/hexdump.h" +#include "system/logging.h" +#include "system/passert.h" +#include "system/reset.h" +#include "util/assert.h" +#include "util/circular_buffer.h" +#include "util/crc32.h" +#include "util/math.h" + +#include "FreeRTOS.h" +#include "semphr.h" +#include "task.h" + +#include + +// Design doc & State Machine Diagram at: +// https://docs.google.com/document/d/1or2Ygs3sWt_5XNW_Mpe3Vxmhwuh3DTzdgZr6QlEe7iQ + +// Core Dump doc at: +// https://docs.google.com/document/d/1UiasJyyQTD66mdpELfr7oLZjekOZn-o33E4iN5lQNic + +// We want TIM6 to run at 32KHz +static const uint32_t TIMER_FREQUENCY_HZ = 32000; +// We want to be told when 500 ms elapses +static const uint32_t TIMER_PERIOD_TICKS = 16000; + +typedef enum { + SPITransportState_Idle, + SPITransportState_StatusExchange, + SPITransportState_WaitingForReceiving, + SPITransportState_Receiving, + SPITransportState_WaitingForReceiving_Footer, + SPITransportState_Receiving_Footer, + SPITransportState_WaitingForSending, + SPITransportState_Sending, + SPITransportState_WaitingForSending_Footer, + SPITransportState_Sending_Footer, + SPITransportState_CoreDump, +} SPITransportState; + +typedef struct SPITransport { + SPITransportState state; + size_t bytes_receivable_count; + size_t bytes_sendable_count; + bool should_transact_after_consuming_rx; + uint8_t *rx_write_ptr; + const uint8_t *tx_read_ptr; + CircularBuffer rx; + CircularBuffer tx; + SemaphoreHandle_t semph; + TaskHandle_t task; + volatile bool task_should_deinit; + volatile bool task_is_running; + SPITransportMsgStatus *status_local; + SPITransportMsgStatus status_remote; + uint8_t rx_storage[HOST_TRANSPORT_HOST_RX_BUFFER_SIZE]; + uint8_t *tx_storage; + SPITransportMsgFooter rx_footer; + SPITransportMsgFooter *tx_footer; + bool watchdog_timer_active; +} SPITransport; + +static uint8_t DMA_READ_BSS s_spi_tx_storage_buffer[HOST_TRANSPORT_HOST_TX_BUFFER_SIZE]; +static SPITransportMsgFooter DMA_READ_BSS s_spi_tx_footer; +static SPITransportMsgStatus DMA_READ_BSS s_spi_status_local; + +static SPITransport DMA_BSS s_spi_transport; +static bool s_is_host_transport_initialized; + +static void prv_give_semamphore_from_isr(bool *should_context_switch); +static void prv_trigger_core_dump_from_isr(void); + +static void prv_lock(void) { + portENTER_CRITICAL(); +} + +static void prv_unlock(void) { + portEXIT_CRITICAL(); +} + +static void prv_watchdog_init(void) { + s_spi_transport.watchdog_timer_active = false; + + // Enable the timer clock + periph_config_enable(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, + BOARD_BT_WATCHDOG_TIMER.timer.config_clock); + + // Setup timer 3 to generate Bluetooth Host Transport priority interrupts + NVIC_InitTypeDef NVIC_InitStructure; + TIM_ClearITPendingBit(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, TIM_IT_Update); + NVIC_InitStructure.NVIC_IRQChannel = BOARD_BT_WATCHDOG_TIMER.irq_channel; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = tskIDLE_PRIORITY + 3; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); + + // Setup timer 3 for periodic interrupts at TIMER_INTERRUPT_HZ + TIM_TimeBaseInitTypeDef tim_config; + TIM_TimeBaseStructInit(&tim_config); + tim_config.TIM_Prescaler = timer_find_prescaler(&BOARD_BT_WATCHDOG_TIMER.timer, + TIMER_FREQUENCY_HZ); + tim_config.TIM_Period = TIMER_PERIOD_TICKS; + tim_config.TIM_CounterMode = TIM_CounterMode_Up; + TIM_TimeBaseInit(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, &tim_config); + + TIM_Cmd(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, DISABLE); + TIM_ClearITPendingBit(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, TIM_IT_Update); + TIM_ITConfig(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, TIM_IT_Update, ENABLE); +} + +static void prv_watchdog_deinit(void) { + TIM_Cmd(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, DISABLE); + TIM_ITConfig(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, TIM_IT_Update, DISABLE); + periph_config_disable(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, RCC_APB1Periph_TIM6); + s_spi_transport.watchdog_timer_active = false; +} + +static void prv_watchdog_start(void) { + prv_lock(); + if (!s_spi_transport.watchdog_timer_active) { + TIM_SetCounter(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, 0); // Reset the count to 0. + TIM_Cmd(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, ENABLE); + s_spi_transport.watchdog_timer_active = true; + + // Prevent us from entering stop mode (and disabling the clock timer) + stop_mode_disable(InhibitorBluetoothWatchdog); + } + prv_unlock(); +} + +static void prv_watchdog_stop(void) { + prv_lock(); + if (s_spi_transport.watchdog_timer_active) { + TIM_Cmd(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, DISABLE); + s_spi_transport.watchdog_timer_active = false; + // Allow us to enter stop mode + stop_mode_enable(InhibitorBluetoothWatchdog); + } + prv_unlock(); +} + +static void prv_trigger_core_dump_from_isr(void) { + bool should_context_switch; + s_spi_transport.state = SPITransportState_CoreDump; + prv_give_semamphore_from_isr(&should_context_switch); +} + +#if !defined(DIALOG_TIMER_IRQ_HANDLER) +#error "DIALOG_TIMER_IRQ_HANDLER must be defined in the board_*.h file!" +#endif +void DIALOG_TIMER_IRQ_HANDLER(void *ctx) { + dbgserial_putstr("BT Trans TO!"); + + prv_watchdog_stop(); + + prv_trigger_core_dump_from_isr(); + + TIM_ClearITPendingBit(BOARD_BT_WATCHDOG_TIMER.timer.peripheral, TIM_IT_Update); +} + +static void prv_give_semamphore_from_isr(bool *should_context_switch) { + portBASE_TYPE was_higher_task_woken = pdFALSE; + xSemaphoreGiveFromISR(s_spi_transport.semph, &was_higher_task_woken); + *should_context_switch = (was_higher_task_woken != pdFALSE); +} + +static void prv_handle_sending_footer_complete_isr(bool *should_context_switch) { + s_spi_transport.state = SPITransportState_Idle; + prv_lock(); + circular_buffer_consume(&s_spi_transport.tx, s_spi_transport.bytes_sendable_count); + prv_unlock(); + + prv_give_semamphore_from_isr(should_context_switch); +} + +static void prv_handle_sending_complete_isr(bool *should_context_switch) { + // Wait for the controller to signal that it's ready + prv_watchdog_start(); + s_spi_transport.state = SPITransportState_WaitingForSending_Footer; +} + +static void prv_handle_int_waiting_for_sending_footer(void) { + s_spi_transport.state = SPITransportState_Sending_Footer; + s_spi_transport.tx_footer->crc = crc32( + CRC32_INIT, s_spi_transport.tx_read_ptr, + s_spi_transport.bytes_sendable_count); + + dialog_spi_send_and_receive_dma(s_spi_transport.tx_footer, NULL, + sizeof(*s_spi_transport.tx_footer), + prv_handle_sending_footer_complete_isr); +} + +static void prv_handle_int_waiting_for_sending(void) { + s_spi_transport.state = SPITransportState_Sending; + dialog_spi_send_and_receive_dma(s_spi_transport.tx_read_ptr, NULL, + s_spi_transport.bytes_sendable_count, + prv_handle_sending_complete_isr); +} + +static void prv_handle_circular_buffer_write_complete(void) { + prv_lock(); + circular_buffer_write_finish(&s_spi_transport.rx, s_spi_transport.bytes_receivable_count); + prv_unlock(); +} + +static void prv_handle_receiving_footer_complete_isr(bool *should_context_switch) { + // Check the received CRC + uint32_t crc = crc32(CRC32_INIT, s_spi_transport.rx_write_ptr, + s_spi_transport.bytes_receivable_count); + if (crc != s_spi_transport.rx_footer.crc) { + PBL_LOG(LOG_LEVEL_ERROR, "CRC failed on remote SPI Receive 0x%08" PRIX32 " != 0x%08" PRIX32, + crc, s_spi_transport.rx_footer.crc); + + prv_trigger_core_dump_from_isr(); + } + + bool should_give_semaphore = false; + if (s_spi_transport.bytes_sendable_count) { + prv_watchdog_start(); + s_spi_transport.state = SPITransportState_WaitingForSending; + } else { + // Nothing to send, we're done: + s_spi_transport.state = SPITransportState_Idle; + should_give_semaphore = true; + } + + prv_handle_circular_buffer_write_complete(); + + if (should_give_semaphore) { + prv_give_semamphore_from_isr(should_context_switch); + } +} + +static void prv_handle_receiving_complete_isr(bool *should_context_switch) { + // Wait for the controller to signal that it's ready + prv_watchdog_start(); + s_spi_transport.state = SPITransportState_WaitingForReceiving_Footer; +} + +static void prv_handle_int_waiting_for_receiving(void) { + s_spi_transport.state = SPITransportState_Receiving; + dialog_spi_send_and_receive_dma(NULL, s_spi_transport.rx_write_ptr, + s_spi_transport.bytes_receivable_count, + prv_handle_receiving_complete_isr); +} + +static void prv_handle_int_waiting_for_receiving_footer(void) { + s_spi_transport.state = SPITransportState_Receiving_Footer; + dialog_spi_send_and_receive_dma(NULL, &s_spi_transport.rx_footer, + sizeof(s_spi_transport.rx_footer), + prv_handle_receiving_footer_complete_isr); +} + +static void prv_handle_status_exchange_complete_isr(bool *should_context_switch) { + uint32_t crc = crc32(CRC32_INIT, &s_spi_transport.status_remote, + sizeof(s_spi_transport.status_remote)); + if (crc != CRC32_RESIDUE) { + PBL_LOG(LOG_LEVEL_ERROR, "CRC failed on remote SPITransportMsgStatus 0x%"PRIx32 " vs 0x%"PRIx32, + crc, (uint32_t)CRC32_RESIDUE); + PBL_LOG(LOG_LEVEL_DEBUG, "->OUT"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)s_spi_transport.status_local, + sizeof(s_spi_transport.status_remote)); + PBL_LOG(LOG_LEVEL_DEBUG, "->IN:"); + PBL_HEXDUMP(LOG_LEVEL_DEBUG, (uint8_t *)&s_spi_transport.status_remote, + sizeof(s_spi_transport.status_remote)); + prv_trigger_core_dump_from_isr(); + return; + } + + if (s_spi_transport.status_remote.msg_id != SPITransportMsgID_Status) { + // Unexpected msg ID... + prv_trigger_core_dump_from_isr(); + return; + } + + // We now know how many bytes to receive / send: + s_spi_transport.bytes_receivable_count = MIN(s_spi_transport.status_local->bytes_receivable_count, + s_spi_transport.status_remote.bytes_sendable_count); + s_spi_transport.bytes_sendable_count = MIN(s_spi_transport.status_local->bytes_sendable_count, + s_spi_transport.status_remote.bytes_receivable_count); + + if (s_spi_transport.bytes_receivable_count == 0) { + // There's no data to receive so close out the write prep to the rx buffer + prv_handle_circular_buffer_write_complete(); + } + + // Nothing to do for now, just wait for INT to be asserted again. + + if (s_spi_transport.bytes_receivable_count) { + prv_watchdog_start(); + s_spi_transport.state = SPITransportState_WaitingForReceiving; + } else if (s_spi_transport.bytes_sendable_count) { + prv_watchdog_start(); + s_spi_transport.state = SPITransportState_WaitingForSending; + } else { + // Nothing to send nor receive, back to idle: + s_spi_transport.state = SPITransportState_Idle; + } +} + +static void prv_update_local_status_msg(void) { + prv_lock(); + circular_buffer_read(&s_spi_transport.tx, s_spi_transport.tx.data_length, + &s_spi_transport.tx_read_ptr, + &s_spi_transport.status_local->bytes_sendable_count); + s_spi_transport.status_local->bytes_receivable_count = + circular_buffer_write_prepare(&s_spi_transport.rx, &s_spi_transport.rx_write_ptr); + prv_unlock(); + + size_t crc_len = sizeof(*s_spi_transport.status_local) - + sizeof(s_spi_transport.status_local->crc); + s_spi_transport.status_local->crc = crc32(CRC32_INIT, s_spi_transport.status_local, crc_len); +} + +static void prv_handle_int_idle(void) { + // We're idle. First do the "status exchange", so we know how much to send & receive. + s_spi_transport.state = SPITransportState_StatusExchange; + prv_update_local_status_msg(); + dialog_spi_send_and_receive_dma(s_spi_transport.status_local, + &s_spi_transport.status_remote, + sizeof(SPITransportMsgStatus), + prv_handle_status_exchange_complete_isr); +} + +static bool prv_handle_rx_buffer_full(void) { + prv_lock(); + bool is_rx_buffer_full = (0 == circular_buffer_get_write_space_remaining(&s_spi_transport.rx)); + if (is_rx_buffer_full) { + s_spi_transport.should_transact_after_consuming_rx = true; + } + prv_unlock(); + return is_rx_buffer_full; +} + +static void prv_handle_rx_buffer_has_space_available(void) { + // The slave is waiting to send more, just fire the external interrupt again: + if (s_spi_transport.should_transact_after_consuming_rx) { + s_spi_transport.should_transact_after_consuming_rx = false; + dialog_spi_set_pending_int(); + } +} + +static void prv_wtf_analytic_cb(void *context) { +#ifndef RECOVERY_FW + ANALYTICS_LOG_DEBUG("Bluetooth Host Transport WTF"); + AnalyticsEventBlob event_blob = { + .event = AnalyticsEvent_BtLockupError, + }; + analytics_logging_log_event(&event_blob); +#endif +} + +static void prv_int_exti_cb(bool *should_context_switch) { + // The controller responded -- stop the watchdog. + prv_watchdog_stop(); + + switch (s_spi_transport.state) { + case SPITransportState_Idle: + // If the RX buffer is full, defer ack'ing the Dialog INT until we have + // some room to actually receive data. + // + // Note: We could probably skip this check altogether but I think it will + // prevent the BT controller from continously doing a status exchange + // only to find out it can't send any data + if (!prv_handle_rx_buffer_full()) { + prv_handle_int_idle(); + } + break; + + case SPITransportState_WaitingForReceiving: + prv_handle_int_waiting_for_receiving(); + break; + + case SPITransportState_WaitingForReceiving_Footer: + prv_handle_int_waiting_for_receiving_footer(); + break; + + case SPITransportState_WaitingForSending: + prv_handle_int_waiting_for_sending(); + break; + + case SPITransportState_WaitingForSending_Footer: + prv_handle_int_waiting_for_sending_footer(); + break; + + case SPITransportState_StatusExchange: + case SPITransportState_Receiving: + case SPITransportState_Sending: + case SPITransportState_Receiving_Footer: + case SPITransportState_Sending_Footer: { + // Got INT while DMA TC interrupt hasn't happened yet. + // This might happen if the Dialog side would be *extremely* fast and assert the INT line + // faster than the DMA transfer complete interrupt happens. Probably very unlikely, but + // in case we're worried about this: + // FIXME: Maybe the solution is to disable the EXTI while DMA is on-going and re-enable + // when it's finished? OTOH, this might be tricky when we're also using INT toggles to + // detect a spontaneous reset of the Dialog chip. + + // For now, let's try to collect a core dump and then cleanly restart BLE. The root cause + // will be determined later. + // Also, collect an analytic to see how frequently this happens. + bool should_context_switch; + system_task_add_callback_from_isr(prv_wtf_analytic_cb, NULL, &should_context_switch); + + prv_trigger_core_dump_from_isr(); + break; + } + + case SPITransportState_CoreDump: + // Do nothing. The proper authorities have already been notified. + break; + + default: + break; + } +} + +static size_t prv_host_transport_tx_get_length(void) { + prv_lock(); + size_t rx_length = circular_buffer_get_read_space_remaining(&s_spi_transport.tx); + prv_unlock(); + return rx_length; +} + +static void prv_indicate_has_data_if_idle(void) { + prv_lock(); + bool is_idle = (s_spi_transport.state == SPITransportState_Idle); + prv_unlock(); + if (is_idle) { + prv_watchdog_start(); + dialog_spi_indicate_data_ready_to_tx(); + } +} + +static void prv_host_transport_main(void *unused) { + s_spi_transport.task_is_running = true; + + while (true) { + xSemaphoreTake(s_spi_transport.semph, portMAX_DELAY); + + if (s_spi_transport.task_should_deinit) { + break; + } + if (s_spi_transport.state == SPITransportState_CoreDump) { + PBL_LOG(LOG_LEVEL_ALWAYS, "Bluetooth Host Transport Crash -- Dumping Core"); + core_dump_and_reset_or_reboot(); + break; + } + + // It's possible new data has been batched up while we were sending data. + // Check the circular buffer and notify the controller if that's the case + if (prv_host_transport_tx_get_length() > 0) { + prv_indicate_has_data_if_idle(); + } + + hc_protocol_process_receive_buffer(); + + prv_handle_rx_buffer_has_space_available(); + } + + // Clean-up & kill this task + pebble_task_unregister(PebbleTask_BTRX); + + // Signal host_transport_deinit() that we're done + s_spi_transport.task_is_running = false; + vTaskDelete(NULL); +} + +HostTransportEnqueueStatus host_transport_tx_enqueue(const uint8_t *data, size_t length) { + if (!s_is_host_transport_initialized) { + return HostTransportEnqueueStatus_Failure; + } + + if (s_spi_transport.state == SPITransportState_CoreDump) { + PBL_LOG(LOG_LEVEL_INFO, "Skipping host transport enqueue .. waiting for reset"); + return HostTransportEnqueueStatus_Failure; + } + + PBL_ASSERTN(length < HOST_TRANSPORT_HOST_TX_BUFFER_SIZE && + length < HOST_TRANSPORT_CTLR_RX_BUFFER_SIZE); + + prv_lock(); + bool success = circular_buffer_write(&s_spi_transport.tx, data, length); + prv_unlock(); + + if (success) { + prv_indicate_has_data_if_idle(); + analytics_add(ANALYTICS_DEVICE_METRIC_BT_UART_BYTES_OUT, length, AnalyticsClient_System); + return HostTransportEnqueueStatus_Success; + } + + return HostTransportEnqueueStatus_RetryLater; +} + +size_t host_transport_rx_get_length(void) { + prv_lock(); + size_t rx_length = circular_buffer_get_read_space_remaining(&s_spi_transport.rx); + prv_unlock(); + return rx_length; +} + +bool host_transport_rx_read(uint8_t **data_ptr_out, size_t length) { + prv_lock(); + bool caller_should_free = false; + PBL_ASSERTN(circular_buffer_read_or_copy(&s_spi_transport.rx, data_ptr_out, length, + kernel_malloc, &caller_should_free)); + prv_unlock(); + return caller_should_free; +} + +void host_transport_rx_consume(size_t length) { + prv_lock(); + circular_buffer_consume(&s_spi_transport.rx, length); + prv_unlock(); + + hc_protocol_buffer_gained_space(); + analytics_add(ANALYTICS_DEVICE_METRIC_BT_UART_BYTES_IN, length, AnalyticsClient_System); +} + +bool host_transport_is_current_task_host_transport_task(void) { + return (s_spi_transport.task == xTaskGetCurrentTaskHandle()); +} + +bool host_transport_init(void) { + s_spi_transport.tx_storage = s_spi_tx_storage_buffer; + s_spi_transport.tx_footer = &s_spi_tx_footer; + s_spi_transport.status_local = &s_spi_status_local; + s_spi_transport.semph = xSemaphoreCreateBinary(); + PBL_ASSERTN(s_spi_transport.semph); + + prv_watchdog_init(); + + hc_protocol_cb_dispatcher_init(); + + s_spi_transport.task_is_running = false; + s_spi_transport.task_should_deinit = false; + + TaskParameters_t task_params = { + .pvTaskCode = prv_host_transport_main, + .pcName = "BTTrans", + .usStackDepth = configMINIMAL_STACK_SIZE * 2, + .uxPriority = (tskIDLE_PRIORITY + 3) | portPRIVILEGE_BIT, + .puxStackBuffer = NULL, // TODO: Do we want the stack to live in a particular place? + }; + pebble_task_create(PebbleTask_BTRX, &task_params, &s_spi_transport.task); + + s_spi_transport.state = SPITransportState_Idle; + + s_spi_transport.should_transact_after_consuming_rx = false; + + *s_spi_transport.status_local = (SPITransportMsgStatus) {}; + s_spi_transport.status_local->msg_id = SPITransportMsgID_Status; + + circular_buffer_init(&s_spi_transport.rx, s_spi_transport.rx_storage, + HOST_TRANSPORT_HOST_RX_BUFFER_SIZE); + circular_buffer_init(&s_spi_transport.tx, s_spi_transport.tx_storage, + HOST_TRANSPORT_HOST_TX_BUFFER_SIZE); + dialog_spi_init(prv_int_exti_cb); + + s_is_host_transport_initialized = true; + + return true; +} + +void host_transport_deinit(void) { + PBL_LOG(LOG_LEVEL_DEBUG, "BLE transport deinit"); + s_is_host_transport_initialized = false; + + prv_watchdog_deinit(); + + dialog_spi_deinit(); + + // Flag the task exit, wake the task, and wait for the task to exit before continuing + s_spi_transport.task_should_deinit = true; + xSemaphoreGive(s_spi_transport.semph); + while (s_spi_transport.task_is_running) { + // Trigger context switches & give low priority tasks a chance to run until + // the host_transport thread wakes and exits + psleep(2); + } + vSemaphoreDelete(s_spi_transport.semph); + s_spi_transport.semph = NULL; + + s_spi_transport.task = NULL; + + hc_protocol_cb_dispatcher_deinit(); +} diff --git a/src/bluetooth-fw/da1468x/host/host_transport_impl.h b/src/bluetooth-fw/da1468x/host/host_transport_impl.h new file mode 100644 index 00000000..e5074582 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/host_transport_impl.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#pragma once + +#include + +bool host_transport_init(void); + +void host_transport_deinit(void); diff --git a/src/bluetooth-fw/da1468x/host/hrm_service.c b/src/bluetooth-fw/da1468x/host/hrm_service.c new file mode 100644 index 00000000..be5f3a7a --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/hrm_service.c @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#include + +#include "kernel/pbl_malloc.h" + +#include "hc_protocol/hc_endpoint_hrm.h" + +bool bt_driver_is_hrm_service_supported(void) { + return true; +} + +void bt_driver_hrm_service_enable(bool enable) { + hc_endpoint_hrm_enable(enable); +} + +void bt_driver_hrm_service_handle_measurement(const BleHrmServiceMeasurement *measurement, + const BTDeviceInternal *permitted_devices, + size_t num_permitted_devices) { + hc_endpoint_hrm_send_measurement(measurement, permitted_devices, num_permitted_devices); +} diff --git a/src/bluetooth-fw/da1468x/host/id.c b/src/bluetooth-fw/da1468x/host/id.c new file mode 100644 index 00000000..928d8707 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/id.c @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#include "services/common/bluetooth/local_id.h" + +#include +#include + +#include +#include + +#include "hc_protocol/hc_endpoint_gap_service.h" +#include "hc_protocol/hc_endpoint_chip_id.h" + +void bt_driver_id_set_local_device_name(const char device_name[BT_DEVICE_NAME_BUFFER_SIZE]) { + hc_endpoint_gap_service_set_dev_name(device_name); +} + +//! Generates a static address +//! +//! A static address is a 48-bit randomly generated address and shall meet the following +//! requirements: +//! - The two most significant bits of the static address shall be equal to '1' +//! - All bits of the random part of the static address shall not be equal to '1' +//! - All bits of the random part of the static address shall not be equal to '0' +//! +void bt_driver_id_copy_local_identity_address(BTDeviceAddress *addr_out) { + bt_local_id_generate_address_from_serial(addr_out); +} + +void bt_driver_set_local_address(bool allow_cycling, + const BTDeviceAddress *pinned_address) { + hc_endpoint_gap_service_set_local_address(allow_cycling, pinned_address); +} + +void bt_driver_id_copy_chip_info_string(char *dest, size_t dest_size) { + DialogChipID chip_id; + if (!hc_endpoint_chip_id_query_chip_info(&chip_id)) { + strncpy(dest, "?", dest_size); + return; + } + + // Use hex string of chip_id as unique id: + uint8_t *chip_id_bytes = (uint8_t *)&chip_id; + for (uint32_t i = 0; dest_size && i < sizeof(DialogChipID); ++i, dest_size -= 2, dest += 2) { + sprintf(dest, "%02X", chip_id_bytes[i]); + } +} + +bool bt_driver_id_generate_private_resolvable_address(BTDeviceAddress *address_out) { + return hc_endpoint_gap_service_generate_private_resolvable_address(address_out); +} diff --git a/src/bluetooth-fw/da1468x/host/init.c b/src/bluetooth-fw/da1468x/host/init.c new file mode 100644 index 00000000..866fa45b --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/init.c @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#include +#include + +#include "dialog_bootrom.h" +#include "dialog_spi_bootloader.h" +#include "hc_protocol/hc_endpoint_ctl.h" +#include "hc_protocol/hc_protocol.h" +#include "services/common/bluetooth/local_id.h" + +#include "host_transport_impl.h" + +// FW Includes: +#include "comm/bt_lock.h" +#include "kernel/events.h" +#include "kernel/util/standby.h" +#include "pebble_errors.h" +#include "resource/system_resource.h" +#include "system/logging.h" +#include "system/passert.h" +#include "system/reboot_reason.h" + +void bt_driver_init(void) { + // Perform any one-time initialization here that should happen when the main FW boots. + bt_lock_init(); + hc_protocol_boot(); +} + +bool bt_driver_start(BTDriverConfig *config) { + if (!system_resource_is_valid()) { + return false; + } + // Do stuff that needs to happen when stack starts, i.e. bootstrap, load FW, ... + + if (!dialog_bootrom_load_second_stage_bootloader()) { + // Failed to load BT bootloader. Shutdown if we haven't tried this already. + PBL_LOG(LOG_LEVEL_ERROR, "Failed to load BLE Second Stage Bootloader - shutting down"); + if (reboot_reason_get_last_reboot_reason() != RebootReasonCode_DialogBootFault) { + enter_standby(RebootReasonCode_DialogBootFault); + } + } else if (dialog_spi_bootloader_load_image() && host_transport_init()) { + hc_protocol_init(); + bt_local_id_generate_address_from_serial(&config->identity_addr); + bool result = hc_endpoint_ctl_init_sync(config); + // Nothing good comes from a failure here. BT gets partially initialized and you won't even see + // the correct state displayed in the BT Settings Menu. Until PBL-36163 is addressed, the best + // thing we can really do is reboot the watch. + PBL_ASSERTN(result); + return result; + } + + // We failed to load BT, panic instead of crash looping to provide info + PebbleEvent event = { + .type = PEBBLE_PANIC_EVENT, + .panic = { + .error_code = ERROR_CANT_LOAD_BT, + }, + }; + event_put(&event); + + return false; +} + +void bt_driver_stop(void) { + // Do stuff that needs to happen when stack stops. + if (!hc_endpoint_ctl_shutdown_sync()) { + // PBL-34091: What if the chip is in a bad state? The message may not have any effect and might + // keep us in a higher-than-desired power state. How can we be sure the chip is hibernating? + } + + host_transport_deinit(); + hc_protocol_deinit(); +} + +void bt_driver_power_down_controller_on_boot(void) { + // Don't make any assumptions about the state of the BT chip on initial bootup. (since the code + // runs in RAM) If we want to be in airplane mode, power the chip on then off + BTDriverConfig config = { }; + if (bt_driver_start(&config)) { + bt_driver_stop(); + } +} diff --git a/src/bluetooth-fw/da1468x/host/pairability.c b/src/bluetooth-fw/da1468x/host/pairability.c new file mode 100644 index 00000000..1d217412 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/pairability.c @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#include + +void bt_driver_le_pairability_set_enabled(bool enabled) { + // NYI +} + +void bt_driver_classic_pairability_set_enabled(bool enabled) { + // No BT Classic support on DA1468x +} diff --git a/src/bluetooth-fw/da1468x/host/pairing_confirm.c b/src/bluetooth-fw/da1468x/host/pairing_confirm.c new file mode 100644 index 00000000..0864211c --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/pairing_confirm.c @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#include "hc_protocol/hc_endpoint_pairing.h" + +#include "comm/ble/gap_le_connection.h" +#include "comm/bt_conn_mgr.h" +#include "comm/bt_lock.h" +#include "kernel/pbl_malloc.h" +#include "comm/bluetooth_analytics.h" +#include "system/logging.h" + +#include +#include + +void bt_driver_pairing_confirm(const PairingUserConfirmationCtx *ctx, + bool is_confirmed) { + GAPLEConnection *connection = (GAPLEConnection *)ctx; + if (!gap_le_connection_is_valid(connection)) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection found for pairing confirm"); + return; + } + hc_endpoint_pairing_send_pairing_response(&connection->device, is_confirmed); +} + +void pairing_confirm_handle_request(const BTDeviceInternal *device) { + GAPLEConnection *connection = gap_le_connection_by_device(device); + if (!connection) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection found for pairing request"); + return; + } + + // PBL-38595: Make the connection fast during the pairing: + const uint16_t max_period_secs = 40; + conn_mgr_set_ble_conn_response_time(connection, BtConsumerLePairing, + ResponseTimeMin, max_period_secs); + + PairingUserConfirmationCtx *ctx = (PairingUserConfirmationCtx *)connection; + bt_driver_cb_pairing_confirm_handle_request(ctx, NULL /* device name */, + NULL /* "just works", so no token */); + + bluetooth_analytics_handle_ble_pairing_request(); +} + +void pairing_confirm_handle_complete(const BTDeviceInternal *device, HciStatusCode status) { + const PairingUserConfirmationCtx *ctx; + bt_lock(); + { + ctx = (const PairingUserConfirmationCtx *)gap_le_connection_by_device(device); + } + bt_unlock(); + if (!ctx) { + PBL_LOG(LOG_LEVEL_ERROR, "No connection found for pairing complete"); + return; + } + + const bool success = (status == HciStatusCode_Success); + if (!success) { + bluetooth_analytics_handle_ble_pairing_error(status); + PBL_LOG(LOG_LEVEL_ERROR, "Pairing failed w/ status: 0x%x", status); + } + + bt_driver_cb_pairing_confirm_handle_completed(ctx, success); +} diff --git a/src/bluetooth-fw/da1468x/host/pairing_confirm_impl.h b/src/bluetooth-fw/da1468x/host/pairing_confirm_impl.h new file mode 100644 index 00000000..dc9d4ebc --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/pairing_confirm_impl.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +typedef struct BTDeviceInternal BTDeviceInternal; + +void pairing_confirm_handle_request(const BTDeviceInternal *device); + +void pairing_confirm_handle_complete(const BTDeviceInternal *device, HciStatusCode status); diff --git a/src/bluetooth-fw/da1468x/host/pebble_pairing_service.c b/src/bluetooth-fw/da1468x/host/pebble_pairing_service.c new file mode 100644 index 00000000..899c0d48 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/pebble_pairing_service.c @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include + +// Note: status and MTU changes are handled directly inside the controller (see ble_task.c), +// avoiding a round-trip to the MCU and back. Therefore, these are just stubs. + +void bt_driver_pebble_pairing_service_handle_status_change(const GAPLEConnection *connection) { + // noop +} + +void bt_driver_pebble_pairing_service_handle_gatt_mtu_change(const GAPLEConnection *connection) { + // noop +} diff --git a/src/bluetooth-fw/da1468x/host/reconnect.c b/src/bluetooth-fw/da1468x/host/reconnect.c new file mode 100644 index 00000000..be622c49 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/reconnect.c @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include +#include + +void bt_driver_reconnect_pause(void) { +} + +void bt_driver_reconnect_resume(void) { +} + +void reconnect_set_interval(uint16_t new_interval) { +} + +void bt_driver_reconnect_try_now(bool ignore_paused) { +} + +void bt_driver_reconnect_reset_interval(void) { +} + +void bt_driver_reconnect_notify_platform_bitfield(uint32_t platform_bitfield) { + // Don't care about this info at the moment +} diff --git a/src/bluetooth-fw/da1468x/host/responsiveness.c b/src/bluetooth-fw/da1468x/host/responsiveness.c new file mode 100644 index 00000000..52613fab --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/responsiveness.c @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include "bluetooth/responsiveness.h" +#include "bluetooth/gap_le_connect.h" +#include "hc_protocol/hc_protocol.h" +#include "hc_protocol/hc_endpoint_responsiveness.h" + +#include + +// Will be implemented by PBL-32986 +bool bt_driver_le_connection_parameter_update( + const BTDeviceInternal *addr, const BleConnectionParamsUpdateReq *req) { + return hc_endpoint_responsiveness_request_update(addr, req); +} diff --git a/src/bluetooth-fw/da1468x/host/sniff.c b/src/bluetooth-fw/da1468x/host/sniff.c new file mode 100644 index 00000000..b3300766 --- /dev/null +++ b/src/bluetooth-fw/da1468x/host/sniff.c @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#include + +// FIXME: PBL-21106 +// Figure out what to do for our app_comm_get_sniff_interval() sniff API +uint32_t sys_app_comm_get_sniff_interval(void) { + return 0; +} diff --git a/src/bluetooth-fw/da1468x/include/advert_state.h b/src/bluetooth-fw/da1468x/include/advert_state.h new file mode 100644 index 00000000..71294065 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/advert_state.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +typedef enum { + AdvertState_Off = 0, + AdvertState_Stopping, + AdvertState_Pausing, + AdvertState_Paused, + AdvertState_Running, +} AdvertState; diff --git a/src/bluetooth-fw/da1468x/include/bonding_flags.h b/src/bluetooth-fw/da1468x/include/bonding_flags.h new file mode 100644 index 00000000..a5300c04 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/bonding_flags.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +//! Flags for work-arounds in the BT driver that the upper FW does not need to know about, +//! but that do need to be persisted by bt_persistent_storage_... +typedef enum { + BleBondingFlag_ShouldAutoAcceptRePairing = (1 << 0), + BleBondingFlag_IsReversedPPoGATTEnabled = (1 << 1), + // NOTE: bt_persistent_storage_... uses only 5 bits to store this +} BleBondingFlag; diff --git a/src/bluetooth-fw/da1468x/include/bonding_sync_impl.h b/src/bluetooth-fw/da1468x/include/bonding_sync_impl.h new file mode 100644 index 00000000..bb8fc7b7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/bonding_sync_impl.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +typedef struct BleBonding BleBonding; +typedef struct Connection Connection; + +#include + +//! Host vs controller implementations are different for these: +void bonding_sync_handle_hc_add(const BleBonding *bonding); +void bonding_sync_handle_hc_remove(const BleBonding *bonding); +void bonding_sync_handle_pairing_completed(Connection *connection, uint16_t conn_idx); diff --git a/src/bluetooth-fw/da1468x/include/core_dump.h b/src/bluetooth-fw/da1468x/include/core_dump.h new file mode 100644 index 00000000..2f5759fb --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/core_dump.h @@ -0,0 +1,93 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include "pebbleos/core_dump_structs.h" +#include "util/attributes.h" + +// Host/Controller handshake +static const uint8_t core_dump_connect_ping[] = { 0x5A, 0x67, 0x57, 0xBC, 0x59 }; +static const uint8_t core_dump_connect_response[] = { 0xA5, 0xEA, 0xB8, 0xBE, 0x74 }; + +typedef enum MemoryRegionTag { + MemoryRegionTag_Text, + MemoryRegionTag_VectorTable, + MemoryRegionTag_Heap, + MemoryRegionTag_RwData, + MemoryRegionTag_StackAndBss, + MemoryRegionTag_BleVariables, + MemoryRegionTag_CacheRam, + MemoryRegionTag_RebootReason, + MemoryRegionTagCount +} MemoryRegionTag; + +typedef struct MemoryRegion { + MemoryRegionTag tag; + const void *start; + const uint32_t length; +} MemoryRegion; + +typedef enum { + CoreDumpCmd_GetTextCRC = 0x01, + CoreDumpCmd_GetBuildID = 0x02, + CoreDumpCmd_ReadRegionTable = 0x03, + CoreDumpCmd_ReadMemory = 0x04, + CoreDumpCmd_ReadRunningThreadInfo = 0x05, + CoreDumpCmd_ReadExtraThreadInfo = 0x06, + CoreDumpCmd_LowPowerMode = 0x07, +} CoreDumpCmd_Cmds; + +typedef struct PACKED CoreDumpSPICmd { + uint8_t cmd; + uint16_t len; + void * addr; + uint32_t crc32; +} CoreDumpSPICmd; + +// Controller functions +NORETURN core_dump(bool user_requested); + +// Host functions +void core_dump_and_reset_or_reboot(void); + +// Fault Handler Stack Frame Contains: +// r0, r1, r2, r3, r12, r14, the return address and xPSR +// - Stacked R0 = hf_args[0] +// - Stacked R1 = hf_args[1] +// - Stacked R2 = hf_args[2] +// - Stacked R3 = hf_args[3] +// - Stacked R12 = hf_args[4] +// - Stacked LR = hf_args[5] +// - Stacked PC = hf_args[6] +// - Stacked xPSR= hf_args[7] + +typedef enum { + Stacked_Register_R0 = 0, + Stacked_Register_R1, + Stacked_Register_R2, + Stacked_Register_R3, + Stacked_Register_R12, + Stacked_Register_LR, + Stacked_Register_PC, + Stacked_Register_xPSR, + Stacked_Register_Count, +} Stacked_Register; + +static const uint16_t stacked_regs_size = (Stacked_Register_Count * sizeof(uint32_t)); diff --git a/src/bluetooth-fw/da1468x/include/da1468x_mem_map.h b/src/bluetooth-fw/da1468x/include/da1468x_mem_map.h new file mode 100644 index 00000000..a41335c9 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/da1468x_mem_map.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#define DATA_RAM_BASE_ADDRESS 0x7FC0000 +#define DATA_RAM_SIZE (128 * 1024) + +#define CACHE_RAM_BASE_ADDRESS 0x7FE0000 +#define CACHE_RAM_SIZE (16 * 1024) + +#define OTP_POS_PKG_TIMESTAMP_ADDRESS (0x7F8EA00) +#define OTP_CHIP_ID_ADDRESS (0x7F8EA20) + +// Copy pasta from ble_stack_config.h +#if ((dg_configBLACK_ORCA_IC_REV == BLACK_ORCA_IC_REV_A) \ + && (dg_configBLACK_ORCA_IC_STEP <= BLACK_ORCA_IC_STEP_D)) +# define BLE_VAR_ADDR (0x7FDEC00) +#elif ((dg_configBLACK_ORCA_IC_REV == BLACK_ORCA_IC_REV_A) \ + && (dg_configBLACK_ORCA_IC_STEP == BLACK_ORCA_IC_STEP_E)) +# define BLE_VAR_ADDR (0x7FDC000) +#else +# error "Unsupported chip version" +#endif diff --git a/src/bluetooth-fw/da1468x/include/dialog_chip_id.h b/src/bluetooth-fw/da1468x/include/dialog_chip_id.h new file mode 100644 index 00000000..91a99825 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/dialog_chip_id.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include "util/attributes.h" + +#include + +typedef enum { + DialogChipPackageType_WLCSP53 = 0x00, + DialogChipPackageType_QFN60 = 0xAA, + DialogChipPackageType_KGD = 0x99, + + DialogChipPackageType_Reserved = 0x55, +} DialogChipPackageType; + +//! Timestamp value that corresponds to "1/1/2015 12:00:00 AM", which is a couple months before the +//! the first DA1468x chip was produced. This can be used for sanity checking. +#define DIALOG_CHIP_ID_MIN_TIMESTAMP ((uint32_t)189302400) + +typedef struct PACKED { + //! Info that is programmed by Dialog during the manufacturing process of the chip. + struct PACKED { + uint8_t x_coord; + uint8_t y_coord; + uint8_t wafer_number; + DialogChipPackageType package_type:8; + uint8_t rsvd[4]; // reserved fields, value should == 0 + //! Time in seconds since 1/1/2009 12:00:00 AM + uint32_t timestamp; + } info; + + //! ASCII String containing the type of chip, for example "DA14681". + char chip_id[8]; +} DialogChipID; diff --git a/src/bluetooth-fw/da1468x/include/dialog_utils.h b/src/bluetooth-fw/da1468x/include/dialog_utils.h new file mode 100644 index 00000000..aac2c5d7 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/dialog_utils.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "ble_common.h" +#include + +addr_type_t dialog_utils_local_addr_type_to_dialog(const BTDeviceInternal *address); + +bool dialog_utils_dialog_is_addr_type_random(addr_type_t addr_type); + +void dialog_utils_bd_address_to_bt_device(const bd_address_t *addr, BTDeviceInternal *device_out); + +void dialog_utils_bt_device_to_bd_address(const BTDeviceInternal *device, bd_address_t *addr_out); + +HciStatusCode ble_error_to_hci_status_error(ble_error_t e); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_advert.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_advert.h new file mode 100644 index 00000000..f34b75bc --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_advert.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include "advert_state.h" +#include "hc_protocol/hc_protocol.h" + +// Dialog SDK: +#include "ble_common.h" + +#include + +typedef enum { + HcMessageID_Advert_Enable = 0x01, + HcMessageID_Advert_Disable = 0x02, + HcMessageID_Advert_SetAdvData = 0x03, +} HcMessageID_Advert; + + +typedef struct PACKED HcAdvertEnableData { + uint16_t min_interval_ms; + uint16_t max_interval_ms; +} HcAdvertEnableData; + +typedef struct PACKED HcAdvertEnableResponseData { + ble_error_t error; + AdvertState current_state; +} HcAdvertEnableResponseData; + +void hc_endpoint_advert_handler(const HcProtocolMessage *msg); + +void hc_endpoint_advert_resp_handler(const HcProtocolMessage *msg); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_analytics.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_analytics.h new file mode 100644 index 00000000..9aecd857 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_analytics.h @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include + +#include +#include + +// Must keep these in sync with table in host/hc_endpoint_analytics +typedef enum DialogAnalyticsMetric { + DialogAnalyticMetric_Test, + DialogAnalyticMetric_Count, +} DialogAnalyticsMetric; + +typedef enum { + HcMessageID_Analytics_CollectBLEParameters = 0x01, // Host -> BT Controller + HcMessageID_Analytics_GetConnectionQuality = 0x02, // Host -> BT Controller + HcMessageID_Analytics_LogRebootInfo = 0x03, // BT Controller -> Host + HcMessageID_Analytics_GetHeartbeatData = 0x04, // Host -> BT Controller + HcMessageID_Analytics_GetConnEventStats = 0x05, // Host -> BT Controller + HcMessageID_Analytics_LogBleMicErrorEvent = 0x06, // BT Controller -> Host +} HcMessageID_Analytics; + +typedef struct PACKED HcAnalyticsLogBleMicErrorEvent { + uint32_t num_subsequent_mic_errors; +} HcAnalyticsLogBleMicErrorEvent; + +typedef struct PACKED HcAnalyticsCollectBleParameters { + LEChannelMap le_chan_map_res; + bool success; +} HcAnalyticsCollectBleParameters; + +typedef struct PACKED HcAnalyticsGetConnectionQuality { + int8_t rssi; + bool success; +} HcAnalyticsGetConnectionQuality; + +typedef struct PACKED HcAnalyticsRebootInfo { + uint8_t build_id[BUILD_ID_EXPECTED_LEN]; + uint32_t last_crash_lr; + uint32_t reboot_reason_code; +} HcAnalyticsRebootInfo; + +typedef struct SerializedAnalytic { + uint32_t metric; + uint32_t value; +} SerializedAnalytic; + +typedef struct PACKED HcAnalyticsHeartbeatData { + uint32_t count; + SerializedAnalytic analytics[]; +} HcAnalyticsHeartbeatData; + +//! Host functions + +void hc_endpoint_analytics_host_handler(const HcProtocolMessage *msg); + +bool hc_endpoint_analytics_collect_ble_parameters(LEChannelMap *le_chan_map_res); + +bool hc_endpoint_analytics_get_connection_quality(const BTDeviceInternal *device, int8_t *rssi_out); + +void hc_endpoint_analytics_collect_heartbeat_data(void); + +//! Controller Functions + +void hc_endpoint_analytics_ctlr_handler(const HcProtocolMessage *msg); + +//! Host -> BT Controller +bool hc_endpoint_analytics_get_conn_event_stats(SlaveConnEventStats *stats); + +//! BT Controller -> Host +void hc_endpoint_analytics_send_reboot_info(void); +void hc_endpoint_analytics_log_mic_error_detected(uint32_t num_subsequent_mic_errors); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_bonding_sync.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_bonding_sync.h new file mode 100644 index 00000000..1307e285 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_bonding_sync.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include + +typedef enum { + HcMessageID_BondingSync_AddBonding = 0x01, + HcMessageID_BondingSync_RemoveBonding = 0x02, +} HcMessageID_BondingSync; + +void hc_endpoint_bonding_sync_handler(const HcProtocolMessage *msg); + +//! Host => Controller and Controller => Host +void hc_endpoint_bonding_sync_add(const BleBonding *bonding); + +//! Host => Controller +void hc_endpoint_bonding_sync_remove(const BleBonding *bonding); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_chip_id.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_chip_id.h new file mode 100644 index 00000000..ef085772 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_chip_id.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +#include "dialog_chip_id.h" +#include "hc_protocol/hc_protocol.h" + +#include + +typedef enum { + HcMessageID_Id_ChipInfo = 0x01, +} HcMessageID_Id; + +void hc_endpoint_chip_id_handler(const HcProtocolMessage *msg); + +typedef void (*HcEndpointIdResponseHandler)(const DialogChipID *chip_id); + +void hc_endpoint_chip_id_send_request(HcEndpointIdResponseHandler *response_handler); + +//! @return False if the request failed. +bool hc_endpoint_chip_id_query_chip_info(DialogChipID *chip_id_out); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_ctl.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_ctl.h new file mode 100644 index 00000000..db64b33f --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_ctl.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include + +typedef enum { + HcMessageID_Ctl_Init = 0x01, + HcMessageID_Ctl_Shutdown = 0x02, +} HcMessageID_Ctl; + +void hc_endpoint_ctl_handler(const HcProtocolMessage *msg); + +//! Sends the init command and blocks until ack has been received or timeout is hit. +bool hc_endpoint_ctl_init_sync(const BTDriverConfig *config); + +//! Sends the shutdown command and blocks until ack has been received or timeout is hit. +bool hc_endpoint_ctl_shutdown_sync(void); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_discovery.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_discovery.h new file mode 100644 index 00000000..b6349079 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_discovery.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include +#include + +#include + +typedef enum { + HcMessageID_Discovery_Start = 0x01, // Host -> BT Controller + HcMessageID_Discovery_Stop = 0x02, // Host -> BT Controller + HcMessageID_Discovery_Service_Found = 0x03, // BT Controller -> Host + HcMessageID_Discovery_Complete = 0x04, // BT Controller -> Host + HcMessageID_Discovery_Service_Changed_Handle = 0x05 // BT Controller -> Host +} HcMessageID_Discovery; + +typedef struct PACKED HcProtocolDiscoveryStartPayload { + BTDeviceInternal address; + ATTHandleRange range; +} HcProtocolDiscoveryStartPayload; + +typedef struct PACKED HcProtocolDiscoveryServiceFoundPayload { + BTDeviceInternal address; + GATTService service; +} HcProtocolDiscoveryServiceFoundPayload; + +typedef struct PACKED HcProtocolDiscoveryCompletePayload { + BTDeviceInternal address; + HciStatusCode status; +} HcProtocolDiscoveryCompletePayload; + +typedef struct PACKED HcProtocolDiscoveryServiceChangedHandlePayload { + BTDeviceInternal address; + uint16_t handle; +} HcProtocolDiscoveryServiceChangedHandlePayload; + +void hc_endpoint_discovery_handler(const HcProtocolMessage *msg); + +//! Host -> BT Controller +bool hc_endpoint_discovery_start(const ATTHandleRange *range, const BTDeviceInternal *address); +bool hc_endpoint_discovery_stop(const BTDeviceInternal *address); + +//! BT Controller -> Host +void hc_endpoint_discovery_send_service_found( + const HcProtocolDiscoveryServiceFoundPayload *payload, uint32_t payload_size); +void hc_endpoint_discovery_complete(const BTDeviceInternal *address, HciStatusCode status); +void hc_endpoint_discovery_service_changed_handle(const BTDeviceInternal *address, uint16_t handle); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gap_le_connect.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gap_le_connect.h new file mode 100644 index 00000000..d7f6a677 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gap_le_connect.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" +#include "util/attributes.h" + +#include + +typedef enum { + HcMessageID_GapLEConnect_ConnectionComplete = 0x1, // Controller -> Host + HcMessageID_GapLEConnect_DisconnectionComplete = 0x2, // Controller -> Host + HcMessageID_GapLEConnect_EncryptionChange = 0x3, // Controller -> Host + HcMessageID_GapLEConnect_UpdateAddressAndIRK = 0x4, // Controller -> Host + HcMessageID_GapLEConnect_Disconnect = 0x5, // Host -> Controller + HcMessageID_GapLEConnect_PeerVersionInfo = 0x6, // Controller -> Host +} HcMessageID_GapLEConnect; + +typedef struct PACKED HcGapLeConnectionData { + BleConnectionCompleteEvent connection_complete_event; + uint16_t mtu; +} HcGapLeConnectionData; + +// On the Main MCU and handles messages sent by the Dialog chip +void hc_endpoint_gap_le_connect_handler(const HcProtocolMessage *msg); + +//! Controller -> Host +void hc_endpoint_gap_le_connect_send_connection_complete(HcGapLeConnectionData *e); +void hc_endpoint_gap_le_connect_send_disconnection_complete(BleDisconnectionCompleteEvent *e); +void hc_endpoint_gap_le_connect_send_encryption_changed(BleEncryptionChange *e); +void hc_endpoint_gap_le_connect_send_address_and_irk_changed(BleAddressAndIRKChange *e); +void hc_endpoint_gap_le_connect_send_peer_version_info(BleRemoteVersionInfoReceivedEvent *e); + +//! Host -> Controller +int hc_endpoint_gap_le_disconnect(const BTDeviceInternal *address); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gap_service.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gap_service.h new file mode 100644 index 00000000..b3d53754 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gap_service.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include "util/attributes.h" + +#include + +typedef struct ble_evt_gattc_read_completed_t ble_evt_gattc_read_completed_t; +typedef struct Connection Connection; + +typedef enum { + HcMessageID_GapService_SetName = 0x1, + HcMessageId_GapService_MtuChanged = 0x2, + HcMessageID_GapService_DeviceNameRequest = 0x3, + HcMessageID_GapService_DeviceNameRequest_All = 0x4, + HcMessageID_GapService_SetLocalAddress = 0x5, + HcMessageID_GapService_GeneratePrivateResolvable_address = 0x6, +} HcMessageID_GapService; + +typedef struct PACKED HcProtocol_GapServiceMtuChanged { + BTDeviceInternal addr; + uint16_t mtu; +} HcProtocol_GapServiceMtuChanged; + +typedef struct PACKED HcProtocol_GapDeviceNameResponseHeader { + BTDeviceInternal addr; + uint8_t name_length; + uint8_t name[]; +} HcProtocol_GapDeviceNameResponseHeader; + +typedef struct PACKED HcProtocol_GapServiceSetLocalAddress { + bool allow_cycling; + BTDeviceAddress pinned_addr; +} HcProtocol_GapServiceSetLocalAddress; + +typedef struct PACKED HcProtocol_GapServiceGeneratePrivateResolvableAddressResponse { + BTDeviceAddress address; +} HcProtocol_GapServiceGeneratePrivateResolvableAddressResponse; + +void hc_endpoint_gap_service_handler(const HcProtocolMessage *msg); +void hc_endpoint_gap_service_resp_handler(const HcProtocolMessage *msg); + +void hc_endpoint_gap_service_mtu_changed(const Connection *connection, uint16_t mtu); +void hc_endpoint_gap_service_device_name_read(const ble_evt_gattc_read_completed_t *evt); + +void hc_endpoint_gap_service_set_dev_name(const char *name); +void hc_endpoint_gap_service_device_name_request(const BTDeviceInternal *addr); +void hc_endpoint_gap_service_device_name_request_all(void); + +void hc_endpoint_gap_service_set_local_address(bool allow_cycling, + const BTDeviceAddress *pinned_address); + +bool hc_endpoint_gap_service_generate_private_resolvable_address(BTDeviceAddress *address_out); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gatt.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gatt.h new file mode 100644 index 00000000..7fb9e0a8 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_gatt.h @@ -0,0 +1,108 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include + +#include +#include +#include + +typedef enum { + HcMessageID_Gatt_Read = 0x01, + HcMessageID_Gatt_ReadCompleted = 0x02, + HcMessageID_Gatt_Write = 0x03, + HcMessageID_Gatt_WriteNoResponse = 0x04, + HcMessageID_Gatt_WriteCompleted = 0x05, + HcMessageID_Gatt_Notification = 0x06, + HcMessageID_Gatt_Indication = 0x07, +} HcMessageID_Gatt; + +typedef struct PACKED HcGattHdr { + BTDeviceInternal addr; +} HcGattHdr; + +#ifndef __clang__ +_Static_assert(sizeof(HcGattHdr) == 8, "HcGattHdr is not 8 bytes in size"); +#endif + +typedef struct PACKED HcGattReadData { + HcGattHdr hdr; + uintptr_t context_ref; + uint16_t att_handle; +} HcGattReadData; + +typedef struct PACKED HcGattWriteData { + HcGattHdr hdr; + uintptr_t context_ref; + uint16_t att_handle; + uint16_t value_length; + uint8_t value[]; +} HcGattWriteData; + +typedef struct PACKED HcGattReadRespData { + HcGattHdr hdr; + uintptr_t context_ref; + BLEGATTError status; + uint16_t att_handle; + uint16_t value_length; + uint8_t value[]; +} HcGattReadRespData; + +typedef struct PACKED HcGattWriteRespData { + HcGattHdr hdr; + uintptr_t context_ref; + BLEGATTError status; + uint16_t att_handle; +} HcGattWriteRespData; + +typedef struct PACKED HcGattNotifIndicData { + HcGattHdr hdr; + uint16_t att_handle; + uint16_t value_length; + uint8_t value[]; +} HcGattNotifIndicData; + +// Handler on Host side +void hc_endpoint_gatt_handler_host(const HcProtocolMessage *msg); + +// Handler on BT Controller side +void hc_endpoint_gatt_handler_controller(const HcProtocolMessage *msg); + +// Functions called by Host +BTErrno hc_endpoint_gatt_read(const BTDeviceInternal *device, uint16_t att_handle, void *context); + +BTErrno hc_endpoint_gatt_write(const BTDeviceInternal *device, uint16_t att_handle, + const uint8_t *value, uint16_t value_length, bool resp_required, + void *context); + +// Functions called by BT Controller +void hc_endpoint_gatt_send_read_complete(const BTDeviceInternal *device, uint16_t handle, + BLEGATTError status, uint16_t value_length, const uint8_t *value, uintptr_t context_ref); + +void hc_endpoint_gatt_send_write_complete(const BTDeviceInternal *device, uint16_t handle, + BLEGATTError status, uintptr_t context_ref); + +void hc_endpoint_gatt_send_notification(const BTDeviceInternal *device, uint16_t handle, + uint16_t value_length, const uint8_t *value); + +void hc_endpoint_gatt_send_indication(const BTDeviceInternal *device, uint16_t handle, + uint16_t value_length, const uint8_t *value); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_hci.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_hci.h new file mode 100644 index 00000000..0c3acb36 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_hci.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +typedef enum { + HcMessageID_Hci_Cmd = 0x01, + HcMessageID_Hci_Evt = 0x04, +} HcMessageID_Hci; + +void hc_endpoint_hci_handler(const HcProtocolMessage *msg); +//! Controller -> Main MCU (Host) +void hc_endpoint_enqueue_hci_evt(const uint8_t *hci_evt_buf, uint8_t payload_len); +//! Main MCU (Host) -> Controller +void hc_endpoint_enqueue_hci_cmd(const uint8_t *hci_buf, uint8_t payload_len); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_hrm.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_hrm.h new file mode 100644 index 00000000..44d72ffe --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_hrm.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include + +#include +#include + +typedef enum { + HcMessageID_HRM_UpdateSubscription = 0x1, + HcMessageID_HRM_Measurement = 0x2, + HcMessageID_HRM_Enable = 0x3, +} HcMessageID_HRM; + +typedef struct PACKED { + BTDeviceInternal device; + bool is_subscribed; +} HcHrmSubscription; + +typedef struct PACKED { + uint16_t bpm; + bool is_on_wrist:1; + + //! Array of devices (that have been granted access) to which the update should be sent: + uint32_t num_devices; + BTDeviceInternal devices[]; +} HcHrmMeasurement; + +typedef struct PACKED { + bool enable:1; +} HcHrmEnableCmd; + +void hc_endpoint_hrm_handler(const HcProtocolMessage *msg); + +//! Host -> Controller +void hc_endpoint_hrm_send_measurement(const BleHrmServiceMeasurement *measurement, + const BTDeviceInternal *permitted_devices, + size_t num_permitted_devices); + +//! Host -> Controller +void hc_endpoint_hrm_enable(bool enable); + +//! Controller -> Host +void hc_endpoint_hrm_update_subscription(const HcHrmSubscription *subscription); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_logging.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_logging.h new file mode 100644 index 00000000..fcb66a13 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_logging.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +typedef enum { + HcMessageID_Logging_LogMsg = 0x1, + HcMessageID_Logging_SetLevel = 0x2, + HcMessageID_Logging_GetLevel = 0x3, +} HcMessageID_LoggingService; + +void hc_endpoint_logging_handler(const HcProtocolMessage *msg); + +//! Host -> Controller +void hc_endpoint_logging_set_level(uint8_t level); +bool hc_endpoint_logging_get_level(uint8_t *level); + +//! Controller -> Host +// The caller must have crafted the HcProtocolMessage + payload correctly +// Returns: true, if sent or if host_transport isn't yet available. False, otherwise. +bool hc_endpoint_logging_send_msg(HcProtocolMessage *msg); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_pairing.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_pairing.h new file mode 100644 index 00000000..749b5c57 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_pairing.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include + +#include + +typedef enum { + HcMessageID_Pairing_PairingRequest = 0x01, + HcMessageID_Pairing_PairingResponse = 0x02, + HcMessageID_Pairing_PairingComplete = 0x03, +} HcMessageID_Pairing; + +typedef struct PACKED { + BTDeviceInternal device; +} HcProtocolMessagePairingRequestPayload; + +typedef struct PACKED { + BTDeviceInternal device; + bool is_confirmed:1; + uint8_t rsvd:7; +} HcProtocolMessagePairingResponsePayload; + +typedef struct PACKED { + BTDeviceInternal device; + HciStatusCode status; +} HcProtocolMessagePairingCompletePayload; + +void hc_endpoint_pairing_handler(const HcProtocolMessage *msg); + +//! Controller => Host +void hc_endpoint_pairing_send_pairing_request(const BTDeviceInternal *device); + +//! Host => Controller +void hc_endpoint_pairing_send_pairing_response(const BTDeviceInternal *device, bool is_confirmed); + +//! Host => Controller +void hc_endpoint_pairing_send_pairing_complete(const BTDeviceInternal *device, + HciStatusCode status); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_pebble_pairing_service.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_pebble_pairing_service.h new file mode 100644 index 00000000..726f7a40 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_pebble_pairing_service.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include +#include + +typedef struct Connection Connection; + +typedef enum { + HcMessageID_PebblePairingServiceiOSAppTerminationDetected = 0x01, + HcMessageID_PebblePairingServiceFoundGateway = 0x02, + HcMessageID_PebblePairingServiceConnParams = 0x03, +} HcMessageID_PebblePairingService; + +typedef struct PACKED { + BTDeviceInternal device; + PebblePairingServiceConnParamsWrite conn_params; +} HcPpsConnParamsPayload; + +void hc_endpoint_pebble_pairing_service_handler(const HcProtocolMessage *msg); + +//! Host => Controller +void hc_endpoint_pebble_pairing_service_send_ios_app_termination_detected(void); + +void hc_endpoint_pebble_pairing_service_found_gateway(BTDeviceInternal *device); + +void hc_endpoint_pebble_pairing_service_send_conn_params(const Connection *connection, + const PebblePairingServiceConnParamsWrite *params, size_t params_length); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_responsiveness.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_responsiveness.h new file mode 100644 index 00000000..bbb52a24 --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_responsiveness.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +#include "bluetooth/responsiveness.h" +#include "hc_protocol/hc_protocol.h" + +#include "util/attributes.h" + +typedef enum { + HcMessageID_Id_ConnParamUpdateReq = 0x01, + HcMessageID_Id_ConnParamUpdateResponse = 0x02, +} HcMessageID_Responsiveness; + +typedef struct PACKED { + BTDeviceInternal address; + BleConnectionParamsUpdateReq params; +} HcProtocolMessageResponsivenessPayload; + +void hc_endpoint_responsiveness_handler(const HcProtocolMessage *msg); + +//! Host -> BT Controller +bool hc_endpoint_responsiveness_request_update( + const BTDeviceInternal *addr, const BleConnectionParamsUpdateReq *params); + +//! BT Controller -> Host +void hc_endpoint_responsiveness_notify_update( + const BleConnectionParams *params, const BTDeviceInternal *addr, HciStatusCode status); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_test.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_test.h new file mode 100644 index 00000000..446b9b7b --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_endpoint_test.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include "hc_protocol/hc_protocol.h" + +#include +#include + +#include + +typedef enum { + HcMessageID_Test_Config_PA = 0x01, + HcMessageID_Test_UnmodulatedTxStart = 0x02, + HcMessageID_Test_UnmodulatedTxStop = 0x03, + HcMessageID_Test_Core_Dump = 0x04, + HCMessageID_Test_Sleep = 0x5, +} HcMessageID_Test; + +typedef struct PACKED HcTestSleep { + bool force_sleep; +} HcTestSleep; + +typedef struct PACKED HcTestUnmodTxStart { + uint8_t tx_channel; +} HcTestUnmodTxStart; + +// Handler on BT Controller side +void hc_endpoint_test_handler(const HcProtocolMessage *msg); + +// Host -> Controller +void hc_endpoint_test_unmodulated_tx_test_start(uint8_t tx_channel); +void hc_endpoint_test_unmodulated_tx_test_stop(void); diff --git a/src/bluetooth-fw/da1468x/include/hc_protocol/hc_protocol.h b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_protocol.h new file mode 100644 index 00000000..bb6f773a --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/hc_protocol/hc_protocol.h @@ -0,0 +1,148 @@ +/* + * 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. + */ + +#pragma once + +#include "util/attributes.h" +#include "util/circular_buffer.h" + +#include +#include +#include + +typedef enum { + // - Please don't shuffle values to keep debugging simple. + // - Please use consequtive values to avoid "gaps" in the table. + HcEndpointID_Invalid = 0x00, + HcEndpointID_Ctl = 0x01, + HcEndpointID_Hci = 0x02, + HcEndpointID_GapService = 0x03, + HcEndpointID_Id = 0x04, + HcEndpointID_Advert = 0x05, + HcEndpointID_Responsiveness = 0x06, + HcEndpointID_GapLEConnect = 0x07, + HcEndpointID_Gatt = 0x08, + HcEndpointID_Discovery = 0x09, + HcEndpointID_BondingSync = 0x0a, + HcEndpointID_PebblePairingService = 0x0b, + HcEndpointID_Pairing = 0x0c, + HcEndpointID_Analytics = 0x0d, + HcEndpointID_Logging = 0x0e, + HcEndpointID_Test = 0x0f, + HcEndpointID_HRM = 0x10, + HcEndpointIDCount, +} HcEndpointID; + +typedef uint8_t HcCommandID; + +#define HC_PROTOCOL_SN_BIT_WIDTH (7) + +typedef struct PACKED HcProtocolMessage { + uint16_t message_length; //!< Total message length including header, little-endian + HcEndpointID endpoint_id:8; + HcCommandID command_id:8; + struct PACKED { + uint8_t sn:HC_PROTOCOL_SN_BIT_WIDTH; + bool is_response:1; //!< If the MSBit of the transaction_id byte is set, it's a response. + } transaction_id; //!< Automatically assigned by hc_protocol_enqueue...() + uint8_t rsvd[3]; //! keep the payload 4 byte aligned + uint8_t payload[]; +} HcProtocolMessage; + +_Static_assert((sizeof(HcProtocolMessage) % 4) == 0, "HcProtocolMessage not 4-byte multiple"); + +#define HC_PROTOCOL_DEFAULT_RESPONSE_TIMEOUT_MS (1000) +// We need the control endpoint requests to succeed for BT to be in a good state so give them a +// little more time than a typical endpoint +#define HC_PROTOCOL_DEFAULT_CTL_ENDPOINT_RESPONSE_TIMEOUT_MS \ + (HC_PROTOCOL_DEFAULT_RESPONSE_TIMEOUT_MS + 1000) + +typedef void (*HcProtocolMessageHandler)(const HcProtocolMessage *msg); + +//! Enqueues a message with the current host transport. +//! @param message The message to enqueue. +//! @return True if the message was successfully enqueued. +//! @note This function will automatically assign the `transaction_id`. +bool hc_protocol_enqueue(HcProtocolMessage *message); + +//! Enqueues a message with the current host transport by creating a message and copying the +//! specified payload into the message. +//! @return True if the message was successfully enqueued. +//! @note This heap-allocates a buffer (and frees it before returning). If you need to send a large +//! payload and want to avoid this additional allocate-and-copy, consider using hc_protocol_enqueue. +//! @note This function will automatically assign the `transaction_id`. +bool hc_protocol_enqueue_with_payload(HcEndpointID endpoint_id, HcCommandID command_id, + const uint8_t *request_payload, + size_t request_payload_length); + +#if !BT_CONTROLLER_BUILD // Not currently supported for Controller->Host messaging +//! Enqueues a message with the current host transport and blocks until a response message has been +//! received, that matches the `transaction_id` of the request message. If a message was not +//! received within the protocol timeout, NULL will be returned. +//! @param request_message The message with the request. +//! @return Heap-allocated message or NULL if the timeout was hit. The caller of this function is +//! responsible for calling kernel_free() to avoid leaking the memory. +//! @note This function will automatically assign the `transaction_id`. +HcProtocolMessage *hc_protocol_enqueue_and_expect(HcProtocolMessage *request_message); + +//! Same as hc_protocol_enqueue_and_expect, but creates a request message and copies the specified +//! payload into the request message. +//! @note This function will automatically assign the `transaction_id`. +HcProtocolMessage *hc_protocol_enqueue_with_payload_and_expect(HcEndpointID endpoint_id, + HcCommandID command_id, + const uint8_t *request_payload, + size_t request_payload_length); +#endif + +//! Enqueues a response to previously received request. +//! @param to_request The request message being responded to. +//! @param response_payload The response payload +//! @param payload_length The length of response_payload in bytes +//! @return True if the message was successfully enqueued. +//! @note This function will automatically assign the correct `transaction_id` to match with +//! to_request. +bool hc_protocol_enqueue_response(const HcProtocolMessage *to_request, + const uint8_t *response_payload, size_t response_payload_length); + +//! Called by the host transport every time new data got written into the receive buffer. +//! This function will go through the data in the receive buffer as returned by +//! host_transport_rx_read() and call the endpoint handler for each message. After each message is +//! handled, this function will call host_transport_rx_consume() for the handled length. The message +//! will also be free'd if needed by this function. If a message has not been received completely, +//! the endpoint handler will not yet be called. +void hc_protocol_process_receive_buffer(void); + +//! Called when the buffer has added extra free space in its buffer so that the client +//! can try to enqueue data if it had previously failed. +void hc_protocol_buffer_gained_space(void); + +//! Must be implemented by user of HC Trans. Called every time an asynchronous message +//! is received from the other side. +//! @param handler The callback to invoke +//! @param message The message to pass to the invoked callback +//! @param[in/out] should_free For input, indicates whether the buffer has been allocated. +//! As output, set to False to prevent the callee from freeing message +extern void hc_protocol_cb_dispatch_handler( + const HcProtocolMessageHandler handler, HcProtocolMessage *message, bool *should_free); + +//! Should be called only once at boot. +void hc_protocol_boot(void); + +//! Should be called when stack is brought up. +void hc_protocol_init(void); + +//! Should be called when stack is torn down. +void hc_protocol_deinit(void); diff --git a/src/bluetooth-fw/da1468x/include/host_transport.h b/src/bluetooth-fw/da1468x/include/host_transport.h new file mode 100644 index 00000000..affc159e --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/host_transport.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +//! @file host_transport.h Common interfaces (shared between host and controller side FWs) + +#include +#include +#include + +typedef enum HostTransportEnqueueStatus { + HostTransportEnqueueStatus_Success, + // The stack is not initialized yet or has crashed and is about to be reset + HostTransportEnqueueStatus_Failure, + // Host Transport buffers are full. In a few they should be drained so retry then + HostTransportEnqueueStatus_RetryLater +} HostTransportEnqueueStatus; + +HostTransportEnqueueStatus host_transport_tx_enqueue(const uint8_t *data, size_t length); + +size_t host_transport_rx_get_length(void); + +//! @return True if the caller must kernel_free(*data_ptr_out) at some point. +bool host_transport_rx_read(uint8_t **data_ptr_out, size_t length); + +void host_transport_rx_consume(size_t length); + +bool host_transport_is_current_task_host_transport_task(void); diff --git a/src/bluetooth-fw/da1468x/include/host_transport_protocol.h b/src/bluetooth-fw/da1468x/include/host_transport_protocol.h new file mode 100644 index 00000000..78aba46f --- /dev/null +++ b/src/bluetooth-fw/da1468x/include/host_transport_protocol.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include "util/attributes.h" + +#include + +// Design doc: +// https://docs.google.com/document/d/1or2Ygs3sWt_5XNW_Mpe3Vxmhwuh3DTzdgZr6QlEe7iQ/edit# + +// TODO: Arbitrary for values. At some point we should evaluate if these sizes +// are too big or small, see PBL-36239 +#define HOST_TRANSPORT_HOST_RX_BUFFER_SIZE (2048) +#define HOST_TRANSPORT_HOST_TX_BUFFER_SIZE (2048) +#define HOST_TRANSPORT_CTLR_RX_BUFFER_SIZE (1024) +#define HOST_TRANSPORT_CTLR_TX_BUFFER_SIZE (1024) + +typedef enum { + SPITransportMsgID_Status = 0x88, +} SPITransportMsgID; + +typedef struct PACKED SPITransportMsgStatus { + SPITransportMsgID msg_id:8; + uint16_t bytes_sendable_count; + uint16_t bytes_receivable_count; + uint32_t crc; +} SPITransportMsgStatus; + +typedef struct SPITransportMsgFooter { + uint32_t crc; +} SPITransportMsgFooter; diff --git a/src/bluetooth-fw/da1468x/load.sh b/src/bluetooth-fw/da1468x/load.sh new file mode 100755 index 00000000..9c186c1f --- /dev/null +++ b/src/bluetooth-fw/da1468x/load.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# 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. + +if [ "$1" == "main" ]; then + ELF_PATH=../../../build/src/bluetooth-fw/DA1468x/main/bt_da14681_main.elf +else + ELF_PATH=../../../build/src/bluetooth-fw/DA1468x/boot/bt_da14681_boot.elf +fi + +# We need to update the SYS_CTRL_REG in order to run from RAM and be able to debug: +# SYS_CTRL_REG (0x50000012): Size 16 bits, Reset Value: 0xb0010.0000, Program Value: 0b1010.1011 +# REMAP_ADR0[2:0] - 0x3 - Execute from RAMS +# REMAP_RAMS[4:3] - 0x1 - DataRAM2, DataRAM1, DataRAM3 (Sequence of 3 first DataRAMs in memory space) +# PAD_LATCH_EN[5:5] - 0x1 +# OTPC_RESET_REQ[6:6] - 0x0 +# DEBUGGER_ENABLE[7:7] - 0x1 +# Remaining 8 bits we just use default for now +# +# Note: The dialog example scripts also issue a "SWD_RESET_REG" by writing "mww 0x400C3050 1" +# but this seems to jam up openocd so don't do it for now + +openocd -f openocd.cfg -c "init; reset halt; mwh 0x50000012 0xAB; load_image $ELF_PATH; reset; shutdown;" diff --git a/src/bluetooth-fw/da1468x/openocd.cfg b/src/bluetooth-fw/da1468x/openocd.cfg new file mode 100644 index 00000000..92d2e446 --- /dev/null +++ b/src/bluetooth-fw/da1468x/openocd.cfg @@ -0,0 +1,54 @@ +interface ftdi +ftdi_vid_pid 0x0403 0x7892 +ftdi_channel 0 + +# Signal lines: +# 0x0001: SWCLK +# 0x0002: SWDOUT +# 0x0004: SWDIN +# 0x0040: RST (active high) + +ftdi_layout_init 0x0000 0x0043 +ftdi_layout_signal SWD_EN -data 0 -oe 0 +ftdi_layout_signal SWDIO_OE -data 0 -oe 0 + +ftdi_layout_signal nSRST -ndata 0x0040 +reset_config srst_push_pull + +# After asserting SRST, the SWD of the DA14681 takes a bit of time to become +# responsive again, so wait a little bit: +adapter_nsrst_delay 50 + +transport select swd +source [find target/swj-dp.tcl] + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME DA1468x +} + +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + set _CPUTAPID 0x0BB11477 +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -expected-id $_CPUTAPID + +set _TARGETNAME $_CHIPNAME.cpu + +target create $_TARGETNAME cortex_m -chain-position $_TARGETNAME -rtos FreeRTOS + +adapter_khz 1000 + +cortex_m reset_config sysresetreq + +$_TARGETNAME configure -event gdb-attach { + echo "Halting target because GDB is attaching..." + halt +} +$_TARGETNAME configure -event gdb-detach { + echo "Resuming target because GDB is detaching..." + resume +} diff --git a/src/bluetooth-fw/da1468x/wscript b/src/bluetooth-fw/da1468x/wscript new file mode 100644 index 00000000..ca5aac3b --- /dev/null +++ b/src/bluetooth-fw/da1468x/wscript @@ -0,0 +1,191 @@ +import copy +import os +from waflib.ConfigSet import ConfigSet +from waflib.Configure import conf +import waflib.Logs + +from elftools.elf.elffile import ELFFile + + +@conf +def get_bluetooth_fw_storage(ctx): + from resources.types.resource_definition import StorageType + if ctx.env.BT_FW_BUILTIN: + return StorageType.builtin + else: + is_prf = (ctx.variant == 'prf') + return StorageType.builtin if is_prf else StorageType.pbpack + + +@conf +def get_dialog_ic_step_define(ctx): + chip_rev_step = None + if ctx.env.bt_controller == 'da14681-00': + chip_rev_step = 'D' + elif ctx.env.bt_controller == 'da14681-01': + chip_rev_step = 'E' + else: + raise Exeception('Unknown bt_controller %s' % ctx.env.bt_controller) + return ('dg_configBLACK_ORCA_IC_STEP=BLACK_ORCA_IC_STEP_%s' % chip_rev_step) + +def _recurse(ctx): + ctx.recurse("controller/boot") + ctx.recurse("controller/main") + + +def _export_dialog_sdk_includes(bld): + sdk_node = bld.path.find_node('vendor/bt-dialog-sdk') + sdk_headers = sdk_node.ant_glob('sdk/bsp/**/include/*.h ' + 'sdk/bsp/config/*.h ' + 'sdk/bsp/free_rtos/portable/**/*.h ' + 'sdk/bsp/osal/*.h ' + 'sdk/interfaces/ble/**/*.h ' + 'sdk/interfaces/ble_services/include/*.h ', + excl='sdk/interfaces/ble/include/util/* ' + 'sdk/bsp/system/loaders/**') + sdk_includes = set([h.parent.abspath() for h in sdk_headers]) + bld(export_includes=sdk_includes, name='dialog_sdk_includes') + + +def options(opt): + opt.add_option('--bt_fw_builtin', action='store_true', default=False, + help='Store BT firmware images into `built-in` resources ' + 'instead of system resources (skip image_resources).') + + +def handle_configure_options(conf): + conf.env.BT_FW_BUILTIN = conf.options.bt_fw_builtin + conf.env.bt_controller = conf.options.bt_controller + # insert more here in the future + +def configure(conf): + # For DA14681, derive a new environment from 'cortex-m0': + conf.setenv('da14681x', conf.all_envs['cortex-m0']) + + # Handle configure options + handle_configure_options(conf) + + # Configure optimization: + is_optimized = True + optimize_flag = '-Os' if is_optimized else '-O0' + conf.env.append_unique('CFLAGS', [optimize_flag]) + if is_optimized: + # This define controls timing values that are different depending + # on the optimization level. See for example jump_table.c and + # ble_config.c in the Dialog SDK. + conf.env.append_unique('DEFINES', ['RELEASE_BUILD']) + + if conf.is_silk(): + platform = 'SILK' + elif conf.is_cutts(): + platform = 'CALCULUS' + elif conf.is_robert(): + platform = 'ROBERT' + else: + conf.fatal('No platform specified for {}!'.format(conf.options.board)) + + conf.env.append_unique('DEFINES', [ + 'PLATFORM_' + platform + '=1' + ]) + + conf.env.append_unique('DEFINES', [ + 'BOARD_' + conf.options.board.upper() + '=1' + ]) + conf.env.append_unique('DEFINES', ['BT_CONTROLLER_BUILD=1']) + conf.env.BOARD = conf.options.board + + conf.env.append_unique('DEFINES', [ + conf.get_dialog_ic_step_define() + ]) + + conf.load('c_inject_include_files') + conf.load('c_preprocessor') + + _recurse(conf) + + +def build(bld): + if 'da14681x' not in bld.all_envs: + bld.fatal("Whoops! You need to ./waf configure again...") + + # Build host "BT Driver" library: + sources = bld.path.ant_glob('host/**/*.c common/**/*.c') + bld.stlib(source=sources, + target='bt_driver', + includes=['host', 'include'], + defines=['FILE_LOG_COLOR=LOG_COLOR_BLUE'], + use=[ + # Note: Dialog SDK also includes a "free_rtos/includes/list.h" header. + # Put 'fw_includes' before 'dialog_sdk_includes' to + # make sure libutil's "util/list.h" gets picked first: + 'fw_includes', + 'freertos', + 'root_includes', + 'dialog_sdk_includes', + 'driver_dma', + ]) + + # Build DA14681 firmwares: + bld.env = bld.all_envs['da14681x'] + if bld.variant == 'prf': + bld.env.append_unique('DEFINES', ['RECOVERY_FW=1']) + _export_dialog_sdk_includes(bld) + bld.recurse("controller/board") + _recurse(bld) + + bld.add_post_fun(size_dialog_fw) + +def size_dialog_fw(ctx): + dialog_bin = ctx.get_bluetooth_fw_node() + dialog_elf = dialog_bin.change_ext('.elf') + + if dialog_elf is None: + ctx.fatal('No dialog ELF found for size') + + sections = get_elf_sections(dialog_elf.abspath()) + + # Sum the sections: + TEXT_SECTIONS = ['.text', '.text_crc32'] + # .ARM.exidx only exists if synthetic instructions (i.e long divide) are pulled in + DATA_SECTIONS = ['.vt_stash_region', '.note.gnu.build-id', '.zero.table', '.data', '.ARM.exidx', + 'retention_mem_uninit', '.debug_region'] + BSS_SECTIONS = ['.stack_dummy', '.bss', '.log_buffer'] + + text = sum([size for addr, size in [sections[x] for x in TEXT_SECTIONS]]) + data = sum([size for addr, size in [sections[x] for x in DATA_SECTIONS if x in sections]]) + bss = sum([size for addr, size in [sections[x] for x in BSS_SECTIONS]]) + + total = text + data + bss + output = ('{:>7} {:>7} {:>7} {:>7} {:>7} filename\n' + '{:7} {:7} {:7} {:7} {:7x} bt_da14861_main.elf'. + format('text', 'data', 'bss', 'dec', 'hex', text, data, bss, total, total)) + waflib.Logs.pprint('BLUE', '\n' + output) + + # Calculate free RAM (delta between RETENTION_BLE and .log_buffer) + log_buffer_addr, log_buffer_size = sections['.log_buffer'] + retention_addr = sections['RETENTION_BLE'][0] + log_buffer_end = log_buffer_addr + log_buffer_size + ram_unused = retention_addr - log_buffer_end + if ram_unused < 0: + ctx.fatal('Bluetooth FW too large. .bss overlaps RETENTION_BLE by %d bytes' % -ram_unused) + else: + waflib.Logs.pprint('BLUE', 'RAM: %d free' % ram_unused) + + # Calculate size of the .bin + bt_resource_size = os.path.getsize(dialog_bin.abspath()) + waflib.Logs.pprint('BLUE', 'Resource size: %d' % bt_resource_size) + + +def get_elf_sections(filename): + section_dict = {} + + with open(filename, 'rb') as f: + elffile = ELFFile(f) + + for section in elffile.iter_sections(): + if not section.is_null(): + section_dict[section.name] = (section['sh_addr'], section['sh_size']) + + return section_dict + +# vim:filetype=python diff --git a/src/bluetooth-fw/wscript b/src/bluetooth-fw/wscript index 32ad499c..e07d4fee 100644 --- a/src/bluetooth-fw/wscript +++ b/src/bluetooth-fw/wscript @@ -5,6 +5,24 @@ import waflib.Logs from waflib.Configure import conf +@conf +def get_bluetooth_fw_node(ctx, variant='main', subdir=None): + subpath = ('src/bluetooth-fw/da1468x/controller/{variant}/bt_da14681_{variant}.bin' + .format(variant=variant)) + if subdir: + subpath = os.path.join(subdir, subpath) + return ctx.path.get_bld().make_node(subpath) + + +@conf +def get_bluetooth_fw_node_prf(ctx): + return ctx.get_bluetooth_fw_node(subdir='prf') + + +@conf +def get_bluetooth_fw_node_boot(ctx): + return ctx.get_bluetooth_fw_node('boot') + @conf def uses_dialog_bluetooth(ctx): @@ -16,8 +34,7 @@ def _recurse(ctx): # TODO: replace with real FW ctx.recurse('stub') elif ctx.uses_dialog_bluetooth(): - # TODO: replace with real FW - ctx.recurse('stub') + ctx.recurse('da1468x') elif ctx.env.bt_controller == 'qemu': ctx.recurse('qemu') else: @@ -28,6 +45,7 @@ def options(opt): opt.add_option('--bt_controller', action='store', default=None, help='Override Bluetooth controller to build for', choices=['da14681-01', 'da14681-00', 'cc2564x', 'qemu']) + opt.recurse('da1468x') def configure(conf): diff --git a/src/libos/wscript b/src/libos/wscript index ead623d0..5d452b48 100644 --- a/src/libos/wscript +++ b/src/libos/wscript @@ -19,6 +19,10 @@ def build(bld): # Skip building sources for local builds, like unit tests. return + if bld.uses_dialog_bluetooth(): + build_lib('libos-dialog', bld.all_envs['cortex-m0'], + ['dialog_fw_includes', 'dialog_sdk_includes']) + build_lib('libos', bld.env, ['fw_includes', 'freertos']) diff --git a/tools/analyze_mcu_flash_config.py b/tools/analyze_mcu_flash_config.py index 0e1db153..9f204705 100644 --- a/tools/analyze_mcu_flash_config.py +++ b/tools/analyze_mcu_flash_config.py @@ -79,6 +79,25 @@ class DialogElfConfig(Config): return (sysram_start, cacheram_end) +class DialogBootElfConfig(DialogElfConfig): + def rel_elf_path(self): + return '../build/src/bluetooth-fw/da1468x/controller/' \ + 'boot/bt_da14681_boot.elf' + + +class DialogMainElfConfig(DialogElfConfig): + def rel_elf_path(self): + return '../build/src/bluetooth-fw/da1468x/controller/' \ + 'main/bt_da14681_main.elf' + + def lib_paths(self): + libble_stack_path = self.abs_path( + '../src/bluetooth-fw/da1468x/vendor/bt-dialog-sdk/sdk/interfaces/' + 'ble_stack/DA14681-01-Debug/libble_stack_da14681_01.a') + return [libble_stack_path] + CONFIG_CLASSES = { 'tintin': TintinElfConfig, + 'dialog-boot': DialogBootElfConfig, + 'dialog-main': DialogMainElfConfig, }