blob: 6126a776cb51a814174cd814de471d90458d96b7 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2012 Texas Instruments, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; 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 "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "attrib/gattrib.h"
#include "attrib/att.h"
#include "attrib/gatt.h"
#include "profiles/deviceinfo/dis.h"
#define DIS_UUID16 0x180a
#define PNP_ID_SIZE 7
struct bt_dis {
int ref_count;
uint16_t handle;
uint8_t source;
uint16_t vendor;
uint16_t product;
uint16_t version;
GAttrib *attrib; /* GATT connection */
struct gatt_primary *primary; /* Primary details */
bt_dis_notify notify;
void *notify_data;
struct queue *gatt_op;
};
struct characteristic {
struct gatt_char attr; /* Characteristic */
struct bt_dis *d; /* deviceinfo where the char belongs */
};
struct gatt_request {
unsigned int id;
struct bt_dis *dis;
void *user_data;
};
static void destroy_gatt_req(struct gatt_request *req)
{
queue_remove(req->dis->gatt_op, req);
bt_dis_unref(req->dis);
free(req);
}
static void dis_free(struct bt_dis *dis)
{
bt_dis_detach(dis);
g_free(dis->primary);
queue_destroy(dis->gatt_op, (void *) destroy_gatt_req);
g_free(dis);
}
static void foreach_dis_char(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_dis *dis = user_data;
bt_uuid_t pnpid_uuid, uuid;
uint16_t value_handle;
/* Ignore if there are multiple instances */
if (dis->handle)
return;
if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid))
return;
/* Find PNPID characteristic's value handle */
bt_string_to_uuid(&pnpid_uuid, PNPID_UUID);
if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0)
dis->handle = value_handle;
}
static void foreach_dis_service(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_dis *dis = user_data;
/* Ignore if there are multiple instances */
if (dis->handle)
return;
gatt_db_service_foreach_char(attr, foreach_dis_char, dis);
}
struct bt_dis *bt_dis_new(struct gatt_db *db)
{
struct bt_dis *dis;
dis = g_try_new0(struct bt_dis, 1);
if (!dis)
return NULL;
dis->gatt_op = queue_new();
if (db) {
bt_uuid_t uuid;
/* Handle the DIS service */
bt_uuid16_create(&uuid, DIS_UUID16);
gatt_db_foreach_service(db, &uuid, foreach_dis_service, dis);
if (!dis->handle) {
dis_free(dis);
return NULL;
}
}
return bt_dis_ref(dis);
}
struct bt_dis *bt_dis_new_primary(void *primary)
{
struct bt_dis *dis;
dis = g_try_new0(struct bt_dis, 1);
if (!dis)
return NULL;
dis->gatt_op = queue_new();
if (primary)
dis->primary = g_memdup(primary, sizeof(*dis->primary));
return bt_dis_ref(dis);
}
struct bt_dis *bt_dis_ref(struct bt_dis *dis)
{
if (!dis)
return NULL;
__sync_fetch_and_add(&dis->ref_count, 1);
return dis;
}
void bt_dis_unref(struct bt_dis *dis)
{
if (!dis)
return;
if (__sync_sub_and_fetch(&dis->ref_count, 1))
return;
dis_free(dis);
}
static struct gatt_request *create_request(struct bt_dis *dis,
void *user_data)
{
struct gatt_request *req;
req = new0(struct gatt_request, 1);
req->user_data = user_data;
req->dis = bt_dis_ref(dis);
return req;
}
static bool set_and_store_gatt_req(struct bt_dis *dis,
struct gatt_request *req,
unsigned int id)
{
req->id = id;
return queue_push_head(dis->gatt_op, req);
}
static void read_pnpid_cb(guint8 status, const guint8 *pdu, guint16 len,
gpointer user_data)
{
struct gatt_request *req = user_data;
struct bt_dis *dis = req->user_data;
uint8_t value[PNP_ID_SIZE];
ssize_t vlen;
destroy_gatt_req(req);
if (status != 0) {
error("Error reading PNP_ID value: %s", att_ecode2str(status));
return;
}
vlen = dec_read_resp(pdu, len, value, sizeof(value));
if (vlen < 0) {
error("Error reading PNP_ID: Protocol error");
return;
}
if (vlen < 7) {
error("Error reading PNP_ID: Invalid pdu length received");
return;
}
dis->source = value[0];
dis->vendor = get_le16(&value[1]);
dis->product = get_le16(&value[3]);
dis->version = get_le16(&value[5]);
DBG("source: 0x%02X vendor: 0x%04X product: 0x%04X version: 0x%04X",
dis->source, dis->vendor, dis->product, dis->version);
if (dis->notify)
dis->notify(dis->source, dis->vendor, dis->product,
dis->version, dis->notify_data);
}
static void read_char(struct bt_dis *dis, GAttrib *attrib, uint16_t handle,
GAttribResultFunc func, gpointer user_data)
{
struct gatt_request *req;
unsigned int id;
req = create_request(dis, user_data);
id = gatt_read_char(attrib, handle, func, req);
if (set_and_store_gatt_req(dis, req, id))
return;
error("dis: Could not read characteristic");
g_attrib_cancel(attrib, id);
free(req);
}
static void discover_char(struct bt_dis *dis, 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(dis, user_data);
id = gatt_discover_char(attrib, start, end, uuid, func, req);
if (set_and_store_gatt_req(dis, req, id))
return;
error("dis: Could not send discover characteristic");
g_attrib_cancel(attrib, id);
free(req);
}
static void configure_deviceinfo_cb(uint8_t status, GSList *characteristics,
void *user_data)
{
struct gatt_request *req = user_data;
struct bt_dis *d = req->user_data;
GSList *l;
destroy_gatt_req(req);
if (status != 0) {
error("Discover deviceinfo characteristics: %s",
att_ecode2str(status));
return;
}
for (l = characteristics; l; l = l->next) {
struct gatt_char *c = l->data;
if (strcmp(c->uuid, PNPID_UUID) == 0) {
d->handle = c->value_handle;
read_char(d, d->attrib, d->handle, read_pnpid_cb, d);
break;
}
}
}
bool bt_dis_attach(struct bt_dis *dis, void *attrib)
{
struct gatt_primary *primary = dis->primary;
if (dis->attrib)
return false;
dis->attrib = g_attrib_ref(attrib);
if (!dis->handle)
discover_char(dis, dis->attrib, primary->range.start,
primary->range.end, NULL,
configure_deviceinfo_cb, dis);
else
read_char(dis, attrib, dis->handle, read_pnpid_cb, dis);
return true;
}
static void cancel_gatt_req(struct gatt_request *req)
{
if (g_attrib_cancel(req->dis->attrib, req->id))
destroy_gatt_req(req);
}
void bt_dis_detach(struct bt_dis *dis)
{
if (!dis->attrib)
return;
queue_foreach(dis->gatt_op, (void *) cancel_gatt_req, NULL);
g_attrib_unref(dis->attrib);
dis->attrib = NULL;
}
bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func,
void *user_data)
{
if (!dis)
return false;
dis->notify = func;
dis->notify_data = user_data;
return true;
}