// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/log.h" #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/uuid.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "attrib/gattrib.h" #include "attrib/att.h" #include "attrib/gatt.h" #include "profiles/battery/bas.h" #define ATT_NOTIFICATION_HEADER_SIZE 3 #define ATT_READ_RESPONSE_HEADER_SIZE 1 struct bt_bas { int ref_count; GAttrib *attrib; struct gatt_primary *primary; uint16_t handle; uint16_t ccc_handle; guint id; struct queue *gatt_op; }; struct gatt_request { unsigned int id; struct bt_bas *bas; void *user_data; }; static void destroy_gatt_req(struct gatt_request *req) { queue_remove(req->bas->gatt_op, req); bt_bas_unref(req->bas); free(req); } static void bas_free(struct bt_bas *bas) { bt_bas_detach(bas); free(bas->primary); queue_destroy(bas->gatt_op, (void *) destroy_gatt_req); free(bas); } struct bt_bas *bt_bas_new(void *primary) { struct bt_bas *bas; bas = new0(struct bt_bas, 1); bas->gatt_op = queue_new(); if (primary) bas->primary = util_memdup(primary, sizeof(*bas->primary)); return bt_bas_ref(bas); } struct bt_bas *bt_bas_ref(struct bt_bas *bas) { if (!bas) return NULL; __sync_fetch_and_add(&bas->ref_count, 1); return bas; } void bt_bas_unref(struct bt_bas *bas) { if (!bas) return; if (__sync_sub_and_fetch(&bas->ref_count, 1)) return; bas_free(bas); } static struct gatt_request *create_request(struct bt_bas *bas, void *user_data) { struct gatt_request *req; req = new0(struct gatt_request, 1); req->user_data = user_data; req->bas = bt_bas_ref(bas); return req; } static void set_and_store_gatt_req(struct bt_bas *bas, struct gatt_request *req, unsigned int id) { req->id = id; queue_push_head(bas->gatt_op, req); } static void write_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_write_char(attrib, handle, value, vlen, func, req); set_and_store_gatt_req(bas, req, id); } static void read_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_read_char(attrib, handle, func, req); set_and_store_gatt_req(bas, req, id); } static void discover_char(struct bt_bas *bas, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_discover_char(attrib, start, end, uuid, func, req); set_and_store_gatt_req(bas, req, id); } static void discover_desc(struct bt_bas *bas, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_discover_desc(attrib, start, end, uuid, func, req); set_and_store_gatt_req(bas, req, id); } static void notification_cb(const guint8 *pdu, guint16 len, gpointer user_data) { DBG("Battery Level at %u", pdu[ATT_NOTIFICATION_HEADER_SIZE]); } static void read_value_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { DBG("Battery Level at %u", pdu[ATT_READ_RESPONSE_HEADER_SIZE]); } static void ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; destroy_gatt_req(req); if (status != 0) { error("Write Scan Refresh CCC failed: %s", att_ecode2str(status)); return; } DBG("Battery Level: notification enabled"); bas->id = g_attrib_register(bas->attrib, ATT_OP_HANDLE_NOTIFY, bas->handle, notification_cb, bas, NULL); } static void write_ccc(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, void *user_data) { uint8_t value[2]; put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); write_char(bas, attrib, handle, value, sizeof(value), ccc_written_cb, user_data); } static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; destroy_gatt_req(req); if (status != 0) { error("Error reading CCC value: %s", att_ecode2str(status)); return; } write_ccc(bas, bas->attrib, bas->ccc_handle, bas); } static void discover_descriptor_cb(uint8_t status, GSList *descs, void *user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; struct gatt_desc *desc; destroy_gatt_req(req); if (status != 0) { error("Discover descriptors failed: %s", att_ecode2str(status)); return; } /* There will be only one descriptor on list and it will be CCC */ desc = descs->data; bas->ccc_handle = desc->handle; read_char(bas, bas->attrib, desc->handle, ccc_read_cb, bas); } static void bas_discovered_cb(uint8_t status, GSList *chars, void *user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; struct gatt_char *chr; uint16_t start, end; bt_uuid_t uuid; destroy_gatt_req(req); if (status) { error("Battery: %s", att_ecode2str(status)); return; } chr = chars->data; bas->handle = chr->value_handle; DBG("Battery handle: 0x%04x", bas->handle); read_char(bas, bas->attrib, bas->handle, read_value_cb, bas); start = chr->value_handle + 1; end = bas->primary->range.end; bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); discover_desc(bas, bas->attrib, start, end, &uuid, discover_descriptor_cb, bas); } bool bt_bas_attach(struct bt_bas *bas, void *attrib) { if (!bas || bas->attrib || !bas->primary) return false; bas->attrib = g_attrib_ref(attrib); if (bas->handle > 0) return true; discover_char(bas, bas->attrib, bas->primary->range.start, bas->primary->range.end, NULL, bas_discovered_cb, bas); return true; } static void cancel_gatt_req(struct gatt_request *req) { if (g_attrib_cancel(req->bas->attrib, req->id)) destroy_gatt_req(req); } void bt_bas_detach(struct bt_bas *bas) { if (!bas || !bas->attrib) return; if (bas->id > 0) { g_attrib_unregister(bas->attrib, bas->id); bas->id = 0; } queue_foreach(bas->gatt_op, (void *) cancel_gatt_req, NULL); g_attrib_unref(bas->attrib); bas->attrib = NULL; }