Files
pebble/src/fw/comm/ble/gatt_client_operations.c
2025-01-27 11:38:16 -08:00

364 lines
13 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <bluetooth/gatt.h>
#include "gatt_client_operations.h"
#include "gatt_client_accessors.h"
#include "comm/bt_lock.h"
#include "kernel/events.h"
#include "kernel/pbl_malloc.h"
#include "util/list.h"
#include "system/passert.h"
// -------------------------------------------------------------------------------------------------
//! @see gatt_client_accessors.c / gatt_client_subscriptions.c
// These calls require the caller to own the bt_lock while calling the
// function and for as long as the result is being used / accessed.
extern uint16_t gatt_client_characteristic_get_handle_and_connection(
BLECharacteristic characteristic_ref,
GAPLEConnection **connection_out);
extern uint16_t gatt_client_descriptor_get_handle_and_connection(BLEDescriptor descriptor_ref,
GAPLEConnection **connection_out);
extern void gatt_client_subscriptions_handle_write_cccd_response(BLEDescriptor cccd,
BLEGATTError error);
// -------------------------------------------------------------------------------------------------
typedef struct {
ListNode node;
//! This is redundant, PebbleEvent already has this info, but added as integrity check.
uintptr_t object_ref;
uint16_t length;
uint8_t value[];
} ReadResponseData;
typedef struct GattClientEventContext {
ListNode node;
PebbleBLEGATTClientEventType subtype;
GAPLEClient client;
uintptr_t obj_ref;
} GattClientEventContext;
static ReadResponseData *s_read_responses[GAPLEClientNum];
//! Keeps track of the current outstanding GattClientOperations (reads/writes). Useful for freeing
//! the outstanding op's memory when a connection dies in the middle.
static GattClientEventContext *s_client_event_ctxs[GAPLEClientNum];
static void prv_send_event(PebbleBLEGATTClientEventType subtype, GAPLEClient client,
uintptr_t object_ref, uint16_t value_length, BLEGATTError gatt_error) {
PebbleEvent e = {
.type = PEBBLE_BLE_GATT_CLIENT_EVENT,
.task_mask = ~(gap_le_pebble_task_bit_for_client(client)),
.bluetooth = {
.le = {
.gatt_client = {
.subtype = subtype,
.object_ref = object_ref,
.gatt_error = gatt_error,
.value_length = value_length,
},
},
},
};
event_put(&e);
}
static void prv_internal_write_cccd_response_cb(GattClientOpResponseHdr *event) {
const GattClientEventContext *data = event->context;
const BLEDescriptor cccd = data->obj_ref;
const BLEGATTError error = event->error_code;
gatt_client_subscriptions_handle_write_cccd_response(cccd, error);
}
static BLEGATTError prv_handle_response(const GattClientOpReadReponse *resp,
const GattClientEventContext *data,
uint16_t *gatt_value_length) {
uint16_t val_len = resp->value_length;
if (val_len) {
// Only create ReadResponseData node if length is not 0
ReadResponseData *read_response = kernel_malloc(sizeof(ReadResponseData) + val_len);
if (!read_response) {
*gatt_value_length = 0;
return BLEGATTErrorLocalInsufficientResources;
}
*read_response = (const ReadResponseData) {
.object_ref = data->obj_ref,
.length = val_len,
};
memcpy(read_response->value, resp->value, val_len);
if (s_read_responses[data->client]) {
list_append(&s_read_responses[data->client]->node, &read_response->node);
} else {
s_read_responses[data->client] = read_response;
}
}
*gatt_value_length = val_len;
return BLEGATTErrorSuccess;
}
static bool prv_ctx_in_client_event_ctxs(GattClientEventContext *context) {
const bool exists =
(list_contains(&s_client_event_ctxs[GAPLEClientApp]->node, &context->node) ||
list_contains(&s_client_event_ctxs[GAPLEClientKernel]->node, &context->node));
return exists;
}
void bt_driver_cb_gatt_client_operations_handle_response(GattClientOpResponseHdr *event) {
const GattClientEventContext *data = event->context;
bt_lock();
{
//! Special case: writes to the "Client Characteristic Configuration Descriptor" are handled by
//! the gatt_client_subscriptions.c module.
if (data->client == GAPLEClientKernel
&& data->subtype == PebbleBLEGATTClientEventTypeCharacteristicSubscribe) {
prv_internal_write_cccd_response_cb(event);
goto cleanup;
}
//! There is a time when we have disconnected, but there are still outstanding responses
//! coming back to the MCU (e.g. HcProtocol). When we disconnect, we call gatt_client_op_cleanup
//! which cleans up all of the memory for the nodes in the lists in `s_client_event_ctxs`.
//! Here, we check if the context related to the response coming in has already been cleaned up,
//! and if it has, we instantly unlock and continue on.
if (!prv_ctx_in_client_event_ctxs(event->context)) {
goto unlock;
}
// Default values
uint16_t gatt_value_length = 0;
uint16_t gatt_err_code = BLEGATTErrorSuccess;
if (event->error_code != BLEGATTErrorSuccess) {
gatt_err_code = event->error_code;
} else {
switch (event->type) {
case GattClientOpResponseRead: {
const GattClientOpReadReponse *resp = (GattClientOpReadReponse *)event;
PBL_ASSERTN(data->subtype == PebbleBLEGATTClientEventTypeCharacteristicRead ||
data->subtype == PebbleBLEGATTClientEventTypeDescriptorRead);
gatt_err_code = prv_handle_response(resp, data, &gatt_value_length);
break;
}
case GattClientOpResponseWrite: {
PBL_ASSERTN(data->subtype == PebbleBLEGATTClientEventTypeCharacteristicWrite ||
data->subtype == PebbleBLEGATTClientEventTypeDescriptorWrite);
break;
}
default:
WTF;
}
}
prv_send_event(data->subtype, data->client, data->obj_ref, gatt_value_length, gatt_err_code);
}
cleanup:
list_remove(event->context, (ListNode **)&s_client_event_ctxs[data->client], NULL);
kernel_free(event->context);
unlock:
bt_unlock();
}
typedef uint16_t (*HandleAndConnectionGetter)(uintptr_t obj_ref,
GAPLEConnection **connection_out);
static GattClientEventContext *prv_create_event_context(GAPLEClient client) {
GattClientEventContext *evt_ctx = kernel_zalloc(sizeof(GattClientEventContext));
if (evt_ctx) {
s_client_event_ctxs[client] =
(GattClientEventContext *)list_prepend(&s_client_event_ctxs[client]->node, &evt_ctx->node);
}
return evt_ctx;
}
static BTErrno prv_read(uintptr_t obj_ref, GAPLEClient client,
HandleAndConnectionGetter handle_getter,
PebbleBLEGATTClientEventType subtype) {
BTErrno ret_val = BTErrnoOK;
bt_lock();
{
GAPLEConnection *connection;
const uint16_t att_handle = handle_getter(obj_ref, &connection);
if (!att_handle) {
ret_val = BTErrnoInvalidParameter;
goto unlock;
}
GattClientEventContext *data = prv_create_event_context(client);
if (!data) {
ret_val = BTErrnoNotEnoughResources;
goto unlock;
}
// Zero'd out and added to list in `prv_create_event_context`
data->client = client;
data->subtype = subtype;
data->obj_ref = obj_ref;
ret_val = bt_driver_gatt_read(connection, att_handle, data);
}
unlock:
bt_unlock();
return ret_val;
}
static BTErrno prv_write(uintptr_t obj_ref, const uint8_t *value, size_t value_length,
GAPLEClient client, HandleAndConnectionGetter handle_getter,
PebbleBLEGATTClientEventType subtype) {
BTErrno ret_val = BTErrnoOK;
bt_lock();
{
GAPLEConnection *connection;
const uint16_t att_handle = handle_getter(obj_ref, &connection);
if (!att_handle) {
ret_val = BTErrnoInvalidParameter;
goto unlock;
}
GattClientEventContext *data = prv_create_event_context(client);
if (!data) {
ret_val = BTErrnoNotEnoughResources;
goto unlock;
}
// Zero'd out and added to list in `prv_create_event_context`
data->client = client;
data->subtype = subtype;
data->obj_ref = obj_ref;
ret_val = bt_driver_gatt_write(connection, value, value_length, att_handle, data);
}
unlock:
bt_unlock();
return ret_val;
}
BTErrno gatt_client_op_read(BLECharacteristic characteristic,
GAPLEClient client) {
return prv_read(characteristic, client,
gatt_client_characteristic_get_handle_and_connection,
PebbleBLEGATTClientEventTypeCharacteristicRead);
}
void gatt_client_consume_read_response(uintptr_t object_ref,
uint8_t value_out[],
uint16_t value_length,
GAPLEClient client) {
bt_lock();
{
// For responses with 0 length, no ReadResponseData is created therefore
// should not be attempted to be consumed.
PBL_ASSERTN(value_length);
PBL_ASSERTN(s_read_responses[client]);
ReadResponseData *read_response = s_read_responses[client];
PBL_ASSERTN(value_length == read_response->length);
PBL_ASSERTN(object_ref == read_response->object_ref);
if (value_out) {
memcpy(value_out, read_response->value, read_response->length);
}
list_remove(&read_response->node, (ListNode **) &s_read_responses[client], NULL);
kernel_free(read_response);
}
bt_unlock();
}
BTErrno gatt_client_op_write(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
GAPLEClient client) {
return prv_write(characteristic, value, value_length, client,
gatt_client_characteristic_get_handle_and_connection,
PebbleBLEGATTClientEventTypeCharacteristicWrite);
}
BTErrno gatt_client_op_write_without_response(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
GAPLEClient client) {
BTErrno ret_val = BTErrnoOK;
bt_lock();
{
GAPLEConnection *connection;
const uint16_t att_handle =
gatt_client_characteristic_get_handle_and_connection(characteristic, &connection);
if (!att_handle) {
ret_val = BTErrnoInvalidParameter;
goto unlock;
}
ret_val = bt_driver_gatt_write_without_response(connection, value, value_length, att_handle);
}
unlock:
bt_unlock();
return ret_val;
}
BTErrno gatt_client_op_write_descriptor(BLEDescriptor descriptor,
const uint8_t *value,
size_t value_length,
GAPLEClient client) {
return prv_write(descriptor, value, value_length, client,
gatt_client_descriptor_get_handle_and_connection,
PebbleBLEGATTClientEventTypeDescriptorWrite);
}
BTErrno gatt_client_op_read_descriptor(BLEDescriptor descriptor,
GAPLEClient client) {
return prv_read(descriptor, client,
gatt_client_descriptor_get_handle_and_connection,
PebbleBLEGATTClientEventTypeDescriptorRead);
}
BTErrno gatt_client_op_write_descriptor_cccd(BLEDescriptor cccd, const uint16_t *value) {
return prv_write(cccd, (const uint8_t *) value, sizeof(*value), GAPLEClientKernel,
gatt_client_descriptor_get_handle_and_connection,
PebbleBLEGATTClientEventTypeCharacteristicSubscribe);
}
static bool prv_deinit_ctx_list(ListNode *node, void *unused) {
kernel_free(node);
return true;
}
void gatt_client_op_cleanup(GAPLEClient client) {
bt_lock();
{
// Free all memory associated with outstanding operations
list_foreach(&s_client_event_ctxs[client]->node, prv_deinit_ctx_list, NULL);
s_client_event_ctxs[client] = NULL;
ReadResponseData *read_response = s_read_responses[client];
while (read_response) {
ReadResponseData *next_read_response = (ReadResponseData *) read_response->node.next;
kernel_free(read_response);
read_response = next_read_response;
}
s_read_responses[client] = NULL;
}
bt_unlock();
}