fw: drivers: add da1468x bluetooth code

This commit is contained in:
Liam McLoughlin 2025-02-13 11:50:18 +00:00
parent 5b5d49cb49
commit 4051c5bb97
203 changed files with 19237 additions and 2 deletions

1
src/bluetooth-fw/da1468x/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
!openocd.cfg

View file

@ -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 <bluetooth/gap_le_connect.h>
#include <string.h>
#include <stdbool.h>
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);
}
}

View file

@ -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 <bluetooth/bonding_sync.h>
#include <stdint.h>
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);
}

View file

@ -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 <inttypes.h>
#include <string.h>
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();
}

View file

@ -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;

View file

@ -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;

View file

@ -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 <stdbool.h>
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"

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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");
}

View file

@ -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 <string.h>
// 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
}

View file

@ -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 <stdint.h>
#include <stdio.h>
#include <inttypes.h>
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();
}

View file

@ -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 <string.h>
#include <stdint.h>
#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();
}

View file

@ -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 <util/attributes.h>
#include "debug_print.h"
#include "sdk_defs.h"
#include <stdint.h>
//! 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();
}

View file

@ -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

View file

@ -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 <ad_gpadc.h>
#include <ad_i2c.h>
#include <ad_spi.h>
#include <ad_uart.h>
#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 */

View file

@ -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 <stdbool.h>
#include <stdint.h>
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);

View file

@ -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 <stdbool.h>
#include <stdint.h>
#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]);

View file

@ -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 <stdbool.h>
#include <bluetooth/init.h>
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);

View file

@ -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 <stdbool.h>
bool dialog_chip_id_copy(DialogChipID *chip_id_out);

View file

@ -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 <stdbool.h>
#include <stdint.h>
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);

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <bluetooth/gap_le_connect.h>
#include <util/list.h>
#include <stdint.h>
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!");

View file

@ -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 <stdbool.h>
//! 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);

View file

@ -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);

View file

@ -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

View file

@ -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 <inttypes.h>
//! 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);

View file

@ -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 <util/attributes.h>
#include <stdint.h>
NORETURN reset_due_to_software_failure(void);

View file

@ -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 <stdbool.h>
#include <stdint.h>
typedef struct DisInfo DisInfo;
void device_information_service_init(const DisInfo *dis_info);
bool device_information_service_register(uint16_t start_hdl);

View file

@ -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);

View file

@ -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 <stdbool.h>
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);

View file

@ -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);

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <stdint.h>
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);

View file

@ -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 <util/list.h>
#include <stdint.h>
#include <stdbool.h>
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;

View file

@ -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);

View file

@ -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 <inttypes.h>
#include <stdbool.h>
//! 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);

View file

@ -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);

View file

@ -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 <bluetooth/hrm_service.h>
#include <stdbool.h>
#include <stdint.h>
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);

View file

@ -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 <inttypes.h>
#include <stddef.h>
// 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);

View file

@ -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;
}

View file

@ -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 <stdbool.h>
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);

View file

@ -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 <mcu.h> header.
// This will include the right MCU header and ARM core header:
#include "sdk_defs.h"
#undef CMSIS_COMPATIBLE

View file

@ -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 <stdint.h>
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);

View file

@ -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);

View file

@ -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 <stdint.h>
#include <stdbool.h>
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);

View file

@ -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/sm_types.h>
#include <stdint.h>
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);

View file

@ -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 <stdbool.h>
#include <stdint.h>
// 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);

View file

@ -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 <stdint.h>
void service_changed_send_indication_to_all(uint16_t start_handle, uint16_t end_handle);

View file

@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
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);

View file

@ -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 <util/attributes.h>
#include <stdbool.h>
#include <stdint.h>
// 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)

View file

@ -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 <util/attributes.h>
#include <util/likely.h>
#include <stdint.h>
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)

View file

@ -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);

View file

@ -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__;
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <os/mutex.h>
#include <inttypes.h>
#include <stdint.h>
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();
}

View file

@ -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 <string.h>
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;
}

View file

@ -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 <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ble_gap.h>
#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 <FreeRTOS.h>
#include <semphr.h>
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/gap_le_connect.h>
#include <bluetooth/gatt_service_types.h>
#include <bluetooth/hci_types.h>
#include <bluetooth/init.h>
#include <bluetooth/mtu.h>
#include <bluetooth/sm_types.h>
#include <util/uuid.h>
#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, &params);
pebble_pairing_service_handle_conn_params_change(conn, conn_idx);
hc_endpoint_responsiveness_notify_update(&params, &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, &notif, 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);
}

