mirror of
https://github.com/google/pebble.git
synced 2025-11-28 02:02:23 -05:00
Import of the watch repository from Pebble
This commit is contained in:
363
src/fw/comm/ble/gatt_client_operations.c
Normal file
363
src/fw/comm/ble/gatt_client_operations.c
Normal file
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT 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();
|
||||
}
|
||||
Reference in New Issue
Block a user