mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-25 05:04:18 +08:00
380 lines
8.0 KiB
C
380 lines
8.0 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2014 Intel Corporation. All rights reserved.
|
|
*
|
|
*
|
|
* This library is free software; you can rebastribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is bastributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#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 "android/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);
|
|
|
|
g_free(bas->primary);
|
|
queue_destroy(bas->gatt_op, (void *) destroy_gatt_req);
|
|
g_free(bas);
|
|
}
|
|
|
|
struct bt_bas *bt_bas_new(void *primary)
|
|
{
|
|
struct bt_bas *bas;
|
|
|
|
bas = g_try_new0(struct bt_bas, 1);
|
|
if (!bas)
|
|
return NULL;
|
|
|
|
bas->gatt_op = queue_new();
|
|
if (!bas->gatt_op) {
|
|
bas_free(bas);
|
|
return NULL;
|
|
}
|
|
|
|
if (primary)
|
|
bas->primary = g_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);
|
|
if (!req)
|
|
return NULL;
|
|
|
|
req->user_data = user_data;
|
|
req->bas = bt_bas_ref(bas);
|
|
|
|
return req;
|
|
}
|
|
|
|
static bool set_and_store_gatt_req(struct bt_bas *bas,
|
|
struct gatt_request *req,
|
|
unsigned int id)
|
|
{
|
|
req->id = id;
|
|
return 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);
|
|
if (!req)
|
|
return;
|
|
|
|
id = gatt_write_char(attrib, handle, value, vlen, func, req);
|
|
|
|
if (set_and_store_gatt_req(bas, req, id))
|
|
return;
|
|
|
|
error("bas: Could not write characteristic");
|
|
g_attrib_cancel(attrib, id);
|
|
free(req);
|
|
|
|
}
|
|
|
|
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);
|
|
if (!req)
|
|
return;
|
|
|
|
id = gatt_read_char(attrib, handle, func, req);
|
|
|
|
if (set_and_store_gatt_req(bas, req, id))
|
|
return;
|
|
|
|
error("bas: Could not read characteristic");
|
|
g_attrib_cancel(attrib, id);
|
|
free(req);
|
|
}
|
|
|
|
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);
|
|
if (!req)
|
|
return;
|
|
|
|
id = gatt_discover_char(attrib, start, end, uuid, func, req);
|
|
|
|
if (set_and_store_gatt_req(bas, req, id))
|
|
return;
|
|
|
|
error("bas: Could not discover characteristic");
|
|
g_attrib_cancel(attrib, id);
|
|
free(req);
|
|
}
|
|
|
|
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);
|
|
if (!req)
|
|
return;
|
|
|
|
id = gatt_discover_desc(attrib, start, end, uuid, func, req);
|
|
if (set_and_store_gatt_req(bas, req, id))
|
|
return;
|
|
|
|
error("bas: Could not discover descriptor");
|
|
g_attrib_cancel(attrib, id);
|
|
free(req);
|
|
}
|
|
|
|
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;
|
|
}
|