View file

@ -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 <bluetooth/bonding_sync.h>
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/sm_types.h>
#include <btutil/sm_util.h>
#include <string.h>
#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);
}
}

View file

@ -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 <stdio.h>
#include <string.h>
#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;
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <btutil/bt_device.h>
#include <util/list.h>
#include <stdint.h>
#include <string.h>
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;
}

View file

@ -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 <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#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();
}

View file

@ -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 <util/attributes.h>
#ifdef __GNUC__
// CC: libutil's attributes.h also defines this:
// #define USED __attribute__((used))
#else
#define USED
#endif
const int USED uxTopUsedPriority = configMAX_PRIORITIES;

View file

@ -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 <hw_gpio.h>
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);
}

View file

@ -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 <stdbool.h>
#include <inttypes.h>
#include <stdio.h>
#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();
}

View file

@ -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 <stdio.h>
#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) <hit return a few times to return to the point where the fault happened>\n");
NVIC_ClearPendingIRQ(HardFault_IRQn);
__asm("mov r0, #1");
while (true) {
register uintptr_t r0 __asm("r0");
if (r0 == 0) {
break;
}
}
}
#endif

View file

@ -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 <util/list.h>
#include <util/size.h>
#include <os/mutex.h>
// FreeRTOS
#include "FreeRTOSConfig.h"
// Dialog SDK
#include "sys_rtc.h"
#include <stdint.h>
#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);
}

View file

@ -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();
}

View file

@ -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 <bluetooth/dis.h>
#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;
}

View file

@ -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 <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#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();
}

View file

@ -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 <stdio.h>
#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 */

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <inttypes.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
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);
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <bluetooth/gatt_service_types.h>
#include <bluetooth/pebble_bt.h>
#include <util/uuid.h>
#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);
}

View file

@ -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 <os/mutex.h>
#include <bluetooth/init.h>
// 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;
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <util/list.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
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().
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <inttypes.h>
#include <stdint.h>
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);
}
}

View file

@ -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 <bluetooth/analytics.h>
#include <bluetooth/bluetooth_types.h>
#include <inttypes.h>
#include <stdint.h>
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);
}
}

View file

@ -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 <inttypes.h>
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;
}
}

View file

@ -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 <bluetooth/init.h>
#include <inttypes.h>
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;
}
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <bluetooth/hci_types.h>
// 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);
}
}

View file

@ -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 <bluetooth/gap_le_connect.h>
#include "connection.h"
#include "system/logging.h"
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
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));
}

View file

@ -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 <util/attributes.h>
// Dialog SDK:
#include "att.h"
#include "ble_gap.h"
#include "ble_gattc.h"
#include "ble_uuid.h"
#include <string.h>
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));
}

View file

@ -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 <bluetooth/bluetooth_types.h>
#include <stdint.h>
#include <string.h>
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);
}

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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 <bluetooth/hci_types.h>
#include <stdint.h>
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));
}

View file

@ -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 <bluetooth/pebble_pairing_service.h>
#include <string.h>
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);
}

View file

@ -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 <bluetooth/hci_types.h>
#include <inttypes.h>
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(
&params, &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);
}
}

View file

@ -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 <bluetooth/bt_test.h>
#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;
}
}

View file

@ -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 <stdio.h>
#include <string.h>
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);
}

View file

@ -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 <stddef.h>
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);
}

View file

@ -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 <util/attributes.h>
#include <string.h>
#include <stdio.h>
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 *)&params, 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, &params[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, &params[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++;
}

View file

@ -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 <string.h>
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);
}

View file

@ -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 <hw_wkup.h>
#include <osal.h>
#include <platform_devices.h>
#include <sdk_defs.h>
#include <stdio.h>
#include <string.h>
#include <sys_watchdog.h>
// 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;
}

View file

@ -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 <btutil/bt_device.h>
#include <util/size.h>
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.
}
}

View file

@ -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 <string.h>
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);

View file

@ -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 <stdio.h>
#include <stdlib.h>
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);
}

Some files were not shown because too many files have changed in this diff Show more