summaryrefslogtreecommitdiff
path: root/profiles
diff options
context:
space:
mode:
authorh.sandeep <h.sandeep@samsung.com>2016-04-07 09:39:01 +0530
committerSandeep Hattiholi <h.sandeep@samsung.com>2016-04-12 01:16:48 -0700
commit9cc0d447358bad2c1be6e5c51d477d938e7ecbf5 (patch)
treef9849cf199e3b962a9e8d5f99826bc9def18fe85 /profiles
parent1b52151f0a5f30794e939592489a259808b4ba5a (diff)
downloadbluez-9cc0d447358bad2c1be6e5c51d477d938e7ecbf5.tar.gz
bluez-9cc0d447358bad2c1be6e5c51d477d938e7ecbf5.tar.bz2
bluez-9cc0d447358bad2c1be6e5c51d477d938e7ecbf5.zip
branch:devel/bluetooth/master ================================================ commit 81e16d9fa48dc40f2dcb15aca9ce87ea50d9a85e Author: Injun Yang <injun.yang@samsung.com> Date: Thu Mar 31 17:05:06 2016 +0900 GATT Server : Handle property and write type ============================================== Change-Id: I0d2b69488337b1f393fba43dc1268bd92ea6b6ac Signed-off-by: h.sandeep <h.sandeep@samsung.com>
Diffstat (limited to 'profiles')
-rw-r--r--profiles/audio/a2dp-codecs.h8
-rw-r--r--profiles/audio/a2dp.c139
-rw-r--r--profiles/audio/a2dp.h6
-rw-r--r--profiles/audio/avctp.c59
-rw-r--r--profiles/audio/avctp.h3
-rw-r--r--profiles/audio/avdtp.c159
-rw-r--r--profiles/audio/avrcp.c681
-rw-r--r--profiles/audio/avrcp.h7
-rw-r--r--profiles/audio/control.c67
-rw-r--r--profiles/audio/control.h2
-rw-r--r--profiles/audio/media.c66
-rw-r--r--profiles/audio/player.c142
-rw-r--r--profiles/audio/player.h12
-rw-r--r--profiles/audio/sink.c8
-rw-r--r--profiles/audio/source.c8
-rw-r--r--profiles/audio/transport.c17
-rw-r--r--profiles/battery/bas.c340
-rw-r--r--profiles/battery/bas.h32
-rw-r--r--profiles/deviceinfo/deviceinfo.c163
-rw-r--r--profiles/deviceinfo/dis.c293
-rw-r--r--profiles/deviceinfo/dis.h39
-rw-r--r--profiles/gap/gas.c42
-rw-r--r--profiles/input/hog-lib.c1550
-rw-r--r--profiles/input/hog-lib.h41
-rw-r--r--profiles/input/hog.c934
-rw-r--r--profiles/input/suspend-none.c42
-rw-r--r--profiles/network/bnep.c55
-rw-r--r--profiles/network/connection.c15
-rw-r--r--profiles/network/server.c5
-rw-r--r--profiles/scanparam/scan.c256
-rw-r--r--profiles/scanparam/scpp.c355
-rw-r--r--profiles/scanparam/scpp.h35
32 files changed, 4213 insertions, 1368 deletions
diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h
index 4d2584d0..e9da0bf8 100644
--- a/profiles/audio/a2dp-codecs.h
+++ b/profiles/audio/a2dp-codecs.h
@@ -141,6 +141,9 @@
#define APTX_SAMPLING_FREQ_44100 0x02
#define APTX_SAMPLING_FREQ_48000 0x01
+#define LDAC_VENDOR_ID 0x0000012d
+#define LDAC_CODEC_ID 0x00aa
+
typedef struct {
uint32_t vendor_id;
uint16_t codec_id;
@@ -186,6 +189,11 @@ typedef struct {
uint8_t frequency:4;
} __attribute__ ((packed)) a2dp_aptx_t;
+typedef struct {
+ a2dp_vendor_codec_t info;
+ uint8_t unknown[2];
+} __attribute__ ((packed)) a2dp_ldac_t;
+
#elif __BYTE_ORDER == __BIG_ENDIAN
typedef struct {
diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index 84786dec..2d5c7469 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -72,6 +72,9 @@ struct a2dp_sep {
struct avdtp *session;
struct avdtp_stream *stream;
guint suspend_timer;
+#ifdef __TIZEN_PATCH__
+ gboolean remote_suspended;
+#endif
gboolean delay_reporting;
gboolean locked;
gboolean suspending;
@@ -82,6 +85,7 @@ struct a2dp_sep {
struct a2dp_setup_cb {
struct a2dp_setup *setup;
+ a2dp_discover_cb_t discover_cb;
a2dp_select_cb_t select_cb;
a2dp_config_cb_t config_cb;
a2dp_stream_cb_t resume_cb;
@@ -98,6 +102,7 @@ struct a2dp_setup {
struct avdtp_stream *stream;
struct avdtp_error *err;
avdtp_set_configuration_cb setconf_cb;
+ GSList *seps;
GSList *caps;
gboolean reconfigure;
gboolean start;
@@ -302,6 +307,23 @@ static void finalize_select(struct a2dp_setup *s)
}
}
+static void finalize_discover(struct a2dp_setup *s)
+{
+ GSList *l;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (!cb->discover_cb)
+ continue;
+
+ cb->discover_cb(s->session, s->seps, s->err, cb->user_data);
+ setup_cb_free(cb);
+ }
+}
+
static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
{
GSList *l;
@@ -375,6 +397,13 @@ static void stream_state_changed(struct avdtp_stream *stream,
return;
}
+#ifdef __TIZEN_PATCH__
+ if (new_state == AVDTP_STATE_STREAMING && sep->suspend_timer) {
+ g_source_remove(sep->suspend_timer);
+ sep->suspend_timer = 0;
+ }
+#endif
+
if (new_state != AVDTP_STATE_IDLE)
return;
@@ -799,12 +828,25 @@ static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep,
else
DBG("Source %p: Start_Ind", sep);
+#ifdef __TIZEN_PATCH__
+ if (!a2dp_sep->locked) {
+ a2dp_sep->session = avdtp_ref(session);
+ if(a2dp_sep->remote_suspended == FALSE)
+ a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT,
+ (GSourceFunc) suspend_timeout,
+ a2dp_sep);
+ else
+ a2dp_sep->remote_suspended = FALSE;
+ }
+#else
+
if (!a2dp_sep->locked) {
a2dp_sep->session = avdtp_ref(session);
a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT,
(GSourceFunc) suspend_timeout,
a2dp_sep);
}
+#endif
if (!a2dp_sep->starting)
return TRUE;
@@ -858,6 +900,10 @@ static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
else
DBG("Source %p: Suspend_Ind", sep);
+#ifdef __TIZEN_PATCH__
+ a2dp_sep->remote_suspended = TRUE;
+#endif
+
if (a2dp_sep->suspend_timer) {
g_source_remove(a2dp_sep->suspend_timer);
a2dp_sep->suspend_timer = 0;
@@ -1172,7 +1218,11 @@ static sdp_record_t *a2dp_record(uint8_t type)
sdp_data_t *psm, *version, *features;
uint16_t lp = AVDTP_UUID;
#ifdef __TIZEN_PATCH__
+#ifdef SUPPORT_LOCAL_DEVICE_A2DP_SINK
+ uint16_t a2dp_ver = 0x0102, avdtp_ver = 0x0103, feat = 0x0002;
+#else
uint16_t a2dp_ver = 0x0102, avdtp_ver = 0x0103, feat = 0x0001;
+#endif
#else
uint16_t a2dp_ver = 0x0103, avdtp_ver = 0x0103, feat = 0x000f;
#endif
@@ -1449,7 +1499,7 @@ static void transport_cb(GIOChannel *io, GError *err, gpointer user_data)
if (!avdtp_stream_set_transport(setup->stream,
g_io_channel_unix_get_fd(io),
- omtu, imtu))
+ imtu, omtu))
goto drop;
g_io_channel_set_close_on_unref(io, FALSE);
@@ -1535,6 +1585,9 @@ static bool a2dp_server_listen(struct a2dp_server *server)
btd_adapter_get_address(server->adapter),
BT_IO_OPT_PSM, AVDTP_PSM,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+#if defined(__TIZEN_PATCH__) && defined(SUPPORT_LOCAL_DEVICE_A2DP_SINK)
+ BT_IO_OPT_IMTU, 895,
+#endif
BT_IO_OPT_MASTER, true,
BT_IO_OPT_INVALID);
if (server->io)
@@ -1759,44 +1812,6 @@ done:
finalize_select(setup);
}
-#ifndef __TIZEN_PATCH__
-static gboolean check_vendor_codec(struct a2dp_sep *sep, uint8_t *cap,
- size_t len)
-{
- uint8_t *capabilities;
- size_t length;
- a2dp_vendor_codec_t *local_codec;
- a2dp_vendor_codec_t *remote_codec;
-
- if (len < sizeof(a2dp_vendor_codec_t))
- return FALSE;
-
- remote_codec = (a2dp_vendor_codec_t *) cap;
-
- if (sep->endpoint == NULL)
- return FALSE;
-
- length = sep->endpoint->get_capabilities(sep,
- &capabilities, sep->user_data);
-
- if (length < sizeof(a2dp_vendor_codec_t))
- return FALSE;
-
- local_codec = (a2dp_vendor_codec_t *) capabilities;
-
- if (btohl(remote_codec->vendor_id) != btohl(local_codec->vendor_id))
- return FALSE;
-
- if (btohs(remote_codec->codec_id) != btohs(local_codec->codec_id))
- return FALSE;
-
- DBG("vendor 0x%08x codec 0x%04x", btohl(remote_codec->vendor_id),
- btohs(remote_codec->codec_id));
-
- return TRUE;
-}
-#endif
-
static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
const char *sender)
{
@@ -1823,6 +1838,7 @@ static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
if (g_strcmp0(sender, name) != 0)
continue;
}
+
#ifdef __TIZEN_PATCH__
rsep = avdtp_find_remote_sep(session, sep->lsep);
if (rsep == NULL)
@@ -1836,9 +1852,8 @@ static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
continue;
}
#else
- if (check_vendor_codec(sep, cap->data,
- service->length - sizeof(*cap)))
- return sep;
+ if (avdtp_find_remote_sep(session, sep->lsep) == NULL)
+ continue;
#endif
return sep;
@@ -1876,6 +1891,40 @@ static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type,
return a2dp_find_sep(session, l, NULL);
}
+static void discover_cb(struct avdtp *session, GSList *seps,
+ struct avdtp_error *err, void *user_data)
+{
+ struct a2dp_setup *setup = user_data;
+
+ DBG("err %p", err);
+
+ setup->seps = seps;
+ setup->err = err;
+
+ finalize_discover(setup);
+}
+
+unsigned int a2dp_discover(struct avdtp *session, a2dp_discover_cb_t cb,
+ void *user_data)
+{
+ struct a2dp_setup *setup;
+ struct a2dp_setup_cb *cb_data;
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return 0;
+
+ cb_data = setup_cb_new(setup);
+ cb_data->discover_cb = cb;
+ cb_data->user_data = user_data;
+
+ if (avdtp_discover(session, discover_cb, setup) == 0)
+ return cb_data->id;
+
+ setup_cb_free(cb_data);
+ return 0;
+}
+
unsigned int a2dp_select_capabilities(struct avdtp *session,
uint8_t type, const char *sender,
a2dp_select_cb_t cb,
@@ -2169,8 +2218,8 @@ gboolean a2dp_cancel(unsigned int id)
if (!setup->cb) {
DBG("aborting setup %p", setup);
- avdtp_abort(setup->session, setup->stream);
- return TRUE;
+ if (!avdtp_abort(setup->session, setup->stream))
+ return TRUE;
}
setup_unref(setup);
@@ -2491,7 +2540,9 @@ static struct btd_adapter_driver media_driver = {
static int a2dp_init(void)
{
btd_register_adapter_driver(&media_driver);
+#if defined(__TIZEN_PATCH__) && defined(SUPPORT_LOCAL_DEVICE_A2DP_SINK)
btd_profile_register(&a2dp_source_profile);
+#endif
btd_profile_register(&a2dp_sink_profile);
return 0;
diff --git a/profiles/audio/a2dp.h b/profiles/audio/a2dp.h
index 544eea1e..19d1877b 100644
--- a/profiles/audio/a2dp.h
+++ b/profiles/audio/a2dp.h
@@ -52,6 +52,9 @@ struct a2dp_endpoint {
void *user_data);
};
+typedef void (*a2dp_discover_cb_t) (struct avdtp *session, GSList *seps,
+ struct avdtp_error *err,
+ void *user_data);
typedef void (*a2dp_select_cb_t) (struct avdtp *session,
struct a2dp_sep *sep, GSList *caps,
void *user_data);
@@ -70,6 +73,9 @@ struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type,
int *err);
void a2dp_remove_sep(struct a2dp_sep *sep);
+
+unsigned int a2dp_discover(struct avdtp *session, a2dp_discover_cb_t cb,
+ void *user_data);
unsigned int a2dp_select_capabilities(struct avdtp *session,
uint8_t type, const char *sender,
a2dp_select_cb_t cb,
diff --git a/profiles/audio/avctp.c b/profiles/audio/avctp.c
index 34b01830..4c807276 100644
--- a/profiles/audio/avctp.c
+++ b/profiles/audio/avctp.c
@@ -320,16 +320,31 @@ static void send_key(int fd, uint16_t key, int pressed)
static gboolean auto_release(gpointer user_data)
{
struct avctp *session = user_data;
-
- session->key.timer = 0;
+#ifdef __TIZEN_PATCH__
+ uint16_t op = session->key.op;
+#endif
DBG("AV/C: key press timeout");
+#ifdef __TIZEN_PATCH__
+ if (op != KEY_FASTFORWARD && op != KEY_REWIND) {
+ session->key.timer = 0;
+ send_key(session->uinput, op, 0);
+ } else {
+ return TRUE;
+ }
+#else
+ session->key.timer = 0;
send_key(session->uinput, session->key.op, 0);
+#endif
return FALSE;
}
+#ifdef __TIZEN_PATCH__
+extern void avrcp_stop_position_timer(void);
+#endif
+
static void handle_press(struct avctp *session, uint16_t op)
{
if (session->key.timer > 0) {
@@ -338,8 +353,9 @@ static void handle_press(struct avctp *session, uint16_t op)
/* Only auto release if keys are different */
if (session->key.op == op)
goto done;
-
+#ifndef __TIZEN_PATCH__
send_key(session->uinput, session->key.op, 0);
+#endif
}
session->key.op = op;
@@ -797,7 +813,11 @@ static gboolean process_queue(void *user_data)
return FALSE;
chan->p = p;
+#ifdef __TIZEN_PATCH__
+ p->timeout = g_timeout_add_seconds(5, req_timeout, chan);
+#else
p->timeout = g_timeout_add_seconds(2, req_timeout, chan);
+#endif
return FALSE;
@@ -1446,6 +1466,16 @@ static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
if (!device)
return;
+#ifdef __TIZEN_PATCH__
+ char name[10];
+ device_get_name(device, name, sizeof(name));
+ DBG("name : %s", name);
+ if (g_str_equal(name, "PLT_M50")) {
+ DBG("Don't accept avrcp connection with this headset");
+ return;
+ }
+#endif
+
session = avctp_get_internal(device);
if (session == NULL)
return;
@@ -1702,13 +1732,20 @@ static gboolean repeat_timeout(gpointer user_data)
struct avctp *session = user_data;
avctp_passthrough_release(session, session->key.op);
+#ifndef __TIZEN_PATCH__
avctp_passthrough_press(session, session->key.op);
return TRUE;
+#else
+ return FALSE;
+#endif
}
static void release_pressed(struct avctp *session)
{
+#ifdef __TIZEN_PATCH__
+ if (session->key.op != AVC_FAST_FORWARD && session->key.op != AVC_REWIND)
+#endif
avctp_passthrough_release(session, session->key.op);
if (session->key.timer > 0)
@@ -1759,7 +1796,23 @@ int avctp_send_passthrough(struct avctp *session, uint8_t op)
return avctp_passthrough_press(session, op);
}
+#ifdef __TIZEN_PATCH__
+int avctp_send_release_passthrough(struct avctp *session, uint8_t op)
+{
+ DBG("+");
+ if (op != AVC_FAST_FORWARD && op != AVC_REWIND)
+ return FALSE;
+
+ /* Auto release if key pressed */
+ if (session->key.timer > 0)
+ g_source_remove(session->key.timer);
+ session->key.timer = 0;
+
+ DBG("-");
+ return avctp_passthrough_release(session, op);
+}
+#endif
int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
uint8_t code, uint8_t subunit,
uint8_t *operands, size_t operand_count)
diff --git a/profiles/audio/avctp.h b/profiles/audio/avctp.h
index 6c19ce42..b9329524 100644
--- a/profiles/audio/avctp.h
+++ b/profiles/audio/avctp.h
@@ -172,6 +172,9 @@ unsigned int avctp_register_browsing_pdu_handler(struct avctp *session,
gboolean avctp_unregister_browsing_pdu_handler(unsigned int id);
int avctp_send_passthrough(struct avctp *session, uint8_t op);
+#ifdef __TIZEN_PATCH__
+int avctp_send_release_passthrough(struct avctp *session, uint8_t op);
+#endif
int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
uint8_t code, uint8_t subunit,
uint8_t *operands, size_t operand_count);
diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c
index 0ba00b3d..07337027 100644
--- a/profiles/audio/avdtp.c
+++ b/profiles/audio/avdtp.c
@@ -42,7 +42,7 @@
#include "lib/uuid.h"
#ifdef __TIZEN_PATCH__
-#ifdef __BROADCOM_QOS_PATCH__
+#if defined(__BROADCOM_QOS_PATCH__) || defined(__SPRD_QOS_PATCH__)
#include <sys/ioctl.h>
#include <bluetooth/hci.h>
#endif /* __BROADCOM_QOS_PATCH__ */
@@ -961,7 +961,7 @@ static void handle_unanswered_req(struct avdtp *session,
#ifdef __TIZEN_PATCH__
#ifdef __BROADCOM_QOS_PATCH__
-static gboolean send_broadcom_a2dp_qos(bdaddr_t *dst, gboolean qos_high)
+static gboolean send_broadcom_a2dp_qos(const bdaddr_t *dst, gboolean qos_high)
{
int dd;
int err = 0;
@@ -1007,8 +1007,64 @@ static gboolean send_broadcom_a2dp_qos(bdaddr_t *dst, gboolean qos_high)
return TRUE;
}
#endif /* __BROADCOM_QOS_PATCH__ */
+
+#ifdef __SPRD_QOS_PATCH__
+static gboolean send_sprd_a2dp_qos(bdaddr_t *dst, gboolean qos_high)
+{
+ int dd;
+ int err = 0;
+ struct hci_conn_info_req *cr;
+ qos_setup_cp cp;
+
+ dd = hci_open_dev(0);
+
+ cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info));
+
+ cr->type = ACL_LINK;
+ bacpy(&cr->bdaddr, dst);
+
+ err = ioctl(dd, HCIGETCONNINFO, cr);
+ if (err < 0) {
+ error("Fail to get HCIGETCOINFO");
+ g_free(cr);
+ hci_close_dev(dd);
+ return FALSE;
+ }
+
+ cp.handle = cr->conn_info->handle;
+ cp.flags = 0x00;
+ DBG("Handle %d", cp.handle);
+ g_free(cr);
+
+ if (qos_high) {
+ cp.qos.service_type = 0x02;
+ cp.qos.token_rate = 0X000000C8;
+ cp.qos.peak_bandwidth = 0X000000C8;
+ cp.qos.latency = 0x00000001;
+ cp.qos.delay_variation = 0xFFFFFFFF;
+ } else {
+ cp.qos.service_type = 0x01;
+ cp.qos.token_rate = 0X00000000;
+ cp.qos.peak_bandwidth = 0X00000000;
+ cp.qos.latency = 0x00000001;
+ cp.qos.delay_variation = 0xFFFFFFFF;
+ }
+
+ if (hci_send_cmd(dd, OGF_LINK_POLICY, OCF_QOS_SETUP,
+ QOS_SETUP_CP_SIZE, &cp) < 0) {
+ hci_close_dev(dd);
+ return FALSE;
+ }
+ DBG("Send Spreadtrum Qos Patch %s", qos_high ? "High" : "Low");
+
+ hci_close_dev(dd);
+
+ return TRUE;
+}
+#endif /* __SPRD_QOS_PATCH__ */
+
#ifdef TIZEN_WEARABLE
-static gboolean fix_role_to_master(bdaddr_t *dst, gboolean fix_to_master)
+static gboolean fix_role_to_master(const bdaddr_t *dst, gboolean fix_to_master)
{
int dd;
int err = 0;
@@ -1074,11 +1130,12 @@ static void avdtp_sep_set_state(struct avdtp *session,
avdtp_state_t state)
{
struct avdtp_stream *stream = sep->stream;
-#ifdef __TIZEN_PATCH__
-#if defined(__BROADCOM_QOS_PATCH__) || defined(TIZEN_WEARABLE)
- bdaddr_t *dst;
- dst = (bdaddr_t*)device_get_address(session->device);
+#ifdef __TIZEN_PATCH__
+#if (defined(__BROADCOM_QOS_PATCH__) || defined(TIZEN_WEARABLE)) || \
+ defined(__SPRD_QOS_PATCH__)
+ const bdaddr_t *dst;
+ dst = device_get_address(session->device);
#endif
#endif
avdtp_state_t old_state;
@@ -1114,7 +1171,10 @@ static void avdtp_sep_set_state(struct avdtp *session,
#ifdef __TIZEN_PATCH__
#ifdef __BROADCOM_QOS_PATCH__
send_broadcom_a2dp_qos(dst, FALSE);
-#endif /* __BROADCOM_QOS_PATCH__ */
+#elif defined(__SPRD_QOS_PATCH__)
+ if (old_state == AVDTP_STATE_STREAMING)
+ send_sprd_a2dp_qos(dst, FALSE);
+#endif
#ifdef TIZEN_WEARABLE
fix_role_to_master(dst, FALSE);
#endif /* TIZEN_WEARABLE */
@@ -1130,7 +1190,10 @@ static void avdtp_sep_set_state(struct avdtp *session,
#ifdef __TIZEN_PATCH__
#ifdef __BROADCOM_QOS_PATCH__
send_broadcom_a2dp_qos(dst, TRUE);
-#endif /* __BROADCOM_QOS_PATCH__ */
+#elif defined(__SPRD_QOS_PATCH__)
+ if (old_state == AVDTP_STATE_OPEN)
+ send_sprd_a2dp_qos(dst, TRUE);
+#endif
#ifdef TIZEN_WEARABLE
fix_role_to_master(dst, TRUE);
#endif /* TIZEN_WEARABLE */
@@ -1274,6 +1337,7 @@ static void connection_lost(struct avdtp *session, int err)
if (service)
btd_service_connecting_complete(service, -err);
#endif
+
g_slist_foreach(session->streams, (GFunc) release_stream, session);
session->streams = NULL;
@@ -1326,6 +1390,7 @@ static gboolean disconnect_timeout(gpointer user_data)
return FALSE;
}
#endif
+
session->dc_timer = 0;
stream_setup = session->stream_setup;
@@ -1366,7 +1431,11 @@ static gboolean disconnect_timeout(gpointer user_data)
return FALSE;
}
+#if defined __TIZEN_PATCH__ && defined SUPPORT_LOCAL_DEVICE_A2DP_SINK
+static void set_disconnect_timer(struct avdtp *session, gboolean disconn)
+#else
static void set_disconnect_timer(struct avdtp *session)
+#endif
{
#ifdef __TIZEN_PATCH__
char name[6];
@@ -1376,15 +1445,31 @@ static void set_disconnect_timer(struct avdtp *session)
#ifdef __TIZEN_PATCH__
device_get_name(session->device, name, sizeof(name));
- DBG("name : %s", name);
- if (g_str_equal(name, "VW BT"))
+ DBG("name : [%s]", name);
+ if (g_str_equal(name, "VW BT") || g_str_equal(name, "VW MI") ||
+ g_str_equal(name, "Seat ")) {
session->dc_timer = g_timeout_add_seconds(3, disconnect_timeout,
session);
- else
+ } else if (g_str_equal(name, "CAR M")) {
+ session->dc_timer = g_timeout_add(200, disconnect_timeout,
+ session);
+ } else {
#endif
+#if defined __TIZEN_PATCH__ && defined SUPPORT_LOCAL_DEVICE_A2DP_SINK
+ if (disconn == TRUE)
+ session->dc_timer = g_timeout_add(100,
+ disconnect_timeout,
+ session);
+ else
+ session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
+ disconnect_timeout,
+ session);
+#else
session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
disconnect_timeout,
session);
+#endif
+ }
}
void avdtp_unref(struct avdtp *session)
@@ -1399,7 +1484,11 @@ void avdtp_unref(struct avdtp *session)
if (session->ref > 0)
return;
+#if defined __TIZEN_PATCH__ && defined SUPPORT_LOCAL_DEVICE_A2DP_SINK
+ set_disconnect_timer(session, TRUE);
+#else
set_disconnect_timer(session);
+#endif
}
struct avdtp *avdtp_ref(struct avdtp *session)
@@ -2540,7 +2629,11 @@ static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
NULL);
if (session->stream_setup)
+#if defined __TIZEN_PATCH__ && defined SUPPORT_LOCAL_DEVICE_A2DP_SINK
+ set_disconnect_timer(session, FALSE);
+#else
set_disconnect_timer(session);
+#endif
} else if (session->pending_open)
handle_transport_connect(session, chan, session->imtu,
session->omtu);
@@ -2614,6 +2707,9 @@ static GIOChannel *l2cap_connect(struct avdtp *session)
device_get_address(session->device),
BT_IO_OPT_PSM, AVDTP_PSM,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+#if defined __TIZEN_PATCH__ && defined SUPPORT_LOCAL_DEVICE_A2DP_SINK
+ BT_IO_OPT_IMTU, 895,
+#endif
BT_IO_OPT_INVALID);
if (!io) {
error("%s", err->message);
@@ -3628,11 +3724,43 @@ int avdtp_start(struct avdtp *session, struct avdtp_stream *stream)
/* If timer already active wait it */
if (stream->start_timer)
return 0;
+#ifdef __TIZEN_PATCH__
+ else {
+ char address[18];
+ uint32_t timeout_sec = START_TIMEOUT;
+
+ ba2str(device_get_address(session->device), address);
+ /* For Bose headset (Bose AE2w) 2 seconds timeout is required to avoid AVDTP ABORT_CMD */
+ if (!strncasecmp(address, "00:0C:8A", 8))
+ timeout_sec = 2;
+ /* For Gear Circle, HS3000 headset, this headset doesn't initiate start command and
+ * when we add timer for 1 second so idle may trigger callback after 1.2 sec or
+ * 1.5 sec. So, don't timer for this headset.*/
+ if (!strncasecmp(address, "10:92:66", 8) ||
+ !strncasecmp(address, "A8:9F:BA", 8)) {
+ start_timeout(stream);
+ return 0;
+ }
+ /* Here we can't use Mac address as there are changing so check for name */
+ char name[10];
+ device_get_name(session->device, name, sizeof(name));
+ DBG("name : %s", name);
+ if (g_str_equal(name, "HS3000")) {
+ start_timeout(stream);
+ return 0;
+ }
+ stream->start_timer = g_timeout_add_seconds(timeout_sec,
+ start_timeout,
+ stream);
+ return 0;
+ }
+#else
stream->start_timer = g_timeout_add_seconds(START_TIMEOUT,
start_timeout,
stream);
return 0;
+#endif
}
if (stream->close_int == TRUE) {
@@ -3709,6 +3837,13 @@ int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream)
struct seid_req req;
int ret;
+ if (!stream && session->discover) {
+ /* Don't call cb since it being aborted */
+ session->discover->cb = NULL;
+ finalize_discovery(session, -ECANCELED);
+ return -EALREADY;
+ }
+
if (!g_slist_find(session->streams, stream))
return -EINVAL;
diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c
index a1e14114..72d49069 100644
--- a/profiles/audio/avrcp.c
+++ b/profiles/audio/avrcp.c
@@ -103,11 +103,13 @@
#define AVRCP_REQUEST_CONTINUING 0x40
#define AVRCP_ABORT_CONTINUING 0x41
#define AVRCP_SET_ABSOLUTE_VOLUME 0x50
+#define AVRCP_SET_ADDRESSED_PLAYER 0x60
#define AVRCP_SET_BROWSED_PLAYER 0x70
#define AVRCP_GET_FOLDER_ITEMS 0x71
#define AVRCP_CHANGE_PATH 0x72
#define AVRCP_GET_ITEM_ATTRIBUTES 0x73
#define AVRCP_PLAY_ITEM 0x74
+#define AVRCP_GET_TOTAL_NUMBER_OF_ITEMS 0x75
#define AVRCP_SEARCH 0x80
#define AVRCP_ADD_TO_NOW_PLAYING 0x90
#define AVRCP_GENERAL_REJECT 0xA0
@@ -135,6 +137,17 @@
#define AVRCP_CHARSET_UTF8 106
#define AVRCP_BROWSING_TIMEOUT 1
+#ifdef __TIZEN_PATCH__
+#define AVRCP_CT_VERSION 0x0103
+#define AVRCP_TG_VERSION 0x0103
+#else
+#define AVRCP_CT_VERSION 0x0106
+#define AVRCP_TG_VERSION 0x0105
+#endif
+#define AVRCP_SCOPE_MEDIA_PLAYER_LIST 0x00
+#define AVRCP_SCOPE_MEDIA_PLAYER_VFS 0x01
+#define AVRCP_SCOPE_SEARCH 0x02
+#define AVRCP_SCOPE_NOW_PLAYING 0x03
#if __BYTE_ORDER == __LITTLE_ENDIAN
@@ -174,6 +187,30 @@ struct avrcp_browsing_header {
} __attribute__ ((packed));
#define AVRCP_BROWSING_HEADER_LENGTH 3
+struct get_folder_items_rsp {
+ uint8_t status;
+ uint16_t uid_counter;
+ uint16_t num_items;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+struct folder_item {
+ uint8_t type;
+ uint16_t len;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+struct player_item {
+ uint16_t player_id;
+ uint8_t type;
+ uint32_t subtype;
+ uint8_t status;
+ uint8_t features[16];
+ uint16_t charset;
+ uint16_t namelen;
+ char name[0];
+} __attribute__ ((packed));
+
struct avrcp_server {
struct btd_adapter *adapter;
uint32_t tg_record_id;
@@ -203,8 +240,10 @@ struct avrcp_player {
uint64_t uid;
uint16_t uid_counter;
bool browsed;
+ bool addressed;
uint8_t *features;
char *path;
+ guint changed_id;
struct pending_list_items *p;
char *change_path;
@@ -240,6 +279,9 @@ struct avrcp {
uint8_t transaction;
uint8_t transaction_events[AVRCP_EVENT_LAST + 1];
struct pending_pdu *pending_pdu;
+#ifdef __TIZEN_PATCH__
+ uint32_t playback_status_id;
+#endif
};
struct passthrough_handler {
@@ -255,22 +297,24 @@ struct control_pdu_handler {
};
static GSList *servers = NULL;
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
static unsigned int avctp_id = 0;
-#endif
-#ifdef SUPPORT_AVRCP_TARGET
#ifdef __TIZEN_PATCH__
+#ifdef SUPPORT_AVRCP_TARGET
static uint16_t adapter_avrcp_tg_ver = 0;
#endif
-#endif
-
#ifdef SUPPORT_AVRCP_CONTROL
-#ifdef __TIZEN_PATCH__
static uint16_t adapter_avrcp_ct_ver = 0;
#endif
#endif
+/* Default feature bit mask for media player as per avctp.c:key_map */
+static const uint8_t features[16] = {
+ 0xF8, 0xBF, 0xFF, 0xBF, 0x1F,
+ 0xFB, 0x3F, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00 };
+
/* Company IDs supported by this device */
static uint32_t company_ids[] = {
IEEEID_BTSIG,
@@ -295,7 +339,7 @@ static sdp_record_t *avrcp_ct_record(void)
sdp_data_t *psm[2], *version, *features;
uint16_t lp = AVCTP_CONTROL_PSM, ap = AVCTP_BROWSING_PSM;
#ifdef __TIZEN_PATCH__
- uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0104;
+ uint16_t avctp_ver = 0x0104;
uint16_t feat = 0;
#ifdef ENABLE_AVRCP_CATEGORY1
feat = AVRCP_FEATURE_CATEGORY_1;
@@ -304,7 +348,7 @@ static sdp_record_t *avrcp_ct_record(void)
feat = feat | AVRCP_FEATURE_CATEGORY_2;
#endif
#else
- uint16_t avrcp_ver = 0x0105, avctp_ver = 0x0103;
+ uint16_t avctp_ver = 0x0103;
uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 |
AVRCP_FEATURE_CATEGORY_2 |
AVRCP_FEATURE_CATEGORY_3 |
@@ -360,9 +404,9 @@ static sdp_record_t *avrcp_ct_record(void)
/* Bluetooth Profile Descriptor List */
sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
- profile[0].version = avrcp_ver;
+ profile[0].version = AVRCP_CT_VERSION;
#ifdef __TIZEN_PATCH__
- adapter_avrcp_ct_ver = avrcp_ver;
+ adapter_avrcp_ct_ver = AVRCP_CT_VERSION;
#endif
pfseq = sdp_list_append(NULL, &profile[0]);
sdp_set_profile_descs(record, pfseq);
@@ -407,7 +451,7 @@ static sdp_record_t *avrcp_tg_record(void)
uint16_t lp = AVCTP_CONTROL_PSM;
uint16_t lp_browsing = AVCTP_BROWSING_PSM;
#ifdef __TIZEN_PATCH__
- uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0104;
+ uint16_t avctp_ver = 0x0104;
uint16_t feat = 0;
#ifdef ENABLE_AVRCP_CATEGORY1
feat = AVRCP_FEATURE_CATEGORY_1 |
@@ -417,7 +461,7 @@ static sdp_record_t *avrcp_tg_record(void)
feat = feat | AVRCP_FEATURE_CATEGORY_2;
#endif
#else
- uint16_t avrcp_ver = 0x0104, avctp_ver = 0x0103;
+ uint16_t avctp_ver = 0x0103;
uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 |
AVRCP_FEATURE_CATEGORY_2 |
AVRCP_FEATURE_CATEGORY_3 |
@@ -469,9 +513,9 @@ static sdp_record_t *avrcp_tg_record(void)
/* Bluetooth Profile Descriptor List */
sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
- profile[0].version = avrcp_ver;
+ profile[0].version = AVRCP_TG_VERSION;
#ifdef __TIZEN_PATCH__
- adapter_avrcp_tg_ver = avrcp_ver;
+ adapter_avrcp_tg_ver = AVRCP_TG_VERSION;
#endif
pfseq = sdp_list_append(NULL, &profile[0]);
sdp_set_profile_descs(record, pfseq);
@@ -693,6 +737,7 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id,
{
uint8_t buf[AVRCP_HEADER_LENGTH + 9];
struct avrcp_header *pdu = (void *) buf;
+ uint8_t code;
uint16_t size;
GSList *l;
int attr;
@@ -710,10 +755,19 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id,
set_company_id(pdu->company_id, IEEEID_BTSIG);
pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
- pdu->params[0] = id;
DBG("id=%u", id);
+ if (id != AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED && player->changed_id) {
+ code = AVC_CTYPE_REJECTED;
+ size = 1;
+ pdu->params[0] = AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED;
+ goto done;
+ }
+
+ code = AVC_CTYPE_CHANGED;
+ pdu->params[0] = id;
+
switch (id) {
case AVRCP_EVENT_STATUS_CHANGED:
size = 2;
@@ -774,11 +828,20 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id,
memcpy(&pdu->params[1], position_val, sizeof(uint32_t));
break;
#endif
+ case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED:
+ size = 5;
+ memcpy(&pdu->params[1], &player->id, sizeof(uint16_t));
+ memcpy(&pdu->params[3], &player->uid_counter, sizeof(uint16_t));
+ break;
+ case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED:
+ size = 1;
+ break;
default:
error("Unknown event %u", id);
return;
}
+done:
pdu->params_len = htons(size);
#ifdef __TIZEN_PATCH__
if (id == AVRCP_EVENT_PLAYBACK_POS_CHANGED &&
@@ -801,8 +864,9 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id,
err = avctp_send_vendordep(session->conn,
session->transaction_events[id],
- AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
+ code, AVC_SUBUNIT_PANEL,
buf, size + AVRCP_HEADER_LENGTH);
+
if (err < 0)
continue;
@@ -1418,6 +1482,22 @@ static uint8_t player_get_status(struct avrcp_player *player)
return play_status_to_val(value);
}
+static uint16_t player_get_id(struct avrcp_player *player)
+{
+ if (player == NULL)
+ return 0x0000;
+
+ return player->id;
+}
+
+static uint16_t player_get_uid_counter(struct avrcp_player *player)
+{
+ if (player == NULL)
+ return 0x0000;
+
+ return player->uid_counter;
+}
+
static uint8_t avrcp_handle_get_play_status(struct avrcp *session,
struct avrcp_header *pdu,
uint8_t transaction)
@@ -1533,7 +1613,6 @@ static const struct passthrough_handler passthrough_handlers[] = {
{ },
};
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
static bool handle_passthrough(struct avctp *conn, uint8_t op, bool pressed,
void *user_data)
{
@@ -1555,7 +1634,6 @@ static bool handle_passthrough(struct avctp *conn, uint8_t op, bool pressed,
return handler->func(session);
}
-#endif
#ifdef __TIZEN_PATCH__
void avrcp_stop_position_timer(void)
@@ -1649,6 +1727,16 @@ static uint8_t avrcp_handle_register_notification(struct avrcp *session,
pdu->params[len++] = val;
}
+ g_list_free(settings);
+
+ break;
+ case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED:
+ len = 5;
+ bt_put_be16(player_get_id(player), &pdu->params[1]);
+ bt_put_be16(player_get_uid_counter(player), &pdu->params[3]);
+ break;
+ case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED:
+ len = 1;
break;
case AVRCP_EVENT_VOLUME_CHANGED:
pdu->params[1] = media_transport_get_device_volume(dev);
@@ -1782,19 +1870,12 @@ static uint8_t avrcp_handle_set_absolute_volume(struct avrcp *session,
struct avrcp_header *pdu,
uint8_t transaction)
{
-#ifndef __TIZEN_PATCH__
- struct avrcp_player *player = session->controller->player;
-#else
- struct avrcp_player *player = target_get_player(session);
-#endif
uint16_t len = ntohs(pdu->params_len);
uint8_t volume;
if (len != 1)
goto err;
- if (!player)
- goto err;
volume = pdu->params[0] & 0x7F;
@@ -1808,6 +1889,90 @@ err:
return AVC_CTYPE_REJECTED;
}
+static struct avrcp_player *find_tg_player(struct avrcp *session, uint16_t id)
+{
+ struct avrcp_server *server = session->server;
+ GSList *l;
+
+ for (l = server->players; l; l = l->next) {
+ struct avrcp_player *player = l->data;
+
+ if (player->id == id)
+ return player;
+ }
+
+ return NULL;
+}
+
+static gboolean notify_addressed_player_changed(gpointer user_data)
+{
+ struct avrcp_player *player = user_data;
+ uint8_t events[6] = { AVRCP_EVENT_STATUS_CHANGED,
+ AVRCP_EVENT_TRACK_CHANGED,
+ AVRCP_EVENT_TRACK_REACHED_START,
+ AVRCP_EVENT_TRACK_REACHED_END,
+ AVRCP_EVENT_SETTINGS_CHANGED,
+ AVRCP_EVENT_PLAYBACK_POS_CHANGED
+ };
+ uint8_t i;
+
+ avrcp_player_event(player, AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED, NULL);
+
+ /*
+ * TG shall complete all player specific
+ * notifications with AV/C C-Type REJECTED
+ * with error code as Addressed Player Changed.
+ */
+ for (i = 0; i < sizeof(events); i++)
+ avrcp_player_event(player, events[i], NULL);
+
+ player->changed_id = 0;
+
+ return FALSE;
+}
+
+static uint8_t avrcp_handle_set_addressed_player(struct avrcp *session,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ struct avrcp_player *player;
+ uint16_t len = ntohs(pdu->params_len);
+ uint16_t player_id = 0;
+ uint8_t status;
+
+ if (len < 1) {
+ status = AVRCP_STATUS_INVALID_PARAM;
+ goto err;
+ }
+
+ player_id = bt_get_be16(&pdu->params[0]);
+ player = find_tg_player(session, player_id);
+ pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE;
+
+ if (player) {
+ player->addressed = true;
+ status = AVRCP_STATUS_SUCCESS;
+ pdu->params_len = htons(len);
+ pdu->params[0] = status;
+ } else {
+ status = AVRCP_STATUS_INVALID_PLAYER_ID;
+ goto err;
+ }
+
+ /* Don't emit player changed immediately since PTS expect the
+ * response of SetAddressedPlayer before the event.
+ */
+ player->changed_id = g_idle_add(notify_addressed_player_changed,
+ player);
+
+ return AVC_CTYPE_ACCEPTED;
+
+err:
+ pdu->params_len = htons(sizeof(status));
+ pdu->params[0] = status;
+ return AVC_CTYPE_REJECTED;
+}
+
static const struct control_pdu_handler control_handlers[] = {
{ AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
avrcp_handle_get_capabilities },
@@ -1839,10 +2004,11 @@ static const struct control_pdu_handler control_handlers[] = {
avrcp_handle_request_continuing },
{ AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL,
avrcp_handle_abort_continuing },
+ { AVRCP_SET_ADDRESSED_PLAYER, AVC_CTYPE_CONTROL,
+ avrcp_handle_set_addressed_player },
{ },
};
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
/* handle vendordep pdu inside an avctp packet */
static size_t handle_vendordep_pdu(struct avctp *conn, uint8_t transaction,
uint8_t *code, uint8_t *subunit,
@@ -1902,14 +2068,124 @@ err_metadata:
return AVRCP_HEADER_LENGTH + 1;
}
+static void avrcp_handle_media_player_list(struct avrcp *session,
+ struct avrcp_browsing_header *pdu,
+ uint32_t start_item, uint32_t end_item)
+{
+ struct avrcp_player *player = session->target->player;
+ struct get_folder_items_rsp *rsp;
+ const char *name = NULL;
+ GSList *l;
+
+ rsp = (void *)pdu->params;
+ rsp->status = AVRCP_STATUS_SUCCESS;
+ rsp->uid_counter = htons(player_get_uid_counter(player));
+ rsp->num_items = 0;
+ pdu->param_len = sizeof(*rsp);
+
+ for (l = g_slist_nth(session->server->players, start_item);
+ l; l = g_slist_next(l)) {
+ struct avrcp_player *player = l->data;
+ struct folder_item *folder;
+ struct player_item *item;
+ uint16_t namelen;
+
+ if (rsp->num_items == (end_item - start_item) + 1)
+ break;
+
+ folder = (void *)&pdu->params[pdu->param_len];
+ folder->type = 0x01; /* Media Player */
+
+ pdu->param_len += sizeof(*folder);
+
+ item = (void *)folder->data;
+ item->player_id = htons(player->id);
+ item->type = 0x01; /* Audio */
+ item->subtype = htonl(0x01); /* Audio Book */
+ item->status = player_get_status(player);
+ /* Assign Default Feature Bit Mask */
+ memcpy(&item->features, &features, sizeof(features));
+
+ item->charset = htons(AVRCP_CHARSET_UTF8);
+
+ name = player->cb->get_name(player->user_data);
+ namelen = strlen(name);
+ item->namelen = htons(namelen);
+ memcpy(item->name, name, namelen);
+
+ folder->len = htons(sizeof(*item) + namelen);
+ pdu->param_len += sizeof(*item) + namelen;
+ rsp->num_items++;
+ }
+
+ /* If no player could be found respond with an error */
+ if (!rsp->num_items)
+ goto failed;
+
+ rsp->num_items = htons(rsp->num_items);
+ pdu->param_len = htons(pdu->param_len);
+
+ return;
+
+failed:
+ pdu->params[0] = AVRCP_STATUS_OUT_OF_BOUNDS;
+ pdu->param_len = htons(1);
+}
+
+static void avrcp_handle_get_folder_items(struct avrcp *session,
+ struct avrcp_browsing_header *pdu,
+ uint8_t transaction)
+{
+ uint32_t start_item = 0;
+ uint32_t end_item = 0;
+ uint8_t scope;
+ uint8_t status = AVRCP_STATUS_SUCCESS;
+
+ if (ntohs(pdu->param_len) < 10) {
+ status = AVRCP_STATUS_INVALID_PARAM;
+ goto failed;
+ }
+
+ scope = pdu->params[0];
+ start_item = bt_get_be32(&pdu->params[1]);
+ end_item = bt_get_be32(&pdu->params[5]);
+
+ DBG("scope 0x%02x start_item 0x%08x end_item 0x%08x", scope,
+ start_item, end_item);
+
+ if (end_item < start_item) {
+ status = AVRCP_STATUS_INVALID_PARAM;
+ goto failed;
+ }
+
+ switch (scope) {
+ case AVRCP_SCOPE_MEDIA_PLAYER_LIST:
+ avrcp_handle_media_player_list(session, pdu,
+ start_item, end_item);
+ break;
+ case AVRCP_SCOPE_MEDIA_PLAYER_VFS:
+ case AVRCP_SCOPE_SEARCH:
+ case AVRCP_SCOPE_NOW_PLAYING:
+ default:
+ status = AVRCP_STATUS_INVALID_PARAM;
+ goto failed;
+ }
+
+ return;
+
+failed:
+ pdu->params[0] = status;
+ pdu->param_len = htons(1);
+}
+
static struct browsing_pdu_handler {
uint8_t pdu_id;
void (*func) (struct avrcp *session, struct avrcp_browsing_header *pdu,
uint8_t transaction);
} browsing_handlers[] = {
+ { AVRCP_GET_FOLDER_ITEMS, avrcp_handle_get_folder_items },
{ },
};
-#endif
size_t avrcp_browsing_general_reject(uint8_t *operands)
{
@@ -1924,7 +2200,6 @@ size_t avrcp_browsing_general_reject(uint8_t *operands)
return AVRCP_BROWSING_HEADER_LENGTH + sizeof(status);
}
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
static size_t handle_browsing_pdu(struct avctp *conn,
uint8_t transaction, uint8_t *operands,
size_t operand_count, void *user_data)
@@ -1948,7 +2223,6 @@ done:
handler->func(session, pdu, transaction);
return AVRCP_BROWSING_HEADER_LENGTH + ntohs(pdu->param_len);
}
-#endif
size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands)
{
@@ -2107,7 +2381,6 @@ static gboolean avrcp_player_value_rsp(struct avctp *conn,
return FALSE;
}
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
static void avrcp_get_current_player_value(struct avrcp *session,
uint8_t *attrs, uint8_t count)
{
@@ -2181,7 +2454,6 @@ static void avrcp_list_player_attributes(struct avrcp *session)
avrcp_list_player_attributes_rsp,
session);
}
-#endif
static void avrcp_parse_attribute_list(struct avrcp_player *player,
uint8_t *operands, uint8_t count)
@@ -2756,7 +3028,25 @@ static int ct_press(struct avrcp_player *player, uint8_t op)
return 0;
}
+#ifdef __TIZEN_PATCH__
+static int ct_release(struct avrcp_player *player, uint8_t op)
+{
+ DBG("+");
+ int err;
+ struct avrcp *session;
+
+ session = player->sessions->data;
+ if (session == NULL)
+ return -ENOTCONN;
+ err = avctp_send_release_passthrough(session->conn, op);
+ if (err < 0)
+ return err;
+
+ DBG("-");
+ return 0;
+}
+#endif
static int ct_play(struct media_player *mp, void *user_data)
{
struct avrcp_player *player = user_data;
@@ -2791,7 +3081,43 @@ static int ct_previous(struct media_player *mp, void *user_data)
return ct_press(player, AVC_BACKWARD);
}
+#ifdef __TIZEN_PATCH__
+static int ct_press_fast_forward(struct media_player *mp, void *user_data)
+{
+ DBG("+");
+ struct avrcp_player *player = user_data;
+
+ DBG("-");
+ return ct_press(player, AVC_FAST_FORWARD);
+}
+
+static int ct_release_fast_forward(struct media_player *mp, void *user_data)
+{
+ DBG("+");
+ struct avrcp_player *player = user_data;
+
+ DBG("-");
+ return ct_release(player, AVC_FAST_FORWARD);
+}
+
+static int ct_press_rewind(struct media_player *mp, void *user_data)
+{
+ DBG("+");
+ struct avrcp_player *player = user_data;
+
+ DBG("-");
+ return ct_press(player, AVC_REWIND);
+}
+static int ct_release_rewind(struct media_player *mp, void *user_data)
+{
+ DBG("+");
+ struct avrcp_player *player = user_data;
+
+ DBG("-");
+ return ct_release(player, AVC_REWIND);
+}
+#else
static int ct_fast_forward(struct media_player *mp, void *user_data)
{
struct avrcp_player *player = user_data;
@@ -2805,7 +3131,7 @@ static int ct_rewind(struct media_player *mp, void *user_data)
return ct_press(player, AVC_REWIND);
}
-
+#endif
static int ct_list_items(struct media_player *mp, const char *name,
uint32_t start, uint32_t end, void *user_data)
{
@@ -2953,7 +3279,7 @@ static void avrcp_play_item(struct avrcp *session, uint64_t uid)
length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
- avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS,
+ avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL,
AVC_SUBUNIT_PANEL, buf, length,
NULL, session);
}
@@ -2999,7 +3325,7 @@ static void avrcp_add_to_nowplaying(struct avrcp *session, uint64_t uid)
length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
- avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS,
+ avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL,
AVC_SUBUNIT_PANEL, buf, length,
NULL, session);
}
@@ -3025,6 +3351,78 @@ static int ct_add_to_nowplaying(struct media_player *mp, const char *name,
return 0;
}
+static gboolean avrcp_get_total_numberofitems_rsp(struct avctp *conn,
+ uint8_t *operands, size_t operand_count,
+ void *user_data)
+{
+ struct avrcp_browsing_header *pdu = (void *) operands;
+ struct avrcp *session = user_data;
+ struct avrcp_player *player = session->controller->player;
+ struct media_player *mp = player->user_data;
+ uint32_t num_of_items;
+
+ if (pdu == NULL)
+ return -ETIMEDOUT;
+
+ if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 7)
+ return -EINVAL;
+
+ if (pdu->params[0] == AVRCP_STATUS_OUT_OF_BOUNDS)
+ goto done;
+
+ player->uid_counter = get_be16(&pdu->params[1]);
+ num_of_items = get_be32(&pdu->params[3]);
+
+ if (!num_of_items)
+ return -EINVAL;
+
+done:
+ media_player_total_items_complete(mp, num_of_items);
+ return FALSE;
+}
+
+static void avrcp_get_total_numberofitems(struct avrcp *session)
+{
+ uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 7];
+ struct avrcp_player *player = session->controller->player;
+ struct avrcp_browsing_header *pdu = (void *) buf;
+
+ memset(buf, 0, sizeof(buf));
+
+ pdu->pdu_id = AVRCP_GET_TOTAL_NUMBER_OF_ITEMS;
+ pdu->param_len = htons(7 + sizeof(uint32_t));
+
+ pdu->params[0] = player->scope;
+
+ avctp_send_browsing_req(session->conn, buf, sizeof(buf),
+ avrcp_get_total_numberofitems_rsp, session);
+}
+
+static int ct_get_total_numberofitems(struct media_player *mp, const char *name,
+ void *user_data)
+{
+ struct avrcp_player *player = user_data;
+ struct avrcp *session;
+
+ session = player->sessions->data;
+
+ if (session->controller->version != 0x0106) {
+ error("version not supported");
+ return -1;
+ }
+
+ if (g_str_has_prefix(name, "/NowPlaying"))
+ player->scope = 0x03;
+ else if (g_str_has_suffix(name, "/search"))
+ player->scope = 0x02;
+ else
+ player->scope = 0x01;
+
+ avrcp_get_total_numberofitems(session);
+
+ return 0;
+}
+
static const struct media_player_callback ct_cbs = {
.set_setting = ct_set_setting,
.play = ct_play,
@@ -3032,15 +3430,32 @@ static const struct media_player_callback ct_cbs = {
.stop = ct_stop,
.next = ct_next,
.previous = ct_previous,
+#ifdef __TIZEN_PATCH__
+ .press_fast_forward = ct_press_fast_forward,
+ .release_fast_forward = ct_release_fast_forward,
+ .press_rewind = ct_press_rewind,
+ .release_rewind = ct_release_rewind,
+#else
.fast_forward = ct_fast_forward,
.rewind = ct_rewind,
+#endif
.list_items = ct_list_items,
.change_folder = ct_change_folder,
.search = ct_search,
.play_item = ct_play_item,
.add_to_nowplaying = ct_add_to_nowplaying,
+ .total_items = ct_get_total_numberofitems,
};
+static void set_ct_player(struct avrcp *session, struct avrcp_player *player)
+{
+ struct btd_service *service;
+
+ session->controller->player = player;
+ service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID);
+ control_set_player(service, media_player_get_path(player->user_data));
+}
+
static struct avrcp_player *create_ct_player(struct avrcp *session,
uint16_t id)
{
@@ -3062,7 +3477,7 @@ static struct avrcp_player *create_ct_player(struct avrcp *session,
player->destroy = (GDestroyNotify) media_player_destroy;
if (session->controller->player == NULL)
- session->controller->player = player;
+ set_ct_player(session, player);
session->controller->players = g_slist_prepend(
session->controller->players,
@@ -3157,6 +3572,9 @@ static void player_destroy(gpointer data)
avrcp_stop_position_timer();
#endif
+ if (player->changed_id > 0)
+ g_source_remove(player->changed_id);
+
g_slist_free(player->sessions);
g_free(player->path);
g_free(player->change_path);
@@ -3171,10 +3589,15 @@ static void player_remove(gpointer data)
for (l = player->sessions; l; l = l->next) {
struct avrcp *session = l->data;
+ struct avrcp_data *controller = session->controller;
- session->controller->players = g_slist_remove(
- session->controller->players,
- player);
+ controller->players = g_slist_remove(controller->players,
+ player);
+
+ /* Check if current player is being removed */
+ if (controller->player == player)
+ set_ct_player(session, g_slist_nth_data(
+ controller->players, 0));
}
player_destroy(player);
@@ -3225,9 +3648,6 @@ static gboolean avrcp_get_media_player_list_rsp(struct avctp *conn,
i += len;
}
- if (g_slist_find(removed, session->controller->player))
- session->controller->player = NULL;
-
g_slist_free_full(removed, player_remove);
return FALSE;
@@ -3241,6 +3661,8 @@ static void avrcp_get_media_player_list(struct avrcp *session)
memset(buf, 0, sizeof(buf));
pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS;
+ put_be32(0, &pdu->params[1]);
+ put_be32(UINT32_MAX, &pdu->params[5]);
pdu->param_len = htons(10);
avctp_send_browsing_req(session->conn, buf, sizeof(buf),
@@ -3292,6 +3714,17 @@ static void avrcp_track_changed(struct avrcp *session,
avrcp_get_element_attributes(session);
}
+static void avrcp_playback_pos_changed(struct avrcp *session,
+ struct avrcp_header *pdu)
+{
+ struct avrcp_player *player = session->controller->player;
+ struct media_player *mp = player->user_data;
+ uint32_t position;
+
+ position = get_be32(&pdu->params[1]);
+ media_player_set_position(mp, position);
+}
+
static void avrcp_setting_changed(struct avrcp *session,
struct avrcp_header *pdu)
{
@@ -3339,7 +3772,7 @@ static void avrcp_addressed_player_changed(struct avrcp *session,
}
player->uid_counter = get_be16(&pdu->params[3]);
- session->controller->player = player;
+ set_ct_player(session, player);
if (player->features != NULL)
return;
@@ -3385,6 +3818,9 @@ static gboolean avrcp_handle_event(struct avctp *conn,
case AVRCP_EVENT_TRACK_CHANGED:
avrcp_track_changed(session, pdu);
break;
+ case AVRCP_EVENT_PLAYBACK_POS_CHANGED:
+ avrcp_playback_pos_changed(session, pdu);
+ break;
case AVRCP_EVENT_SETTINGS_CHANGED:
avrcp_setting_changed(session, pdu);
break;
@@ -3416,6 +3852,14 @@ static void avrcp_register_notification(struct avrcp *session, uint8_t event)
pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE;
pdu->params[0] = event;
+
+ /*
+ * Set maximum interval possible for position changed as we only
+ * use it to resync.
+ */
+ if (event == AVRCP_EVENT_PLAYBACK_POS_CHANGED)
+ bt_put_be32(UINT32_MAX / 1000, &pdu->params[1]);
+
pdu->params_len = htons(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH);
length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
@@ -3425,7 +3869,37 @@ static void avrcp_register_notification(struct avrcp *session, uint8_t event)
avrcp_handle_event, session);
}
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
+#ifdef __TIZEN_PATCH__
+static char *avrcp_event_to_string(uint8_t event)
+{
+
+ switch (event) {
+ case AVRCP_EVENT_STATUS_CHANGED:
+ return "AVRCP EVENT STATUS CHANGED";
+ case AVRCP_EVENT_TRACK_CHANGED:
+ return "AVRCP EVENT TRACK CHANGED";
+ case AVRCP_EVENT_SETTINGS_CHANGED:
+ return "AVRCP EVENT SETTINGS CHANGED";
+ case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED:
+ return "AVRCP EVENT ADDRESSED PLAYER CHANGED";
+ case AVRCP_EVENT_UIDS_CHANGED:
+ return "AVRCP EVENT UIDS CHANGED";
+ case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED:
+ return "AVRCP EVENT AVAILABLE PLAYERS CHANGED";
+ case AVRCP_EVENT_VOLUME_CHANGED:
+ return "AVRCP EVENT VOLUME CHANGED";
+ default:
+ return "Unknown Event";
+ }
+}
+
+static gboolean avrcp_get_playback_status(struct avrcp *session)
+{
+ avrcp_get_play_status(session);
+ return TRUE;
+}
+#endif
+
static gboolean avrcp_get_capabilities_resp(struct avctp *conn,
uint8_t code, uint8_t subunit,
uint8_t *operands, size_t operand_count,
@@ -3453,10 +3927,13 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn,
uint8_t event = pdu->params[1 + count];
events |= (1 << event);
-
+#ifdef __TIZEN_PATCH__
+ DBG("Supported Event %s", avrcp_event_to_string(event));
+#endif
switch (event) {
case AVRCP_EVENT_STATUS_CHANGED:
case AVRCP_EVENT_TRACK_CHANGED:
+ case AVRCP_EVENT_PLAYBACK_POS_CHANGED:
case AVRCP_EVENT_SETTINGS_CHANGED:
#ifndef __TIZEN_PATCH__
case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED:
@@ -3483,7 +3960,12 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn,
if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED)))
avrcp_get_element_attributes(session);
-
+#ifdef __TIZEN_PATCH__
+ if ((events & (1 << AVRCP_EVENT_STATUS_CHANGED)) == 0) {
+ session->playback_status_id = g_timeout_add_seconds(1,
+ avrcp_get_playback_status, session);
+ }
+#endif
return FALSE;
}
@@ -3508,7 +3990,6 @@ static void avrcp_get_capabilities(struct avrcp *session)
avrcp_get_capabilities_resp,
session);
}
-#endif
static struct avrcp *find_session(GSList *list, struct btd_device *dev)
{
@@ -3522,7 +4003,6 @@ static struct avrcp *find_session(GSList *list, struct btd_device *dev)
return NULL;
}
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
static void destroy_browsing(void *data)
{
struct avrcp *session = data;
@@ -3596,7 +4076,6 @@ static void avrcp_connect_browsing(struct avrcp *session)
connect_browsing,
session);
}
-#endif
#ifdef SUPPORT_AVRCP_TARGET
static void target_init(struct avrcp *session)
@@ -3642,6 +4121,11 @@ static void target_init(struct avrcp *session)
return;
#endif
+ session->supported_events |=
+ (1 << AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED) |
+ (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED) |
+ (1 << AVRCP_EVENT_VOLUME_CHANGED);
+
/* Only check capabilities if controller is not supported */
if (session->controller == NULL)
avrcp_get_capabilities(session);
@@ -3664,13 +4148,6 @@ static void controller_init(struct avrcp *session)
return;
controller = data_init(session, AVRCP_TARGET_UUID);
-#ifdef __TIZEN_PATCH__
- /* Fix : NULL_RETURNS */
- if (controller == NULL) {
- error("controller is NULL");
- return;
- }
-#endif
session->controller = controller;
DBG("%p version 0x%04x", controller, controller->version);
@@ -3678,9 +4155,6 @@ static void controller_init(struct avrcp *session)
#ifdef __TIZEN_PATCH__
if ((controller->version >= 0x0104) && (adapter_avrcp_ct_ver >= 0x0104))
session->supported_events |= (1 << AVRCP_EVENT_VOLUME_CHANGED);
-#else
- if (controller->version >= 0x0104)
- session->supported_events |= (1 << AVRCP_EVENT_VOLUME_CHANGED);
#endif
service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID);
@@ -3714,7 +4188,6 @@ static void controller_init(struct avrcp *session)
}
#endif
-#if defined(SUPPORT_AVRCP_TARGET) || defined(SUPPORT_AVRCP_CONTROL)
static void session_init_control(struct avrcp *session)
{
session->passthrough_id = avctp_register_passthrough_handler(
@@ -3768,6 +4241,14 @@ static void session_destroy(struct avrcp *session, int err)
server->sessions = g_slist_remove(server->sessions, session);
+#ifdef __TIZEN_PATCH__
+ if (session->playback_status_id > 0) {
+ DBG("Removing the timer for playback status polling");
+ g_source_remove(session->playback_status_id);
+ session->playback_status_id = 0;
+ }
+#endif
+
session_abort_pending_pdu(session);
service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID);
@@ -3907,7 +4388,6 @@ static void avrcp_server_unregister(struct avrcp_server *server)
avctp_id = 0;
}
}
-#endif
struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter,
struct avrcp_player_cb *cb,
@@ -3917,12 +4397,14 @@ struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter,
struct avrcp_server *server;
struct avrcp_player *player;
GSList *l;
+ static uint16_t id = 0;
server = find_server(servers, adapter);
if (!server)
return NULL;
player = g_new0(struct avrcp_player, 1);
+ player->id = ++id;
player->server = server;
player->cb = cb;
player->user_data = user_data;
@@ -3945,6 +4427,9 @@ struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter,
}
}
+ avrcp_player_event(player,
+ AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED, NULL);
+
return player;
}
@@ -3967,6 +4452,9 @@ void avrcp_unregister_player(struct avrcp_player *player)
target->player = g_slist_nth_data(server->players, 0);
}
+ avrcp_player_event(player,
+ AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED, NULL);
+
player_destroy(player);
}
@@ -3992,7 +4480,53 @@ static gboolean avrcp_handle_set_volume(struct avctp *conn,
return FALSE;
}
-int avrcp_set_volume(struct btd_device *dev, uint8_t volume)
+static int avrcp_event(struct avrcp *session, uint8_t id, const void *data)
+{
+ uint8_t buf[AVRCP_HEADER_LENGTH + 2];
+ struct avrcp_header *pdu = (void *) buf;
+ uint8_t code;
+ uint16_t size;
+ int err;
+
+ /* Verify that the event is registered */
+ if (!(session->registered_events & (1 << id)))
+ return -ENOENT;
+
+ memset(buf, 0, sizeof(buf));
+
+ set_company_id(pdu->company_id, IEEEID_BTSIG);
+ pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
+ code = AVC_CTYPE_CHANGED;
+ pdu->params[0] = id;
+
+ DBG("id=%u", id);
+
+ switch (id) {
+ case AVRCP_EVENT_VOLUME_CHANGED:
+ size = 2;
+ memcpy(&pdu->params[1], data, sizeof(uint8_t));
+ break;
+ default:
+ error("Unknown event %u", id);
+ return -EINVAL;
+ }
+
+ pdu->params_len = htons(size);
+
+ err = avctp_send_vendordep(session->conn,
+ session->transaction_events[id],
+ code, AVC_SUBUNIT_PANEL,
+ buf, size + AVRCP_HEADER_LENGTH);
+ if (err < 0)
+ return err;
+
+ /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
+ session->registered_events ^= 1 << id;
+
+ return err;
+}
+
+int avrcp_set_volume(struct btd_device *dev, uint8_t volume, bool notify)
{
struct avrcp_server *server;
struct avrcp *session;
@@ -4004,18 +4538,23 @@ int avrcp_set_volume(struct btd_device *dev, uint8_t volume)
return -EINVAL;
session = find_session(server->sessions, dev);
- if (session == NULL || session->target == NULL)
+ if (session == NULL)
return -ENOTCONN;
- if (session->target->version < 0x0104)
+ if (notify) {
+ if (!session->target)
+ return -ENOTSUP;
+ return avrcp_event(session, AVRCP_EVENT_VOLUME_CHANGED,
+ &volume);
+ }
+
+ if (!session->controller || session->controller->version < 0x0104)
return -ENOTSUP;
memset(buf, 0, sizeof(buf));
set_company_id(pdu->company_id, IEEEID_BTSIG);
- DBG("volume=%u", volume);
-
pdu->pdu_id = AVRCP_SET_ABSOLUTE_VOLUME;
pdu->params[0] = volume;
pdu->params_len = htons(1);
@@ -4030,9 +4569,19 @@ static int avrcp_connect(struct btd_service *service)
{
struct btd_device *dev = btd_service_get_device(service);
const char *path = device_get_path(dev);
-
+#ifdef __TIZEN_PATCH__
+ char name[10];
+#endif
DBG("path %s", path);
+#ifdef __TIZEN_PATCH__
+ device_get_name(dev, name, sizeof(name));
+ DBG("name : %s", name);
+ if (g_str_equal(name, "PLT_M50")) {
+ DBG("Don't initiate avrcp connection with this headset");
+ return -ENOTSUP;
+ }
+#endif
return control_connect(service);
}
diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h
index 28074db5..86d310c7 100644
--- a/profiles/audio/avrcp.h
+++ b/profiles/audio/avrcp.h
@@ -74,9 +74,7 @@
#define AVRCP_EVENT_TRACK_CHANGED 0x02
#define AVRCP_EVENT_TRACK_REACHED_END 0x03
#define AVRCP_EVENT_TRACK_REACHED_START 0x04
-#ifdef __TIZEN_PATCH__
-#define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05
-#endif
+#define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05
#define AVRCP_EVENT_SETTINGS_CHANGED 0x08
#define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a
#define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b
@@ -95,6 +93,7 @@ struct avrcp_player_cb {
const char *(*get_status) (void *user_data);
uint32_t (*get_position) (void *user_data);
uint32_t (*get_duration) (void *user_data);
+ const char *(*get_name) (void *user_data);
void (*set_volume) (uint8_t volume, struct btd_device *dev,
void *user_data);
bool (*play) (void *user_data);
@@ -104,7 +103,7 @@ struct avrcp_player_cb {
bool (*previous) (void *user_data);
};
-int avrcp_set_volume(struct btd_device *dev, uint8_t volume);
+int avrcp_set_volume(struct btd_device *dev, uint8_t volume, bool notify);
struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter,
struct avrcp_player_cb *cb,
diff --git a/profiles/audio/control.c b/profiles/audio/control.c
index f4656d85..edc4a98c 100644
--- a/profiles/audio/control.c
+++ b/profiles/audio/control.c
@@ -59,6 +59,7 @@
#include "avctp.h"
#include "control.h"
+#include "player.h"
static GSList *devices = NULL;
@@ -68,6 +69,7 @@ struct control {
struct btd_service *target;
struct btd_service *remote;
unsigned int avctp_id;
+ const char *player;
};
static void state_changed(struct btd_device *dev, avctp_state_t old_state,
@@ -81,9 +83,12 @@ static void state_changed(struct btd_device *dev, avctp_state_t old_state,
switch (new_state) {
case AVCTP_STATE_DISCONNECTED:
control->session = NULL;
+ control->player = NULL;
g_dbus_emit_property_changed(conn, path,
AUDIO_CONTROL_INTERFACE, "Connected");
+ g_dbus_emit_property_changed(conn, path,
+ AUDIO_CONTROL_INTERFACE, "Player");
break;
case AVCTP_STATE_CONNECTING:
@@ -215,21 +220,46 @@ static gboolean control_property_get_connected(
return TRUE;
}
+static gboolean control_player_exists(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct control *control = data;
+
+ return control->player != NULL;
+}
+
+static gboolean control_get_player(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct control *control = data;
+
+ if (!control->player)
+ return FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
+ &control->player);
+
+ return TRUE;
+}
+
static const GDBusMethodTable control_methods[] = {
- { GDBUS_METHOD("Play", NULL, NULL, control_play) },
- { GDBUS_METHOD("Pause", NULL, NULL, control_pause) },
- { GDBUS_METHOD("Stop", NULL, NULL, control_stop) },
- { GDBUS_METHOD("Next", NULL, NULL, control_next) },
- { GDBUS_METHOD("Previous", NULL, NULL, control_previous) },
- { GDBUS_METHOD("VolumeUp", NULL, NULL, control_volume_up) },
- { GDBUS_METHOD("VolumeDown", NULL, NULL, control_volume_down) },
- { GDBUS_METHOD("FastForward", NULL, NULL, control_fast_forward) },
- { GDBUS_METHOD("Rewind", NULL, NULL, control_rewind) },
+ { GDBUS_DEPRECATED_METHOD("Play", NULL, NULL, control_play) },
+ { GDBUS_DEPRECATED_METHOD("Pause", NULL, NULL, control_pause) },
+ { GDBUS_DEPRECATED_METHOD("Stop", NULL, NULL, control_stop) },
+ { GDBUS_DEPRECATED_METHOD("Next", NULL, NULL, control_next) },
+ { GDBUS_DEPRECATED_METHOD("Previous", NULL, NULL, control_previous) },
+ { GDBUS_DEPRECATED_METHOD("VolumeUp", NULL, NULL, control_volume_up) },
+ { GDBUS_DEPRECATED_METHOD("VolumeDown", NULL, NULL,
+ control_volume_down) },
+ { GDBUS_DEPRECATED_METHOD("FastForward", NULL, NULL,
+ control_fast_forward) },
+ { GDBUS_DEPRECATED_METHOD("Rewind", NULL, NULL, control_rewind) },
{ }
};
static const GDBusPropertyTable control_properties[] = {
{ "Connected", "b", control_property_get_connected },
+ { "Player", "o", control_get_player, NULL, control_player_exists },
{ }
};
@@ -338,3 +368,22 @@ int control_init_remote(struct btd_service *service)
return 0;
}
+
+int control_set_player(struct btd_service *service, const char *path)
+{
+ struct control *control = btd_service_get_user_data(service);
+
+ if (!control->session)
+ return -ENOTCONN;
+
+ if (g_strcmp0(control->player, path) == 0)
+ return -EALREADY;
+
+ control->player = path;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ device_get_path(control->dev),
+ AUDIO_CONTROL_INTERFACE, "Player");
+
+ return 0;
+}
diff --git a/profiles/audio/control.h b/profiles/audio/control.h
index 4bda8968..aab2631b 100644
--- a/profiles/audio/control.h
+++ b/profiles/audio/control.h
@@ -32,3 +32,5 @@ void control_unregister(struct btd_service *service);
int control_connect(struct btd_service *service);
int control_disconnect(struct btd_service *service);
+
+int control_set_player(struct btd_service *service, const char *path);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index 35566829..dacdc5f4 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -62,6 +62,7 @@
#include "sink.h"
#endif
+
#define MEDIA_INTERFACE "org.bluez.Media1"
#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
#define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
@@ -130,6 +131,7 @@ struct media_player {
bool next;
bool previous;
bool control;
+ char *name;
};
static GSList *adapters = NULL;
@@ -504,16 +506,30 @@ static void media_sink_state_changed_cb(struct btd_service *service,
if ((old_state == SINK_STATE_PLAYING) &&
(new_state == SINK_STATE_CONNECTED)) {
+#ifdef __TIZEN_PATCH__
+ struct btd_device *device = btd_service_get_device(service);
+ char name[20] = {0,};
+#endif
+
/* Check AVRCP play back status */
if (g_strcmp0(mp->status, "playing") != 0)
return;
media_stop_suspend_timer();
+#ifdef __TIZEN_PATCH__
+ device_get_name(device, name, sizeof(name));
+ DBG("Name : %s", name);
+
+ if (g_str_has_prefix(name, "LG HBS") != TRUE) {
+#endif
/* PlayBackStatus is still PLAYING; start a timer */
suspend_timer_id = g_timeout_add_seconds(SINK_SUSPEND_TIMEOUT,
media_reset_mp_status, mp);
DBG("SINK SUSPEND TIMEOUT started");
+#ifdef __TIZEN_PATCH__
+ }
+#endif
}
/* Check if A2DP streaming is started */
@@ -521,7 +537,11 @@ static void media_sink_state_changed_cb(struct btd_service *service,
(new_state == SINK_STATE_PLAYING)) {
struct btd_device *device = btd_service_get_device(service);
+#ifdef __TIZEN_PATCH__
+ char name[20] = {0,};
+#else
char name[20];
+#endif
media_stop_suspend_timer();
@@ -705,8 +725,8 @@ static void config_cb(struct media_endpoint *endpoint, void *ret, int size,
void *user_data)
{
struct a2dp_config_data *data = user_data;
-
- data->cb(data->setup, ret ? TRUE : FALSE);
+ gboolean *ret_value = ret;
+ data->cb(data->setup, ret_value ? *ret_value : FALSE);
}
static int set_config(struct a2dp_sep *sep, uint8_t *configuration,
@@ -1153,6 +1173,7 @@ static void media_player_free(gpointer data)
g_free(mp->sender);
g_free(mp->path);
g_free(mp->status);
+ g_free(mp->name);
g_free(mp);
}
@@ -1201,6 +1222,13 @@ static const char *get_setting(const char *key, void *user_data)
return g_hash_table_lookup(mp->settings, key);
}
+static const char *get_player_name(void *user_data)
+{
+ struct media_player *mp = user_data;
+
+ return mp->name;
+}
+
static void set_shuffle_setting(DBusMessageIter *iter, const char *value)
{
const char *key = "Shuffle";
@@ -1466,6 +1494,7 @@ static struct avrcp_player_cb player_cb = {
.get_position = get_position,
.get_duration = get_duration,
.get_status = get_status,
+ .get_name = get_player_name,
.set_volume = set_volume,
.play = play,
.stop = stop,
@@ -1506,7 +1535,8 @@ static gboolean set_status(struct media_player *mp, DBusMessageIter *iter)
avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, mp->status);
#ifdef __TIZEN_PATCH__
- if (strcasecmp(mp->status, "reverse-seek") != 0) {
+ if (strcasecmp(mp->status, "reverse-seek") != 0 &&
+ strcasecmp(mp->status, "playing") != 0) {
playback_position = get_position(mp);
avrcp_player_event(mp->player, AVRCP_EVENT_PLAYBACK_POS_CHANGED,
&playback_position);
@@ -1877,6 +1907,25 @@ static gboolean set_flag(struct media_player *mp, DBusMessageIter *iter,
return TRUE;
}
+static gboolean set_name(struct media_player *mp, DBusMessageIter *iter)
+{
+ const char *value;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(iter, &value);
+
+ if (g_strcmp0(mp->name, value) == 0)
+ return TRUE;
+
+ g_free(mp->name);
+
+ mp->name = g_strdup(value);
+
+ return TRUE;
+}
+
static gboolean set_player_property(struct media_player *mp, const char *key,
DBusMessageIter *entry)
{
@@ -1917,6 +1966,9 @@ static gboolean set_player_property(struct media_player *mp, const char *key,
if (strcasecmp(key, "CanControl") == 0)
return set_flag(mp, &var, &mp->control);
+ if (strcasecmp(key, "Identity") == 0)
+ return set_name(mp, &var);
+
DBG("%s not supported, ignoring", key);
return TRUE;
@@ -2105,19 +2157,11 @@ static const GDBusMethodTable media_methods[] = {
NULL, register_endpoint) },
{ GDBUS_METHOD("UnregisterEndpoint",
GDBUS_ARGS({ "endpoint", "o" }), NULL, unregister_endpoint) },
-#ifndef __TIZEN_PATCH__
- { GDBUS_EXPERIMENTAL_METHOD("RegisterPlayer",
- GDBUS_ARGS({ "player", "o" }, { "properties", "a{sv}" }),
- NULL, register_player) },
- { GDBUS_EXPERIMENTAL_METHOD("UnregisterPlayer",
- GDBUS_ARGS({ "player", "o" }), NULL, unregister_player) },
-#else
{ GDBUS_METHOD("RegisterPlayer",
GDBUS_ARGS({ "player", "o" }, { "properties", "a{sv}" }),
NULL, register_player) },
{ GDBUS_METHOD("UnregisterPlayer",
GDBUS_ARGS({ "player", "o" }), NULL, unregister_player) },
-#endif /* __TIZEN_PATCH__ */
{ },
};
diff --git a/profiles/audio/player.c b/profiles/audio/player.c
index 94eb2eba..e7ee747f 100644
--- a/profiles/audio/player.c
+++ b/profiles/audio/player.c
@@ -534,7 +534,83 @@ static DBusMessage *media_player_previous(DBusConnection *conn,
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
+#ifdef __TIZEN_PATCH__
+static DBusMessage *media_player_press_fast_forward(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBG("+");
+ struct media_player *mp = data;
+ struct player_callback *cb = mp->cb;
+ int err;
+
+ if (cb->cbs->press_fast_forward == NULL)
+ return btd_error_not_supported(msg);
+
+ err = cb->cbs->press_fast_forward(mp, cb->user_data);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ DBG("-");
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+static DBusMessage *media_player_release_fast_forward(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBG("+");
+ struct media_player *mp = data;
+ struct player_callback *cb = mp->cb;
+ int err;
+
+ if (cb->cbs->release_fast_forward == NULL)
+ return btd_error_not_supported(msg);
+
+ err = cb->cbs->release_fast_forward(mp, cb->user_data);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ DBG("-");
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *media_player_press_rewind(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ DBG("+");
+ struct media_player *mp = data;
+ struct player_callback *cb = mp->cb;
+ int err;
+
+ if (cb->cbs->press_rewind == NULL)
+ return btd_error_not_supported(msg);
+
+ err = cb->cbs->press_rewind(mp, cb->user_data);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ DBG("-");
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *media_player_release_rewind(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ DBG("+");
+ struct media_player *mp = data;
+ struct player_callback *cb = mp->cb;
+ int err;
+
+ if (cb->cbs->release_rewind == NULL)
+ return btd_error_not_supported(msg);
+
+ err = cb->cbs->release_rewind(mp, cb->user_data);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ DBG("-");
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+#else
static DBusMessage *media_player_fast_forward(DBusConnection *conn,
DBusMessage *msg, void *data)
{
@@ -568,7 +644,7 @@ static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg,
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
-
+#endif
static void parse_folder_list(gpointer data, gpointer user_data)
{
struct media_item *item = data;
@@ -702,14 +778,38 @@ done:
folder->msg = NULL;
}
+void media_player_total_items_complete(struct media_player *mp,
+ uint32_t num_of_items)
+{
+ struct media_folder *folder = mp->scope;
+
+ if (folder == NULL || folder->msg == NULL)
+ return;
+
+ if (folder->number_of_items != num_of_items) {
+ folder->number_of_items = num_of_items;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ mp->path, MEDIA_FOLDER_INTERFACE,
+ "NumberOfItems");
+ }
+}
+
static const GDBusMethodTable media_player_methods[] = {
{ GDBUS_METHOD("Play", NULL, NULL, media_player_play) },
{ GDBUS_METHOD("Pause", NULL, NULL, media_player_pause) },
{ GDBUS_METHOD("Stop", NULL, NULL, media_player_stop) },
{ GDBUS_METHOD("Next", NULL, NULL, media_player_next) },
{ GDBUS_METHOD("Previous", NULL, NULL, media_player_previous) },
+#ifdef __TIZEN_PATCH__
+ { GDBUS_METHOD("PressFastForward", NULL, NULL, media_player_press_fast_forward) },
+ { GDBUS_METHOD("ReleaseFastForward", NULL, NULL, media_player_release_fast_forward) },
+ { GDBUS_METHOD("PressRewind", NULL, NULL, media_player_press_rewind) },
+ { GDBUS_METHOD("ReleaseRewind", NULL, NULL, media_player_release_rewind) },
+#else
{ GDBUS_METHOD("FastForward", NULL, NULL, media_player_fast_forward) },
{ GDBUS_METHOD("Rewind", NULL, NULL, media_player_rewind) },
+#endif
{ }
};
@@ -891,6 +991,9 @@ static void media_folder_destroy(void *data)
static void media_player_change_scope(struct media_player *mp,
struct media_folder *folder)
{
+ struct player_callback *cb = mp->cb;
+ int err;
+
if (mp->scope == folder)
return;
@@ -920,10 +1023,19 @@ cleanup:
done:
mp->scope = folder;
+ if (cb->cbs->total_items) {
+ err = cb->cbs->total_items(mp, folder->item->name,
+ cb->user_data);
+ if (err < 0)
+ DBG("Failed to get total num of items");
+ } else {
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ mp->path, MEDIA_FOLDER_INTERFACE,
+ "NumberOfItems");
+ }
+
g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
MEDIA_FOLDER_INTERFACE, "Name");
- g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
- MEDIA_FOLDER_INTERFACE, "NumberOfItems");
}
static struct media_folder *find_folder(GSList *folders, const char *pattern)
@@ -1164,6 +1276,11 @@ struct media_player *media_player_controller_create(const char *path,
return mp;
}
+const char *media_player_get_path(struct media_player *mp)
+{
+ return mp->path;
+}
+
void media_player_set_duration(struct media_player *mp, uint32_t duration)
{
char *value, *curval;
@@ -1297,17 +1414,34 @@ void media_player_set_metadata(struct media_player *mp,
void *data, size_t len)
{
char *value, *curval;
+#ifdef __TIZEN_PATCH__
+ char *end, *temp;
+#endif
value = g_strndup(data, len);
+#ifdef __TIZEN_PATCH__
+ temp = value;
+ while (g_utf8_validate(temp, -1, &end) == FALSE) {
+ temp = g_utf8_find_next_char(end, NULL);
+ if (temp == NULL) {
+ *end = '\0';
+ break;
+ }
+ strcpy(end, temp);
+ temp = end;
+ }
+#endif
+
DBG("%s: %s", key, value);
curval = g_hash_table_lookup(mp->track, key);
+#ifndef __TIZEN_PATCH__
if (g_strcmp0(curval, value) == 0) {
g_free(value);
return;
}
-
+#endif
if (mp->process_id == 0) {
g_hash_table_remove_all(mp->track);
mp->process_id = g_idle_add(process_metadata_changed, mp);
diff --git a/profiles/audio/player.h b/profiles/audio/player.h
index ac2a3daf..c560ec2a 100644
--- a/profiles/audio/player.h
+++ b/profiles/audio/player.h
@@ -52,8 +52,15 @@ struct media_player_callback {
int (*stop) (struct media_player *mp, void *user_data);
int (*next) (struct media_player *mp, void *user_data);
int (*previous) (struct media_player *mp, void *user_data);
+#ifdef __TIZEN_PATCH__
+ int (*press_fast_forward) (struct media_player *mp, void *user_data);
+ int (*release_fast_forward) (struct media_player *mp, void *user_data);
+ int (*press_rewind) (struct media_player *mp, void *user_data);
+ int (*release_rewind) (struct media_player *mp, void *user_data);
+#else
int (*fast_forward) (struct media_player *mp, void *user_data);
int (*rewind) (struct media_player *mp, void *user_data);
+#endif
int (*list_items) (struct media_player *mp, const char *name,
uint32_t start, uint32_t end, void *user_data);
int (*change_folder) (struct media_player *mp, const char *path,
@@ -64,10 +71,13 @@ struct media_player_callback {
uint64_t uid, void *user_data);
int (*add_to_nowplaying) (struct media_player *mp, const char *name,
uint64_t uid, void *user_data);
+ int (*total_items) (struct media_player *mp, const char *name,
+ void *user_data);
};
struct media_player *media_player_controller_create(const char *path,
uint16_t id);
+const char *media_player_get_path(struct media_player *mp);
void media_player_destroy(struct media_player *mp);
void media_player_set_duration(struct media_player *mp, uint32_t duration);
void media_player_set_position(struct media_player *mp, uint32_t position);
@@ -104,6 +114,8 @@ void media_player_list_complete(struct media_player *mp, GSList *items,
void media_player_change_folder_complete(struct media_player *player,
const char *path, int ret);
void media_player_search_complete(struct media_player *mp, int ret);
+void media_player_total_items_complete(struct media_player *mp,
+ uint32_t num_of_items);
void media_player_set_callbacks(struct media_player *mp,
const struct media_player_callback *cbs,
diff --git a/profiles/audio/sink.c b/profiles/audio/sink.c
index 78a68872..e2751abc 100644
--- a/profiles/audio/sink.c
+++ b/profiles/audio/sink.c
@@ -242,6 +242,8 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp
struct sink *sink = user_data;
int id, perr;
+ sink->connect_id = 0;
+
if (err) {
avdtp_unref(sink->session);
sink->session = NULL;
@@ -287,7 +289,9 @@ gboolean sink_setup_stream(struct btd_service *service, struct avdtp *session)
if (!sink->session)
return FALSE;
- if (avdtp_discover(sink->session, discovery_complete, sink) < 0)
+ sink->connect_id = a2dp_discover(sink->session, discovery_complete,
+ sink);
+ if (sink->connect_id == 0)
return FALSE;
return TRUE;
@@ -441,7 +445,7 @@ int sink_disconnect(struct btd_service *service)
if (sink->connect_id > 0) {
a2dp_cancel(sink->connect_id);
sink->connect_id = 0;
- btd_service_connecting_complete(sink->service, -ECANCELED);
+ btd_service_disconnecting_complete(sink->service, 0);
avdtp_unref(sink->session);
sink->session = NULL;
diff --git a/profiles/audio/source.c b/profiles/audio/source.c
index b235a7d9..372b1320 100644
--- a/profiles/audio/source.c
+++ b/profiles/audio/source.c
@@ -227,6 +227,8 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp
struct source *source = user_data;
int id, perr;
+ source->connect_id = 0;
+
if (err) {
avdtp_unref(source->session);
source->session = NULL;
@@ -273,7 +275,9 @@ gboolean source_setup_stream(struct btd_service *service,
if (!source->session)
return FALSE;
- if (avdtp_discover(source->session, discovery_complete, source) < 0)
+ source->connect_id = a2dp_discover(source->session, discovery_complete,
+ source);
+ if (source->connect_id == 0)
return FALSE;
return TRUE;
@@ -398,7 +402,7 @@ int source_disconnect(struct btd_service *service)
if (source->connect_id > 0) {
a2dp_cancel(source->connect_id);
source->connect_id = 0;
- btd_service_connecting_complete(source->service, -ECANCELED);
+ btd_service_disconnecting_complete(source->service, 0);
avdtp_unref(source->session);
source->session = NULL;
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 91b9cae9..9c18edd0 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -676,6 +676,7 @@ static void set_volume(const GDBusPropertyTable *property,
struct media_transport *transport = data;
struct a2dp_transport *a2dp = transport->data;
uint16_t volume;
+ bool notify;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) {
g_dbus_pending_property_error(id,
@@ -693,12 +694,21 @@ static void set_volume(const GDBusPropertyTable *property,
return;
}
- if (a2dp->volume != volume)
- avrcp_set_volume(transport->device, volume);
+ g_dbus_pending_property_success(id);
+
+ if (a2dp->volume == volume)
+ return;
a2dp->volume = volume;
- g_dbus_pending_property_success(id);
+ notify = transport->source_watch ? true : false;
+ if (notify)
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ transport->path,
+ MEDIA_TRANSPORT_INTERFACE,
+ "Volume");
+
+ avrcp_set_volume(transport->device, volume, notify);
}
static const GDBusMethodTable transport_methods[] = {
@@ -840,7 +850,6 @@ static int media_transport_init_sink(struct media_transport *transport)
transport->destroy = destroy_a2dp;
a2dp->volume = 127;
- avrcp_set_volume(transport->device, a2dp->volume);
transport->source_watch = source_add_state_cb(service,
source_state_changed,
transport);
diff --git a/profiles/battery/bas.c b/profiles/battery/bas.c
new file mode 100644
index 00000000..de369fd3
--- /dev/null
+++ b/profiles/battery/bas.c
@@ -0,0 +1,340 @@
+/*
+ *
+ * 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 "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);
+
+ g_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 = 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);
+ 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;
+}
diff --git a/profiles/battery/bas.h b/profiles/battery/bas.h
new file mode 100644
index 00000000..3e175b5b
--- /dev/null
+++ b/profiles/battery/bas.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * 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
+ *
+ */
+
+struct bt_bas;
+
+struct bt_bas *bt_bas_new(void *primary);
+
+struct bt_bas *bt_bas_ref(struct bt_bas *bas);
+void bt_bas_unref(struct bt_bas *bas);
+
+bool bt_bas_attach(struct bt_bas *bas, void *gatt);
+void bt_bas_detach(struct bt_bas *bas);
diff --git a/profiles/deviceinfo/deviceinfo.c b/profiles/deviceinfo/deviceinfo.c
index a0e9951e..d1f51a02 100644
--- a/profiles/deviceinfo/deviceinfo.c
+++ b/profiles/deviceinfo/deviceinfo.c
@@ -3,6 +3,7 @@
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2012 Texas Instruments, Inc.
+ * Copyright (C) 2015 Google 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
@@ -38,161 +39,113 @@
#include "src/device.h"
#include "src/profile.h"
#include "src/service.h"
-#include "src/shared/util.h"
#include "attrib/gattrib.h"
-#include "src/attio.h"
+#include "src/shared/util.h"
+#include "src/shared/queue.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
#include "attrib/att.h"
-#include "attrib/gatt.h"
#include "src/log.h"
#define PNP_ID_SIZE 7
-struct deviceinfo {
- struct btd_device *dev; /* Device reference */
- GAttrib *attrib; /* GATT connection */
- guint attioid; /* Att watcher id */
- struct att_range *svc_range; /* DeviceInfo range */
- GSList *chars; /* Characteristics */
-};
-
-struct characteristic {
- struct gatt_char attr; /* Characteristic */
- struct deviceinfo *d; /* deviceinfo where the char belongs */
-};
-
-static void deviceinfo_driver_remove(struct btd_service *service)
+static void read_pnpid_cb(bool success, uint8_t att_ecode, const uint8_t *value,
+ uint16_t length, void *user_data)
{
- struct deviceinfo *d = btd_service_get_user_data(service);
-
- if (d->attioid > 0)
- btd_device_remove_attio_callback(d->dev, d->attioid);
-
- if (d->attrib != NULL)
- g_attrib_unref(d->attrib);
-
- g_slist_free_full(d->chars, g_free);
+ struct btd_device *device = user_data;
- btd_device_unref(d->dev);
- g_free(d->svc_range);
- g_free(d);
-}
-
-static void read_pnpid_cb(guint8 status, const guint8 *pdu, guint16 len,
- gpointer user_data)
-{
- struct characteristic *ch = user_data;
- uint8_t value[PNP_ID_SIZE];
- ssize_t vlen;
-
- if (status != 0) {
- error("Error reading PNP_ID value: %s", att_ecode2str(status));
+ if (!success) {
+ error("Error reading PNP_ID value: %s",
+ att_ecode2str(att_ecode));
return;
}
- vlen = dec_read_resp(pdu, len, value, sizeof(value));
- if (vlen < 0) {
- error("Error reading PNP_ID: Protocol error");
- return;
- }
-
- if (vlen < 7) {
+ if (length < PNP_ID_SIZE) {
error("Error reading PNP_ID: Invalid pdu length received");
return;
}
- btd_device_set_pnpid(ch->d->dev, value[0], get_le16(&value[1]),
+ btd_device_set_pnpid(device, value[0], get_le16(&value[1]),
get_le16(&value[3]), get_le16(&value[5]));
}
-static void process_deviceinfo_char(struct characteristic *ch)
+static void handle_pnpid(struct btd_device *device, uint16_t value_handle)
{
- if (g_strcmp0(ch->attr.uuid, PNPID_UUID) == 0)
- gatt_read_char(ch->d->attrib, ch->attr.value_handle,
- read_pnpid_cb, ch);
+ struct bt_gatt_client *client = btd_device_get_gatt_client(device);
+
+ if (!bt_gatt_client_read_value(client, value_handle,
+ read_pnpid_cb, device, NULL))
+ DBG("Failed to send request to read pnpid");
}
-static void configure_deviceinfo_cb(uint8_t status, GSList *characteristics,
+static void handle_characteristic(struct gatt_db_attribute *attr,
void *user_data)
{
- struct deviceinfo *d = user_data;
- GSList *l;
+ struct btd_device *device = user_data;
+ uint16_t value_handle;
+ bt_uuid_t uuid, pnpid_uuid;
+
+ bt_string_to_uuid(&pnpid_uuid, PNPID_UUID);
- if (status != 0) {
- error("Discover deviceinfo characteristics: %s",
- att_ecode2str(status));
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL,
+ &uuid)) {
+ error("Failed to obtain characteristic data");
return;
}
- for (l = characteristics; l; l = l->next) {
- struct gatt_char *c = l->data;
- struct characteristic *ch;
+ if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0)
+ handle_pnpid(device, value_handle);
+ else {
+ char uuid_str[MAX_LEN_UUID_STR];
- ch = g_new0(struct characteristic, 1);
- ch->attr.handle = c->handle;
- ch->attr.properties = c->properties;
- ch->attr.value_handle = c->value_handle;
- memcpy(ch->attr.uuid, c->uuid, MAX_LEN_UUID_STR + 1);
- ch->d = d;
-
- d->chars = g_slist_append(d->chars, ch);
-
- process_deviceinfo_char(ch);
+ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
+ DBG("Unsupported characteristic: %s", uuid_str);
}
}
-static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
-{
- struct deviceinfo *d = user_data;
- d->attrib = g_attrib_ref(attrib);
+static void foreach_deviceinfo_service(struct gatt_db_attribute *attr,
+ void *user_data)
+{
+ struct btd_device *device = user_data;
- gatt_discover_char(d->attrib, d->svc_range->start, d->svc_range->end,
- NULL, configure_deviceinfo_cb, d);
+ gatt_db_service_foreach_char(attr, handle_characteristic, device);
}
-static void attio_disconnected_cb(gpointer user_data)
+static int deviceinfo_driver_probe(struct btd_service *service)
{
- struct deviceinfo *d = user_data;
-
- g_attrib_unref(d->attrib);
- d->attrib = NULL;
+ return 0;
}
-static int deviceinfo_register(struct btd_service *service,
- struct gatt_primary *prim)
+static void deviceinfo_driver_remove(struct btd_service *service)
{
- struct btd_device *device = btd_service_get_device(service);
- struct deviceinfo *d;
-
- d = g_new0(struct deviceinfo, 1);
- d->dev = btd_device_ref(device);
- d->svc_range = g_new0(struct att_range, 1);
- d->svc_range->start = prim->range.start;
- d->svc_range->end = prim->range.end;
-
- btd_service_set_user_data(service, d);
-
- d->attioid = btd_device_add_attio_callback(device, attio_connected_cb,
- attio_disconnected_cb, d);
- return 0;
}
-static int deviceinfo_driver_probe(struct btd_service *service)
+
+static int deviceinfo_driver_accept(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
- struct gatt_primary *prim;
+ struct gatt_db *db = btd_device_get_gatt_db(device);
+ char addr[18];
+ bt_uuid_t deviceinfo_uuid;
- prim = btd_device_get_primary(device, DEVICE_INFORMATION_UUID);
- if (prim == NULL)
- return -EINVAL;
+ ba2str(device_get_address(device), addr);
+ DBG("deviceinfo profile accept (%s)", addr);
- return deviceinfo_register(service, prim);
+ /* Handle the device info service */
+ bt_string_to_uuid(&deviceinfo_uuid, DEVICE_INFORMATION_UUID);
+ gatt_db_foreach_service(db, &deviceinfo_uuid,
+ foreach_deviceinfo_service, device);
+
+ return 0;
}
static struct btd_profile deviceinfo_profile = {
.name = "deviceinfo",
.remote_uuid = DEVICE_INFORMATION_UUID,
+ .external = true,
.device_probe = deviceinfo_driver_probe,
- .device_remove = deviceinfo_driver_remove
+ .device_remove = deviceinfo_driver_remove,
+ .accept = deviceinfo_driver_accept,
};
static int deviceinfo_init(void)
diff --git a/profiles/deviceinfo/dis.c b/profiles/deviceinfo/dis.c
new file mode 100644
index 00000000..91c5d392
--- /dev/null
+++ b/profiles/deviceinfo/dis.c
@@ -0,0 +1,293 @@
+/*
+ *
+ * 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 "attrib/gattrib.h"
+#include "attrib/att.h"
+#include "attrib/gatt.h"
+
+#include "profiles/deviceinfo/dis.h"
+
+#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);
+}
+
+struct bt_dis *bt_dis_new(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 || !primary)
+ 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);
+
+ 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;
+}
diff --git a/profiles/deviceinfo/dis.h b/profiles/deviceinfo/dis.h
new file mode 100644
index 00000000..faf27b3d
--- /dev/null
+++ b/profiles/deviceinfo/dis.h
@@ -0,0 +1,39 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute 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 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
+ * 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
+ *
+ */
+
+struct bt_dis;
+
+struct bt_dis *bt_dis_new(void *primary);
+
+struct bt_dis *bt_dis_ref(struct bt_dis *dis);
+void bt_dis_unref(struct bt_dis *dis);
+
+bool bt_dis_attach(struct bt_dis *dis, void *gatt);
+void bt_dis_detach(struct bt_dis *dis);
+
+typedef void (*bt_dis_notify) (uint8_t source, uint16_t vendor,
+ uint16_t product, uint16_t version,
+ void *user_data);
+
+bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func,
+ void *user_data);
diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c
index 09aa832e..35e47530 100644
--- a/profiles/gap/gas.c
+++ b/profiles/gap/gas.c
@@ -53,7 +53,6 @@
struct gas {
struct btd_device *device;
struct gatt_db *db;
- unsigned int db_id;
struct bt_gatt_client *client;
struct gatt_db_attribute *attr;
};
@@ -62,7 +61,6 @@ static GSList *devices;
static void gas_free(struct gas *gas)
{
- gatt_db_unregister(gas->db, gas->db_id);
gatt_db_unref(gas->db);
bt_gatt_client_unref(gas->client);
btd_device_unref(gas->device);
@@ -307,43 +305,6 @@ static void foreach_gap_service(struct gatt_db_attribute *attr, void *user_data)
handle_gap_service(gas);
}
-static void service_added(struct gatt_db_attribute *attr, void *user_data)
-{
- struct gas *gas = user_data;
- bt_uuid_t uuid, gap_uuid;
-
- if (!bt_gatt_client_is_ready(gas->client))
- return;
-
- gatt_db_attribute_get_service_uuid(attr, &uuid);
- bt_uuid16_create(&gap_uuid, GAP_UUID16);
-
- if (bt_uuid_cmp(&uuid, &gap_uuid))
- return;
-
- if (gas->attr) {
- error("More than one GAP service added to device");
- return;
- }
-
- DBG("GAP service added");
-
- gas->attr = attr;
- handle_gap_service(gas);
-}
-
-static void service_removed(struct gatt_db_attribute *attr, void *user_data)
-{
- struct gas *gas = user_data;
-
- if (gas->attr != attr)
- return;
-
- DBG("GAP service removed");
-
- gas->attr = NULL;
-}
-
static int gap_driver_accept(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
@@ -367,14 +328,11 @@ static int gap_driver_accept(struct btd_service *service)
/* Clean-up any old client/db and acquire the new ones */
gas->attr = NULL;
- gatt_db_unregister(gas->db, gas->db_id);
gatt_db_unref(gas->db);
bt_gatt_client_unref(gas->client);
gas->db = gatt_db_ref(db);
gas->client = bt_gatt_client_ref(client);
- gas->db_id = gatt_db_register(db, service_added, service_removed, gas,
- NULL);
/* Handle the GAP services */
bt_uuid16_create(&gap_uuid, GAP_UUID16);
diff --git a/profiles/input/hog-lib.c b/profiles/input/hog-lib.c
new file mode 100644
index 00000000..1df1799b
--- /dev/null
+++ b/profiles/input/hog-lib.c
@@ -0,0 +1,1550 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation.
+ * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ *
+ * 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 <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/shared/util.h"
+#include "src/shared/uhid.h"
+#include "src/shared/queue.h"
+#include "src/log.h"
+
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+
+#include "btio/btio.h"
+
+#include "profiles/scanparam/scpp.h"
+#include "profiles/deviceinfo/dis.h"
+#include "profiles/battery/bas.h"
+#include "profiles/input/hog-lib.h"
+
+#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb"
+
+#define HOG_INFO_UUID 0x2A4A
+#define HOG_REPORT_MAP_UUID 0x2A4B
+#define HOG_REPORT_UUID 0x2A4D
+#define HOG_PROTO_MODE_UUID 0x2A4E
+#define HOG_CONTROL_POINT_UUID 0x2A4C
+
+#define HOG_REPORT_TYPE_INPUT 1
+#define HOG_REPORT_TYPE_OUTPUT 2
+#define HOG_REPORT_TYPE_FEATURE 3
+
+#define HOG_PROTO_MODE_BOOT 0
+#define HOG_PROTO_MODE_REPORT 1
+
+#define HOG_REPORT_MAP_MAX_SIZE 512
+#define HID_INFO_SIZE 4
+#define ATT_NOTIFICATION_HEADER_SIZE 3
+
+struct bt_hog {
+ int ref_count;
+ char *name;
+ uint16_t vendor;
+ uint16_t product;
+ uint16_t version;
+ struct gatt_primary *primary;
+ GAttrib *attrib;
+ GSList *reports;
+ struct bt_uhid *uhid;
+ int uhid_fd;
+ bool uhid_created;
+ gboolean has_report_id;
+ uint16_t bcdhid;
+ uint8_t bcountrycode;
+ uint16_t proto_mode_handle;
+ uint16_t ctrlpt_handle;
+ uint8_t flags;
+ unsigned int getrep_att;
+ uint16_t getrep_id;
+ unsigned int setrep_att;
+ uint16_t setrep_id;
+ struct bt_scpp *scpp;
+ struct bt_dis *dis;
+ struct queue *bas;
+ GSList *instances;
+ struct queue *gatt_op;
+};
+
+struct report {
+ struct bt_hog *hog;
+ uint8_t id;
+ uint8_t type;
+ uint16_t ccc_handle;
+ guint notifyid;
+ struct gatt_char *decl;
+ uint16_t len;
+ uint8_t *value;
+};
+
+struct gatt_request {
+ unsigned int id;
+ struct bt_hog *hog;
+ void *user_data;
+};
+
+static struct gatt_request *create_request(struct bt_hog *hog,
+ void *user_data)
+{
+ struct gatt_request *req;
+
+ req = new0(struct gatt_request, 1);
+ if (!req)
+ return NULL;
+
+ req->user_data = user_data;
+ req->hog = bt_hog_ref(hog);
+
+ return req;
+}
+
+static bool set_and_store_gatt_req(struct bt_hog *hog,
+ struct gatt_request *req,
+ unsigned int id)
+{
+ req->id = id;
+ return queue_push_head(hog->gatt_op, req);
+}
+
+static void destroy_gatt_req(struct gatt_request *req)
+{
+ queue_remove(req->hog->gatt_op, req);
+ bt_hog_unref(req->hog);
+ free(req);
+}
+
+static void write_char(struct bt_hog *hog, 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(hog, user_data);
+ if (!req)
+ return;
+
+ id = gatt_write_char(attrib, handle, value, vlen, func, req);
+
+ if (set_and_store_gatt_req(hog, req, id))
+ return;
+
+ error("hog: Could not read char");
+ g_attrib_cancel(attrib, id);
+ free(req);
+}
+
+static void read_char(struct bt_hog *hog, GAttrib *attrib, uint16_t handle,
+ GAttribResultFunc func, gpointer user_data)
+{
+ struct gatt_request *req;
+ unsigned int id;
+
+ req = create_request(hog, user_data);
+ if (!req)
+ return;
+
+ id = gatt_read_char(attrib, handle, func, req);
+
+ if (set_and_store_gatt_req(hog, req, id))
+ return;
+
+ error("hog: Could not read char");
+ g_attrib_cancel(attrib, id);
+ free(req);
+}
+
+static void discover_desc(struct bt_hog *hog, GAttrib *attrib,
+ uint16_t start, uint16_t end, gatt_cb_t func,
+ gpointer user_data)
+{
+ struct gatt_request *req;
+ unsigned int id;
+
+ req = create_request(hog, user_data);
+ if (!req)
+ return;
+
+ id = gatt_discover_desc(attrib, start, end, NULL, func, req);
+ if (set_and_store_gatt_req(hog, req, id))
+ return;
+
+ error("hog: Could not discover descriptors");
+ g_attrib_cancel(attrib, id);
+ free(req);
+}
+
+static void discover_char(struct bt_hog *hog, 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(hog, user_data);
+ if (!req)
+ return;
+
+ id = gatt_discover_char(attrib, start, end, uuid, func, req);
+
+ if (set_and_store_gatt_req(hog, req, id))
+ return;
+
+ error("hog: Could not discover characteristic");
+ g_attrib_cancel(attrib, id);
+ free(req);
+}
+
+static void discover_primary(struct bt_hog *hog, GAttrib *attrib,
+ bt_uuid_t *uuid, gatt_cb_t func,
+ gpointer user_data)
+{
+ struct gatt_request *req;
+ unsigned int id;
+
+ req = create_request(hog, user_data);
+ if (!req)
+ return;
+
+ id = gatt_discover_primary(attrib, uuid, func, req);
+
+ if (set_and_store_gatt_req(hog, req, id))
+ return;
+
+ error("hog: Could not send discover primary");
+ g_attrib_cancel(attrib, id);
+ free(req);
+}
+
+static void find_included(struct bt_hog *hog, GAttrib *attrib,
+ uint16_t start, uint16_t end,
+ gatt_cb_t func, gpointer user_data)
+{
+ struct gatt_request *req;
+ unsigned int id;
+
+ req = create_request(hog, user_data);
+ if (!req)
+ return;
+
+ id = gatt_find_included(attrib, start, end, func, req);
+
+ if (set_and_store_gatt_req(hog, req, id))
+ return;
+
+ error("Could not find included");
+ g_attrib_cancel(attrib, id);
+ free(req);
+}
+
+static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data)
+{
+ struct report *report = user_data;
+ struct bt_hog *hog = report->hog;
+ struct uhid_event ev;
+ uint8_t *buf;
+ int err;
+
+ if (len < ATT_NOTIFICATION_HEADER_SIZE) {
+ error("Malformed ATT notification");
+ return;
+ }
+
+ pdu += ATT_NOTIFICATION_HEADER_SIZE;
+ len -= ATT_NOTIFICATION_HEADER_SIZE;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_INPUT;
+ buf = ev.u.input.data;
+
+ if (hog->has_report_id) {
+ buf[0] = report->id;
+ len = MIN(len, sizeof(ev.u.input.data) - 1);
+ memcpy(buf + 1, pdu, len);
+ ev.u.input.size = ++len;
+ } else {
+ len = MIN(len, sizeof(ev.u.input.data));
+ memcpy(buf, pdu, len);
+ ev.u.input.size = len;
+ }
+
+ err = bt_uhid_send(hog->uhid, &ev);
+ if (err < 0) {
+ error("bt_uhid_send: %s (%d)", strerror(-err), -err);
+ return;
+ }
+
+ DBG("HoG report (%u bytes)", ev.u.input.size);
+}
+
+static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct report *report = req->user_data;
+ struct bt_hog *hog = report->hog;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Write report characteristic descriptor failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ report->notifyid = g_attrib_register(hog->attrib,
+ ATT_OP_HANDLE_NOTIFY,
+ report->decl->value_handle,
+ report_value_cb, report, NULL);
+
+ DBG("Report characteristic descriptor written: notifications enabled");
+}
+
+static void write_ccc(struct bt_hog *hog, GAttrib *attrib, uint16_t handle,
+ void *user_data)
+{
+ uint8_t value[2];
+
+ put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
+
+ write_char(hog, attrib, handle, value, sizeof(value),
+ report_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 report *report = req->user_data;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Error reading CCC value: %s", att_ecode2str(status));
+ return;
+ }
+
+ write_ccc(report->hog, report->hog->attrib, report->ccc_handle, report);
+}
+
+static const char *type_to_string(uint8_t type)
+{
+ switch (type) {
+ case HOG_REPORT_TYPE_INPUT:
+ return "input";
+ case HOG_REPORT_TYPE_OUTPUT:
+ return "output";
+ case HOG_REPORT_TYPE_FEATURE:
+ return "feature";
+ }
+
+ return NULL;
+}
+
+static void report_reference_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct report *report = req->user_data;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Read Report Reference descriptor failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ if (plen != 3) {
+ error("Malformed ATT read response");
+ return;
+ }
+
+ report->id = pdu[1];
+ report->type = pdu[2];
+
+ DBG("Report 0x%04x: id 0x%02x type %s", report->decl->value_handle,
+ report->id, type_to_string(report->type));
+
+ /* Enable notifications only for Input Reports */
+ if (report->type == HOG_REPORT_TYPE_INPUT)
+ read_char(report->hog, report->hog->attrib, report->ccc_handle,
+ ccc_read_cb, report);
+}
+
+static void external_report_reference_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data);
+
+static void discover_external_cb(uint8_t status, GSList *descs, void *user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Discover external descriptors failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ for ( ; descs; descs = descs->next) {
+ struct gatt_desc *desc = descs->data;
+
+ read_char(hog, hog->attrib, desc->handle,
+ external_report_reference_cb,
+ hog);
+ }
+}
+
+static void discover_external(struct bt_hog *hog, GAttrib *attrib,
+ uint16_t start, uint16_t end,
+ gpointer user_data)
+{
+ bt_uuid_t uuid;
+
+ if (start > end)
+ return;
+
+ bt_uuid16_create(&uuid, GATT_EXTERNAL_REPORT_REFERENCE);
+
+ discover_desc(hog, attrib, start, end, discover_external_cb,
+ user_data);
+}
+
+static void discover_report_cb(uint8_t status, GSList *descs, void *user_data)
+{
+ struct gatt_request *req = user_data;
+ struct report *report = req->user_data;
+ struct bt_hog *hog = report->hog;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Discover report descriptors failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ for ( ; descs; descs = descs->next) {
+ struct gatt_desc *desc = descs->data;
+
+ switch (desc->uuid16) {
+ case GATT_CLIENT_CHARAC_CFG_UUID:
+ report->ccc_handle = desc->handle;
+ break;
+ case GATT_REPORT_REFERENCE:
+ read_char(hog, hog->attrib, desc->handle,
+ report_reference_cb, report);
+ break;
+ }
+ }
+}
+
+static void discover_report(struct bt_hog *hog, GAttrib *attrib,
+ uint16_t start, uint16_t end,
+ gpointer user_data)
+{
+ if (start > end)
+ return;
+
+ discover_desc(hog, attrib, start, end, discover_report_cb, user_data);
+}
+
+static void report_read_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct report *report = req->user_data;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Error reading Report value: %s", att_ecode2str(status));
+ return;
+ }
+
+ if (report->value)
+ g_free(report->value);
+
+ report->value = g_memdup(pdu, len);
+ report->len = len;
+}
+
+static int report_chrc_cmp(const void *data, const void *user_data)
+{
+ const struct report *report = data;
+ const struct gatt_char *decl = user_data;
+
+ return report->decl->handle - decl->handle;
+}
+
+static struct report *report_new(struct bt_hog *hog, struct gatt_char *chr)
+{
+ struct report *report;
+ GSList *l;
+
+ /* Skip if report already exists */
+ l = g_slist_find_custom(hog->reports, chr, report_chrc_cmp);
+ if (l)
+ return l->data;
+
+ report = g_new0(struct report, 1);
+ report->hog = hog;
+ report->decl = g_memdup(chr, sizeof(*chr));
+ hog->reports = g_slist_append(hog->reports, report);
+
+ read_char(hog, hog->attrib, chr->value_handle, report_read_cb, report);
+
+ return report;
+}
+
+static void external_service_char_cb(uint8_t status, GSList *chars,
+ void *user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ struct gatt_primary *primary = hog->primary;
+ struct report *report;
+ GSList *l;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ const char *str = att_ecode2str(status);
+ DBG("Discover external service characteristic failed: %s", str);
+ return;
+ }
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr, *next;
+ uint16_t start, end;
+
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
+ DBG("0x%04x UUID: %s properties: %02x",
+ chr->handle, chr->uuid, chr->properties);
+
+ report = report_new(hog, chr);
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : primary->range.end);
+ discover_report(hog, hog->attrib, start, end, report);
+ }
+}
+
+static void external_report_reference_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ uint16_t uuid16;
+ bt_uuid_t uuid;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Read External Report Reference descriptor failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ if (plen != 3) {
+ error("Malformed ATT read response");
+ return;
+ }
+
+ uuid16 = get_le16(&pdu[1]);
+ DBG("External report reference read, external report characteristic "
+ "UUID: 0x%04x", uuid16);
+
+ /* Do not discover if is not a Report */
+ if (uuid16 != HOG_REPORT_UUID)
+ return;
+
+ bt_uuid16_create(&uuid, uuid16);
+ discover_char(hog, hog->attrib, 0x0001, 0xffff, &uuid,
+ external_service_char_cb, hog);
+}
+
+static int report_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct report *ra = a, *rb = b;
+
+ /* sort by type first.. */
+ if (ra->type != rb->type)
+ return ra->type - rb->type;
+
+ /* skip id check in case of report id 0 */
+ if (!rb->id)
+ return 0;
+
+ /* ..then by id */
+ return ra->id - rb->id;
+}
+
+static struct report *find_report(struct bt_hog *hog, uint8_t type, uint8_t id)
+{
+ struct report cmp;
+ GSList *l;
+
+ cmp.type = type;
+ cmp.id = hog->has_report_id ? id : 0;
+
+ l = g_slist_find_custom(hog->reports, &cmp, report_cmp);
+
+ return l ? l->data : NULL;
+}
+
+static struct report *find_report_by_rtype(struct bt_hog *hog, uint8_t rtype,
+ uint8_t id)
+{
+ uint8_t type;
+
+ switch (rtype) {
+ case UHID_FEATURE_REPORT:
+ type = HOG_REPORT_TYPE_FEATURE;
+ break;
+ case UHID_OUTPUT_REPORT:
+ type = HOG_REPORT_TYPE_OUTPUT;
+ break;
+ case UHID_INPUT_REPORT:
+ type = HOG_REPORT_TYPE_INPUT;
+ break;
+ default:
+ return NULL;
+ }
+
+ return find_report(hog, type, id);
+}
+
+static void output_written_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void forward_report(struct uhid_event *ev, void *user_data)
+{
+ struct bt_hog *hog = user_data;
+ struct report *report;
+ void *data;
+ int size;
+
+ report = find_report_by_rtype(hog, ev->u.output.rtype,
+ ev->u.output.data[0]);
+ if (!report)
+ return;
+
+ data = ev->u.output.data;
+ size = ev->u.output.size;
+ if (hog->has_report_id && size > 0) {
+ data++;
+ --size;
+ }
+
+ DBG("Sending report type %d ID %d to handle 0x%X", report->type,
+ report->id, report->decl->value_handle);
+
+ if (hog->attrib == NULL)
+ return;
+
+ if (report->decl->properties & GATT_CHR_PROP_WRITE)
+ write_char(hog, hog->attrib, report->decl->value_handle,
+ data, size, output_written_cb, hog);
+ else if (report->decl->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
+ gatt_write_cmd(hog->attrib, report->decl->value_handle,
+ data, size, NULL, NULL);
+}
+
+static void get_feature(struct uhid_event *ev, void *user_data)
+{
+ struct bt_hog *hog = user_data;
+ struct report *report;
+ struct uhid_event rsp;
+ int err;
+
+ memset(&rsp, 0, sizeof(rsp));
+ rsp.type = UHID_FEATURE_ANSWER;
+ rsp.u.feature_answer.id = ev->u.feature.id;
+
+ report = find_report_by_rtype(hog, ev->u.feature.rtype,
+ ev->u.feature.rnum);
+ if (!report) {
+ rsp.u.feature_answer.err = ENOTSUP;
+ goto done;
+ }
+
+ if (!report->value) {
+ rsp.u.feature_answer.err = EIO;
+ goto done;
+ }
+
+ rsp.u.feature_answer.size = report->len;
+ memcpy(rsp.u.feature_answer.data, report->value, report->len);
+
+done:
+ err = bt_uhid_send(hog->uhid, &rsp);
+ if (err < 0)
+ error("bt_uhid_send: %s", strerror(-err));
+}
+
+static void set_report_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct bt_hog *hog = user_data;
+ struct uhid_event rsp;
+ int err;
+
+ hog->setrep_att = 0;
+
+ memset(&rsp, 0, sizeof(rsp));
+ rsp.type = UHID_SET_REPORT_REPLY;
+ rsp.u.set_report_reply.id = hog->setrep_id;
+ rsp.u.set_report_reply.err = status;
+
+ if (status != 0)
+ error("Error setting Report value: %s", att_ecode2str(status));
+
+ err = bt_uhid_send(hog->uhid, &rsp);
+ if (err < 0)
+ error("bt_uhid_send: %s", strerror(-err));
+}
+
+static void set_report(struct uhid_event *ev, void *user_data)
+{
+ struct bt_hog *hog = user_data;
+ struct report *report;
+ void *data;
+ int size;
+ int err;
+
+ /* uhid never sends reqs in parallel; if there's a req, it timed out */
+ if (hog->setrep_att) {
+ g_attrib_cancel(hog->attrib, hog->setrep_att);
+ hog->setrep_att = 0;
+ }
+
+ hog->setrep_id = ev->u.set_report.id;
+
+ report = find_report_by_rtype(hog, ev->u.set_report.rtype,
+ ev->u.set_report.rnum);
+ if (!report) {
+ err = ENOTSUP;
+ goto fail;
+ }
+
+ data = ev->u.set_report.data;
+ size = ev->u.set_report.size;
+ if (hog->has_report_id && size > 0) {
+ data++;
+ --size;
+ }
+
+ DBG("Sending report type %d ID %d to handle 0x%X", report->type,
+ report->id, report->decl->value_handle);
+
+ if (hog->attrib == NULL)
+ return;
+
+ hog->setrep_att = gatt_write_char(hog->attrib,
+ report->decl->value_handle,
+ data, size, set_report_cb,
+ hog);
+ if (!hog->setrep_att) {
+ err = ENOMEM;
+ goto fail;
+ }
+
+ return;
+fail:
+ /* cancel the request on failure */
+ set_report_cb(err, NULL, 0, hog);
+}
+
+static void get_report_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct bt_hog *hog = user_data;
+ struct uhid_event rsp;
+ int err;
+
+ hog->getrep_att = 0;
+
+ memset(&rsp, 0, sizeof(rsp));
+ rsp.type = UHID_GET_REPORT_REPLY;
+ rsp.u.get_report_reply.id = hog->getrep_id;
+
+ if (status != 0) {
+ error("Error reading Report value: %s", att_ecode2str(status));
+ goto exit;
+ }
+
+ if (len == 0) {
+ error("Error reading Report, length %d", len);
+ status = EIO;
+ goto exit;
+ }
+
+ if (pdu[0] != 0x0b) {
+ error("Error reading Report, invalid response: %02x", pdu[0]);
+ status = EPROTO;
+ goto exit;
+ }
+
+ --len;
+ ++pdu;
+ if (hog->has_report_id && len > 0) {
+ --len;
+ ++pdu;
+ }
+
+ rsp.u.get_report_reply.size = len;
+ memcpy(rsp.u.get_report_reply.data, pdu, len);
+
+exit:
+ rsp.u.get_report_reply.err = status;
+ err = bt_uhid_send(hog->uhid, &rsp);
+ if (err < 0)
+ error("bt_uhid_send: %s", strerror(-err));
+}
+
+static void get_report(struct uhid_event *ev, void *user_data)
+{
+ struct bt_hog *hog = user_data;
+ struct report *report;
+ guint8 err;
+
+ /* uhid never sends reqs in parallel; if there's a req, it timed out */
+ if (hog->getrep_att) {
+ g_attrib_cancel(hog->attrib, hog->getrep_att);
+ hog->getrep_att = 0;
+ }
+
+ hog->getrep_id = ev->u.get_report.id;
+
+ report = find_report_by_rtype(hog, ev->u.get_report.rtype,
+ ev->u.get_report.rnum);
+ if (!report) {
+ err = ENOTSUP;
+ goto fail;
+ }
+
+ hog->getrep_att = gatt_read_char(hog->attrib,
+ report->decl->value_handle,
+ get_report_cb, hog);
+ if (!hog->getrep_att) {
+ err = ENOMEM;
+ goto fail;
+ }
+
+ return;
+
+fail:
+ /* cancel the request on failure */
+ get_report_cb(err, NULL, 0, hog);
+}
+
+static bool get_descriptor_item_info(uint8_t *buf, ssize_t blen, ssize_t *len,
+ bool *is_long)
+{
+ if (!blen)
+ return false;
+
+ *is_long = (buf[0] == 0xfe);
+
+ if (*is_long) {
+ if (blen < 3)
+ return false;
+
+ /*
+ * long item:
+ * byte 0 -> 0xFE
+ * byte 1 -> data size
+ * byte 2 -> tag
+ * + data
+ */
+
+ *len = buf[1] + 3;
+ } else {
+ uint8_t b_size;
+
+ /*
+ * short item:
+ * byte 0[1..0] -> data size (=0, 1, 2, 4)
+ * byte 0[3..2] -> type
+ * byte 0[7..4] -> tag
+ * + data
+ */
+
+ b_size = buf[0] & 0x03;
+ *len = (b_size ? 1 << (b_size - 1) : 0) + 1;
+ }
+
+ /* item length should be no more than input buffer length */
+ return *len <= blen;
+}
+
+static char *item2string(char *str, uint8_t *buf, uint8_t len)
+{
+ char *p = str;
+ int i;
+
+ /*
+ * Since long item tags are not defined except for vendor ones, we
+ * just ensure that short items are printed properly (up to 5 bytes).
+ */
+ for (i = 0; i < 6 && i < len; i++)
+ p += sprintf(p, " %02x", buf[i]);
+
+ /*
+ * If there are some data left, just add continuation mark to indicate
+ * this.
+ */
+ if (i < len)
+ sprintf(p, " ...");
+
+ return str;
+}
+
+static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ uint8_t value[HOG_REPORT_MAP_MAX_SIZE];
+ struct uhid_event ev;
+ ssize_t vlen;
+ char itemstr[20]; /* 5x3 (data) + 4 (continuation) + 1 (null) */
+ int i, err;
+ GError *gerr = NULL;
+
+ destroy_gatt_req(req);
+
+ DBG("HoG inspecting report map");
+
+ if (status != 0) {
+ error("Report Map read failed: %s", att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen < 0) {
+ error("ATT protocol error");
+ return;
+ }
+
+ DBG("Report MAP:");
+ for (i = 0; i < vlen;) {
+ ssize_t ilen = 0;
+ bool long_item = false;
+
+ if (get_descriptor_item_info(&value[i], vlen - i, &ilen,
+ &long_item)) {
+ /* Report ID is short item with prefix 100001xx */
+ if (!long_item && (value[i] & 0xfc) == 0x84)
+ hog->has_report_id = TRUE;
+
+ DBG("\t%s", item2string(itemstr, &value[i], ilen));
+
+ i += ilen;
+ } else {
+ error("Report Map parsing failed at %d", i);
+
+ /* Just print remaining items at once and break */
+ DBG("\t%s", item2string(itemstr, &value[i], vlen - i));
+ break;
+ }
+ }
+
+ /* create uHID device */
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_CREATE;
+
+ bt_io_get(g_attrib_get_channel(hog->attrib), &gerr,
+ BT_IO_OPT_SOURCE, ev.u.create.phys,
+ BT_IO_OPT_DEST, ev.u.create.uniq,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ error("Failed to connection details: %s", gerr->message);
+ g_error_free(gerr);
+ return;
+ }
+
+ strcpy((char *) ev.u.create.name, hog->name);
+ ev.u.create.vendor = hog->vendor;
+ ev.u.create.product = hog->product;
+ ev.u.create.version = hog->version;
+ ev.u.create.country = hog->bcountrycode;
+ ev.u.create.bus = BUS_BLUETOOTH;
+ ev.u.create.rd_data = value;
+ ev.u.create.rd_size = vlen;
+
+ err = bt_uhid_send(hog->uhid, &ev);
+ if (err < 0) {
+ error("bt_uhid_send: %s", strerror(-err));
+ return;
+ }
+
+ bt_uhid_register(hog->uhid, UHID_OUTPUT, forward_report, hog);
+ bt_uhid_register(hog->uhid, UHID_FEATURE, get_feature, hog);
+ bt_uhid_register(hog->uhid, UHID_GET_REPORT, get_report, hog);
+ bt_uhid_register(hog->uhid, UHID_SET_REPORT, set_report, hog);
+
+ hog->uhid_created = true;
+
+ DBG("HoG created uHID device");
+}
+
+static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ uint8_t value[HID_INFO_SIZE];
+ ssize_t vlen;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("HID Information read failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen != 4) {
+ error("ATT protocol error");
+ return;
+ }
+
+ hog->bcdhid = get_le16(&value[0]);
+ hog->bcountrycode = value[2];
+ hog->flags = value[3];
+
+ DBG("bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X",
+ hog->bcdhid, hog->bcountrycode, hog->flags);
+}
+
+static void proto_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ uint8_t value;
+ ssize_t vlen;
+
+ destroy_gatt_req(req);
+
+ if (status != 0) {
+ error("Protocol Mode characteristic read failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, &value, sizeof(value));
+ if (vlen < 0) {
+ error("ATT protocol error");
+ return;
+ }
+
+ if (value == HOG_PROTO_MODE_BOOT) {
+ uint8_t nval = HOG_PROTO_MODE_REPORT;
+
+ DBG("HoG is operating in Boot Procotol Mode");
+
+ gatt_write_cmd(hog->attrib, hog->proto_mode_handle, &nval,
+ sizeof(nval), NULL, NULL);
+ } else if (value == HOG_PROTO_MODE_REPORT)
+ DBG("HoG is operating in Report Protocol Mode");
+}
+
+static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ struct gatt_primary *primary = hog->primary;
+ bt_uuid_t report_uuid, report_map_uuid, info_uuid;
+ bt_uuid_t proto_mode_uuid, ctrlpt_uuid;
+ struct report *report;
+ GSList *l;
+ uint16_t info_handle = 0, proto_mode_handle = 0;
+
+ destroy_gatt_req(req);
+
+ DBG("HoG inspecting characteristics");
+
+ if (status != 0) {
+ const char *str = att_ecode2str(status);
+ DBG("Discover all characteristics failed: %s", str);
+ return;
+ }
+
+ bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
+ bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
+ bt_uuid16_create(&info_uuid, HOG_INFO_UUID);
+ bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID);
+ bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID);
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr, *next;
+ bt_uuid_t uuid;
+ uint16_t start, end;
+
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
+ DBG("0x%04x UUID: %s properties: %02x",
+ chr->handle, chr->uuid, chr->properties);
+
+ bt_string_to_uuid(&uuid, chr->uuid);
+
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : primary->range.end);
+
+ if (bt_uuid_cmp(&uuid, &report_uuid) == 0) {
+ report = report_new(hog, chr);
+ discover_report(hog, hog->attrib, start, end, report);
+ } else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0) {
+ DBG("HoG discovering report map");
+ read_char(hog, hog->attrib, chr->value_handle,
+ report_map_read_cb, hog);
+ discover_external(hog, hog->attrib, start, end, hog);
+ } else if (bt_uuid_cmp(&uuid, &info_uuid) == 0)
+ info_handle = chr->value_handle;
+ else if (bt_uuid_cmp(&uuid, &proto_mode_uuid) == 0)
+ proto_mode_handle = chr->value_handle;
+ else if (bt_uuid_cmp(&uuid, &ctrlpt_uuid) == 0)
+ hog->ctrlpt_handle = chr->value_handle;
+ }
+
+ if (proto_mode_handle) {
+ hog->proto_mode_handle = proto_mode_handle;
+ read_char(hog, hog->attrib, proto_mode_handle,
+ proto_mode_read_cb, hog);
+ }
+
+ if (info_handle)
+ read_char(hog, hog->attrib, info_handle, info_read_cb, hog);
+}
+
+static void report_free(void *data)
+{
+ struct report *report = data;
+
+ g_free(report->value);
+ g_free(report->decl);
+ g_free(report);
+}
+
+static void cancel_gatt_req(struct gatt_request *req)
+{
+ if (g_attrib_cancel(req->hog->attrib, req->id))
+ destroy_gatt_req(req);
+}
+
+static void hog_free(void *data)
+{
+ struct bt_hog *hog = data;
+
+ bt_hog_detach(hog);
+
+ queue_destroy(hog->bas, (void *) bt_bas_unref);
+ g_slist_free_full(hog->instances, hog_free);
+
+ bt_scpp_unref(hog->scpp);
+ bt_dis_unref(hog->dis);
+ bt_uhid_unref(hog->uhid);
+ g_slist_free_full(hog->reports, report_free);
+ g_free(hog->name);
+ g_free(hog->primary);
+ queue_destroy(hog->gatt_op, (void *) destroy_gatt_req);
+ g_free(hog);
+}
+
+struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
+ uint16_t product, uint16_t version,
+ void *primary)
+{
+ return bt_hog_new(-1, name, vendor, product, version, primary);
+}
+
+struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
+ uint16_t product, uint16_t version,
+ void *primary)
+{
+ struct bt_hog *hog;
+
+ hog = g_try_new0(struct bt_hog, 1);
+ if (!hog)
+ return NULL;
+
+ hog->gatt_op = queue_new();
+ hog->bas = queue_new();
+
+ if (fd < 0)
+ hog->uhid = bt_uhid_new_default();
+ else
+ hog->uhid = bt_uhid_new(fd);
+
+ hog->uhid_fd = fd;
+
+ if (!hog->gatt_op || !hog->bas || !hog->uhid) {
+ hog_free(hog);
+ return NULL;
+ }
+
+ hog->name = g_strdup(name);
+ hog->vendor = vendor;
+ hog->product = product;
+ hog->version = version;
+
+ if (primary)
+ hog->primary = g_memdup(primary, sizeof(*hog->primary));
+
+ return bt_hog_ref(hog);
+}
+
+struct bt_hog *bt_hog_ref(struct bt_hog *hog)
+{
+ if (!hog)
+ return NULL;
+
+ __sync_fetch_and_add(&hog->ref_count, 1);
+
+ return hog;
+}
+
+void bt_hog_unref(struct bt_hog *hog)
+{
+ if (!hog)
+ return;
+
+ if (__sync_sub_and_fetch(&hog->ref_count, 1))
+ return;
+
+ hog_free(hog);
+}
+
+static void find_included_cb(uint8_t status, GSList *services, void *user_data)
+{
+ struct gatt_request *req = user_data;
+ GSList *l;
+
+ DBG("");
+
+ destroy_gatt_req(req);
+
+ if (status) {
+ const char *str = att_ecode2str(status);
+ DBG("Find included failed: %s", str);
+ return;
+ }
+
+ for (l = services; l; l = l->next) {
+ struct gatt_included *include = l->data;
+
+ DBG("included: handle %x, uuid %s",
+ include->handle, include->uuid);
+ }
+}
+
+static void hog_attach_scpp(struct bt_hog *hog, struct gatt_primary *primary)
+{
+ if (hog->scpp) {
+ bt_scpp_attach(hog->scpp, hog->attrib);
+ return;
+ }
+
+ hog->scpp = bt_scpp_new(primary);
+ if (hog->scpp)
+ bt_scpp_attach(hog->scpp, hog->attrib);
+}
+
+static void dis_notify(uint8_t source, uint16_t vendor, uint16_t product,
+ uint16_t version, void *user_data)
+{
+ struct bt_hog *hog = user_data;
+
+ hog->vendor = vendor;
+ hog->product = product;
+ hog->version = version;
+}
+
+static void hog_attach_dis(struct bt_hog *hog, struct gatt_primary *primary)
+{
+ if (hog->dis) {
+ bt_dis_attach(hog->dis, hog->attrib);
+ return;
+ }
+
+ hog->dis = bt_dis_new(primary);
+ if (hog->dis) {
+ bt_dis_set_notification(hog->dis, dis_notify, hog);
+ bt_dis_attach(hog->dis, hog->attrib);
+ }
+}
+
+static void hog_attach_bas(struct bt_hog *hog, struct gatt_primary *primary)
+{
+ struct bt_bas *instance;
+
+ instance = bt_bas_new(primary);
+
+ bt_bas_attach(instance, hog->attrib);
+ queue_push_head(hog->bas, instance);
+}
+
+static void hog_attach_hog(struct bt_hog *hog, struct gatt_primary *primary)
+{
+ struct bt_hog *instance;
+
+ if (!hog->primary) {
+ hog->primary = g_memdup(primary, sizeof(*primary));
+ discover_char(hog, hog->attrib, primary->range.start,
+ primary->range.end, NULL,
+ char_discovered_cb, hog);
+ find_included(hog, hog->attrib, primary->range.start,
+ primary->range.end, find_included_cb, hog);
+ return;
+ }
+
+ instance = bt_hog_new(hog->uhid_fd, hog->name, hog->vendor,
+ hog->product, hog->version, primary);
+ if (!instance)
+ return;
+
+ find_included(instance, hog->attrib, primary->range.start,
+ primary->range.end, find_included_cb, instance);
+
+ bt_hog_attach(instance, hog->attrib);
+ hog->instances = g_slist_append(hog->instances, instance);
+}
+
+static void primary_cb(uint8_t status, GSList *services, void *user_data)
+{
+ struct gatt_request *req = user_data;
+ struct bt_hog *hog = req->user_data;
+ struct gatt_primary *primary;
+ GSList *l;
+
+ DBG("");
+
+ destroy_gatt_req(req);
+
+ if (status) {
+ const char *str = att_ecode2str(status);
+ DBG("Discover primary failed: %s", str);
+ return;
+ }
+
+ if (!services) {
+ DBG("No primary service found");
+ return;
+ }
+
+ for (l = services; l; l = l->next) {
+ primary = l->data;
+
+ if (strcmp(primary->uuid, SCAN_PARAMETERS_UUID) == 0) {
+ hog_attach_scpp(hog, primary);
+ continue;
+ }
+
+ if (strcmp(primary->uuid, DEVICE_INFORMATION_UUID) == 0) {
+ hog_attach_dis(hog, primary);
+ continue;
+ }
+
+ if (strcmp(primary->uuid, BATTERY_UUID) == 0) {
+ hog_attach_bas(hog, primary);
+ continue;
+ }
+
+ if (strcmp(primary->uuid, HOG_UUID) == 0)
+ hog_attach_hog(hog, primary);
+ }
+}
+
+bool bt_hog_attach(struct bt_hog *hog, void *gatt)
+{
+ struct gatt_primary *primary = hog->primary;
+ GSList *l;
+
+ if (hog->attrib)
+ return false;
+
+ hog->attrib = g_attrib_ref(gatt);
+
+ if (!primary) {
+ discover_primary(hog, hog->attrib, NULL, primary_cb, hog);
+ return true;
+ }
+
+ if (hog->scpp)
+ bt_scpp_attach(hog->scpp, gatt);
+
+ if (hog->dis)
+ bt_dis_attach(hog->dis, gatt);
+
+ queue_foreach(hog->bas, (void *) bt_bas_attach, gatt);
+
+ for (l = hog->instances; l; l = l->next) {
+ struct bt_hog *instance = l->data;
+
+ bt_hog_attach(instance, gatt);
+ }
+
+ if (!hog->uhid_created) {
+ DBG("HoG discovering characteristics");
+ discover_char(hog, hog->attrib, primary->range.start,
+ primary->range.end, NULL,
+ char_discovered_cb, hog);
+ return true;
+ }
+
+ for (l = hog->reports; l; l = l->next) {
+ struct report *r = l->data;
+
+ r->notifyid = g_attrib_register(hog->attrib,
+ ATT_OP_HANDLE_NOTIFY,
+ r->decl->value_handle,
+ report_value_cb, r, NULL);
+ }
+
+ return true;
+}
+
+void bt_hog_detach(struct bt_hog *hog)
+{
+ GSList *l;
+
+ if (!hog->attrib)
+ return;
+
+ queue_foreach(hog->bas, (void *) bt_bas_detach, NULL);
+
+ for (l = hog->instances; l; l = l->next) {
+ struct bt_hog *instance = l->data;
+
+ bt_hog_detach(instance);
+ }
+
+ for (l = hog->reports; l; l = l->next) {
+ struct report *r = l->data;
+
+ if (r->notifyid > 0) {
+ g_attrib_unregister(hog->attrib, r->notifyid);
+ r->notifyid = 0;
+ }
+ }
+
+ if (hog->scpp)
+ bt_scpp_detach(hog->scpp);
+
+ if (hog->dis)
+ bt_dis_detach(hog->dis);
+
+ queue_foreach(hog->gatt_op, (void *) cancel_gatt_req, NULL);
+ g_attrib_unref(hog->attrib);
+ hog->attrib = NULL;
+}
+
+int bt_hog_set_control_point(struct bt_hog *hog, bool suspend)
+{
+ uint8_t value = suspend ? 0x00 : 0x01;
+
+ if (hog->attrib == NULL)
+ return -ENOTCONN;
+
+ if (hog->ctrlpt_handle == 0)
+ return -ENOTSUP;
+
+ gatt_write_cmd(hog->attrib, hog->ctrlpt_handle, &value,
+ sizeof(value), NULL, NULL);
+
+ return 0;
+}
+
+int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type)
+{
+ struct report *report;
+ GSList *l;
+
+ if (!hog)
+ return -EINVAL;
+
+ if (!hog->attrib)
+ return -ENOTCONN;
+
+ report = find_report(hog, type, 0);
+ if (!report)
+ return -ENOTSUP;
+
+ DBG("hog: Write report, handle 0x%X", report->decl->value_handle);
+
+ if (report->decl->properties & GATT_CHR_PROP_WRITE)
+ write_char(hog, hog->attrib, report->decl->value_handle,
+ data, size, output_written_cb, hog);
+
+ if (report->decl->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
+ gatt_write_cmd(hog->attrib, report->decl->value_handle,
+ data, size, NULL, NULL);
+
+ for (l = hog->instances; l; l = l->next) {
+ struct bt_hog *instance = l->data;
+
+ bt_hog_send_report(instance, data, size, type);
+ }
+
+ return 0;
+}
diff --git a/profiles/input/hog-lib.h b/profiles/input/hog-lib.h
new file mode 100644
index 00000000..2a9b899c
--- /dev/null
+++ b/profiles/input/hog-lib.h
@@ -0,0 +1,41 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute 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 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
+ * 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
+ *
+ */
+
+struct bt_hog;
+
+struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
+ uint16_t product, uint16_t version,
+ void *primary);
+
+struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
+ uint16_t product, uint16_t version,
+ void *primary);
+
+struct bt_hog *bt_hog_ref(struct bt_hog *hog);
+void bt_hog_unref(struct bt_hog *hog);
+
+bool bt_hog_attach(struct bt_hog *hog, void *gatt);
+void bt_hog_detach(struct bt_hog *hog);
+
+int bt_hog_set_control_point(struct bt_hog *hog, bool suspend);
+int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type);
diff --git a/profiles/input/hog.c b/profiles/input/hog.c
index 3d23d5b4..4dba83f7 100644
--- a/profiles/input/hog.c
+++ b/profiles/input/hog.c
@@ -48,6 +48,7 @@
#include "src/service.h"
#include "src/shared/util.h"
#include "src/shared/uhid.h"
+#include "src/shared/queue.h"
#include "src/plugin.h"
#include "suspend.h"
@@ -55,905 +56,99 @@
#include "attrib/gattrib.h"
#include "src/attio.h"
#include "attrib/gatt.h"
+#include "hog-lib.h"
#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb"
-#define HOG_INFO_UUID 0x2A4A
-#define HOG_REPORT_MAP_UUID 0x2A4B
-#define HOG_REPORT_UUID 0x2A4D
-#define HOG_PROTO_MODE_UUID 0x2A4E
-#define HOG_CONTROL_POINT_UUID 0x2A4C
-
-#define HOG_REPORT_TYPE_INPUT 1
-#define HOG_REPORT_TYPE_OUTPUT 2
-#define HOG_REPORT_TYPE_FEATURE 3
-
-#define HOG_PROTO_MODE_BOOT 0
-#define HOG_PROTO_MODE_REPORT 1
-
-#define HOG_REPORT_MAP_MAX_SIZE 512
-#define HID_INFO_SIZE 4
-#define ATT_NOTIFICATION_HEADER_SIZE 3
-
struct hog_device {
- uint16_t id;
- struct btd_device *device;
- GAttrib *attrib;
guint attioid;
- struct gatt_primary *hog_primary;
- GSList *reports;
- struct bt_uhid *uhid;
- gboolean has_report_id;
- uint16_t bcdhid;
- uint8_t bcountrycode;
- uint16_t proto_mode_handle;
- uint16_t ctrlpt_handle;
- uint8_t flags;
- guint getrep_att;
- uint16_t getrep_id;
- guint setrep_att;
- uint16_t setrep_id;
-};
-
-struct report {
- uint8_t id;
- uint8_t type;
- guint notifyid;
- struct gatt_char *decl;
- struct hog_device *hogdev;
+ struct btd_device *device;
+ struct bt_hog *hog;
};
static gboolean suspend_supported = FALSE;
-static GSList *devices = NULL;
-
-static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data)
-{
- struct report *report = user_data;
- struct hog_device *hogdev = report->hogdev;
- struct uhid_event ev;
- uint8_t *buf;
- int err;
-
- if (len < ATT_NOTIFICATION_HEADER_SIZE) {
- error("Malformed ATT notification");
- return;
- }
-
- pdu += ATT_NOTIFICATION_HEADER_SIZE;
- len -= ATT_NOTIFICATION_HEADER_SIZE;
-
- memset(&ev, 0, sizeof(ev));
- ev.type = UHID_INPUT;
- buf = ev.u.input.data;
-
- if (hogdev->has_report_id) {
- buf[0] = report->id;
- len = MIN(len, sizeof(ev.u.input.data) - 1);
- memcpy(buf + 1, pdu, len);
- ev.u.input.size = ++len;
- } else {
- len = MIN(len, sizeof(ev.u.input.data));
- memcpy(buf, pdu, len);
- ev.u.input.size = len;
- }
-
- err = bt_uhid_send(hogdev->uhid, &ev);
- if (err < 0) {
- error("bt_uhid_send: %s (%d)", strerror(-err), -err);
- return;
- }
-
- DBG("HoG report (%u bytes)", ev.u.input.size);
-}
-
-static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
-{
- struct report *report = user_data;
- struct hog_device *hogdev = report->hogdev;
-
- if (status != 0) {
- error("Write report characteristic descriptor failed: %s",
- att_ecode2str(status));
- return;
- }
-
- report->notifyid = g_attrib_register(hogdev->attrib,
- ATT_OP_HANDLE_NOTIFY,
- report->decl->value_handle,
- report_value_cb, report, NULL);
-
- DBG("Report characteristic descriptor written: notifications enabled");
-}
-
-static void write_ccc(uint16_t handle, gpointer user_data)
-{
- struct report *report = user_data;
- struct hog_device *hogdev = report->hogdev;
- uint8_t value[] = { 0x01, 0x00 };
-
- gatt_write_char(hogdev->attrib, handle, value, sizeof(value),
- report_ccc_written_cb, report);
-}
-
-static void report_reference_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
-{
- struct report *report = user_data;
-
- if (status != 0) {
- error("Read Report Reference descriptor failed: %s",
- att_ecode2str(status));
- return;
- }
-
- if (plen != 3) {
- error("Malformed ATT read response");
- return;
- }
-
- report->id = pdu[1];
- report->type = pdu[2];
- DBG("Report ID: 0x%02x Report type: 0x%02x", pdu[1], pdu[2]);
-}
-
-static void external_report_reference_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data);
-
-
-static void discover_descriptor_cb(uint8_t status, GSList *descs,
- void *user_data)
-{
- struct report *report;
- struct hog_device *hogdev;
- GAttrib *attrib = NULL;
-
- if (status != 0) {
- error("Discover all descriptors failed: %s",
- att_ecode2str(status));
- return;
- }
-
- for ( ; descs; descs = descs->next) {
- struct gatt_desc *desc = descs->data;
-
- switch (desc->uuid16) {
- case GATT_CLIENT_CHARAC_CFG_UUID:
- report = user_data;
- write_ccc(desc->handle, report);
- break;
- case GATT_REPORT_REFERENCE:
- report = user_data;
- attrib = report->hogdev->attrib;
- gatt_read_char(attrib, desc->handle,
- report_reference_cb, report);
- break;
- case GATT_EXTERNAL_REPORT_REFERENCE:
- hogdev = user_data;
- attrib = hogdev->attrib;
- gatt_read_char(attrib, desc->handle,
- external_report_reference_cb, hogdev);
- break;
- }
- }
-}
-
-static void discover_descriptor(GAttrib *attrib, uint16_t start, uint16_t end,
- gpointer user_data)
-{
- if (start > end)
- return;
-
- gatt_discover_desc(attrib, start, end, NULL,
- discover_descriptor_cb, user_data);
-}
-
-static void external_service_char_cb(uint8_t status, GSList *chars,
- void *user_data)
-{
- struct hog_device *hogdev = user_data;
- struct gatt_primary *prim = hogdev->hog_primary;
- struct report *report;
- GSList *l;
-
- if (status != 0) {
- const char *str = att_ecode2str(status);
- DBG("Discover external service characteristic failed: %s", str);
- return;
- }
-
- for (l = chars; l; l = g_slist_next(l)) {
- struct gatt_char *chr, *next;
- uint16_t start, end;
-
- chr = l->data;
- next = l->next ? l->next->data : NULL;
-
- DBG("0x%04x UUID: %s properties: %02x",
- chr->handle, chr->uuid, chr->properties);
-
- report = g_new0(struct report, 1);
- report->hogdev = hogdev;
- report->decl = g_memdup(chr, sizeof(*chr));
- hogdev->reports = g_slist_append(hogdev->reports, report);
- start = chr->value_handle + 1;
- end = (next ? next->handle - 1 : prim->range.end);
- discover_descriptor(hogdev->attrib, start, end, report);
- }
-}
-
-static void external_report_reference_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- uint16_t uuid16;
- bt_uuid_t uuid;
-
- if (status != 0) {
- error("Read External Report Reference descriptor failed: %s",
- att_ecode2str(status));
- return;
- }
-
- if (plen != 3) {
- error("Malformed ATT read response");
- return;
- }
-
- uuid16 = get_le16(&pdu[1]);
- DBG("External report reference read, external report characteristic "
- "UUID: 0x%04x", uuid16);
- bt_uuid16_create(&uuid, uuid16);
- gatt_discover_char(hogdev->attrib, 0x00, 0xff, &uuid,
- external_service_char_cb, hogdev);
-}
-
-static int report_cmp(gconstpointer a, gconstpointer b)
-{
- const struct report *ra = a, *rb = b;
-
- /* sort by type first.. */
- if (ra->type != rb->type)
- return ra->type - rb->type;
-
- /* ..then by id */
- return ra->id - rb->id;
-}
-
-static void output_written_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
-{
- if (status != 0) {
- error("Write output report failed: %s", att_ecode2str(status));
- return;
- }
-}
-
-static struct report *find_report(struct hog_device *hogdev, uint8_t type, uint8_t id)
-{
- struct report cmp;
- GSList *l;
-
- switch (type) {
- case UHID_FEATURE_REPORT:
- cmp.type = HOG_REPORT_TYPE_FEATURE;
- break;
- case UHID_OUTPUT_REPORT:
- cmp.type = HOG_REPORT_TYPE_OUTPUT;
- break;
- case UHID_INPUT_REPORT:
- cmp.type = HOG_REPORT_TYPE_INPUT;
- break;
- default:
- return NULL;
- }
-
- cmp.id = hogdev->has_report_id ? id : 0;
-
- l = g_slist_find_custom(hogdev->reports, &cmp, report_cmp);
-
- return l ? l->data : NULL;
-}
-
-static void forward_report(struct uhid_event *ev, void *user_data)
-{
- struct hog_device *hogdev = user_data;
- struct report *report;
- void *data;
- int size;
-
- report = find_report(hogdev, ev->u.output.rtype, ev->u.output.data[0]);
- if (!report)
- return;
-
- data = ev->u.output.data;
- size = ev->u.output.size;
- if (hogdev->has_report_id && size > 0) {
- data++;
- --size;
- }
-
- DBG("Sending report type %d ID %d to handle 0x%X", report->type,
- report->id, report->decl->value_handle);
-
- if (hogdev->attrib == NULL)
- return;
-
- if (report->decl->properties & GATT_CHR_PROP_WRITE)
- gatt_write_char(hogdev->attrib, report->decl->value_handle,
- data, size, output_written_cb, hogdev);
- else if (report->decl->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP)
- gatt_write_cmd(hogdev->attrib, report->decl->value_handle,
- data, size, NULL, NULL);
-}
-
-static void set_report_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- struct uhid_event rsp;
- int err;
-
- hogdev->setrep_att = 0;
-
- memset(&rsp, 0, sizeof(rsp));
- rsp.type = UHID_SET_REPORT_REPLY;
- rsp.u.set_report_reply.id = hogdev->setrep_id;
- rsp.u.set_report_reply.err = status;
-
- if (status != 0)
- error("Error setting Report value: %s", att_ecode2str(status));
-
- err = bt_uhid_send(hogdev->uhid, &rsp);
- if (err < 0)
- error("bt_uhid_send: %s", strerror(-err));
-}
-
-static void set_report(struct uhid_event *ev, void *user_data)
-{
- struct hog_device *hogdev = user_data;
- struct report *report;
- void *data;
- int size;
- int err;
-
- /* uhid never sends reqs in parallel; if there's a req, it timed out */
- if (hogdev->setrep_att) {
- g_attrib_cancel(hogdev->attrib, hogdev->setrep_att);
- hogdev->setrep_att = 0;
- }
-
- hogdev->setrep_id = ev->u.set_report.id;
-
- report = find_report(hogdev, ev->u.set_report.rtype,
- ev->u.set_report.rnum);
- if (!report) {
- err = ENOTSUP;
- goto fail;
- }
-
- data = ev->u.set_report.data;
- size = ev->u.set_report.size;
- if (hogdev->has_report_id && size > 0) {
- data++;
- --size;
- }
-
- DBG("Sending report type %d ID %d to handle 0x%X", report->type,
- report->id, report->decl->value_handle);
-
- if (hogdev->attrib == NULL)
- return;
-
- hogdev->setrep_att = gatt_write_char(hogdev->attrib,
- report->decl->value_handle,
- data, size, set_report_cb,
- hogdev);
- if (!hogdev->setrep_att) {
- err = ENOMEM;
- goto fail;
- }
-
- return;
-fail:
- /* cancel the request on failure */
- set_report_cb(err, NULL, 0, hogdev);
-}
-
-static void get_report_cb(guint8 status, const guint8 *pdu, guint16 len,
- gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- struct uhid_event rsp;
- int err;
-
- hogdev->getrep_att = 0;
-
- memset(&rsp, 0, sizeof(rsp));
- rsp.type = UHID_GET_REPORT_REPLY;
- rsp.u.get_report_reply.id = hogdev->getrep_id;
-
- if (status != 0) {
- error("Error reading Report value: %s", att_ecode2str(status));
- goto exit;
- }
-
- if (len == 0) {
- error("Error reading Report, length %d", len);
- status = EIO;
- goto exit;
- }
-
- if (pdu[0] != 0x0b) {
- error("Error reading Report, invalid response: %02x", pdu[0]);
- status = EPROTO;
- goto exit;
- }
-
- --len;
- ++pdu;
- if (hogdev->has_report_id && len > 0) {
- --len;
- ++pdu;
- }
-
- rsp.u.get_report_reply.size = len;
- memcpy(rsp.u.get_report_reply.data, pdu, len);
-
-exit:
- rsp.u.get_report_reply.err = status;
- err = bt_uhid_send(hogdev->uhid, &rsp);
- if (err < 0)
- error("bt_uhid_send: %s", strerror(-err));
-}
-
-static void get_report(struct uhid_event *ev, void *user_data)
-{
- struct hog_device *hogdev = user_data;
- struct report *report;
- guint8 err;
-
- /* uhid never sends reqs in parallel; if there's a req, it timed out */
- if (hogdev->getrep_att) {
- g_attrib_cancel(hogdev->attrib, hogdev->getrep_att);
- hogdev->getrep_att = 0;
- }
-
- hogdev->getrep_id = ev->u.get_report.id;
-
- report = find_report(hogdev, ev->u.get_report.rtype,
- ev->u.get_report.rnum);
- if (!report) {
- err = ENOTSUP;
- goto fail;
- }
-
- hogdev->getrep_att = gatt_read_char(hogdev->attrib,
- report->decl->value_handle,
- get_report_cb, hogdev);
- if (!hogdev->getrep_att) {
- err = ENOMEM;
- goto fail;
- }
-
- return;
-
-fail:
- /* cancel the request on failure */
- get_report_cb(err, NULL, 0, hogdev);
-}
-
-static bool get_descriptor_item_info(uint8_t *buf, ssize_t blen, ssize_t *len,
- bool *is_long)
-{
- if (!blen)
- return false;
-
- *is_long = (buf[0] == 0xfe);
-
- if (*is_long) {
- if (blen < 3)
- return false;
-
- /*
- * long item:
- * byte 0 -> 0xFE
- * byte 1 -> data size
- * byte 2 -> tag
- * + data
- */
-
- *len = buf[1] + 3;
- } else {
- uint8_t b_size;
-
- /*
- * short item:
- * byte 0[1..0] -> data size (=0, 1, 2, 4)
- * byte 0[3..2] -> type
- * byte 0[7..4] -> tag
- * + data
- */
-
- b_size = buf[0] & 0x03;
- *len = (b_size ? 1 << (b_size - 1) : 0) + 1;
- }
-
- /* item length should be no more than input buffer length */
- return *len <= blen;
-}
-
-static char *item2string(char *str, uint8_t *buf, uint8_t len)
-{
- char *p = str;
- int i;
-
- /*
- * Since long item tags are not defined except for vendor ones, we
- * just ensure that short items are printed properly (up to 5 bytes).
- */
- for (i = 0; i < 6 && i < len; i++)
- p += sprintf(p, " %02x", buf[i]);
-
- /*
- * If there are some data left, just add continuation mark to indicate
- * this.
- */
- if (i < len)
- sprintf(p, " ...");
-
- return str;
-}
-
-static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- struct btd_adapter *adapter = device_get_adapter(hogdev->device);
- uint8_t value[HOG_REPORT_MAP_MAX_SIZE];
- struct uhid_event ev;
- uint16_t vendor_src, vendor, product, version;
- ssize_t vlen;
- char itemstr[20]; /* 5x3 (data) + 4 (continuation) + 1 (null) */
- int i, err;
-
- if (status != 0) {
- error("Report Map read failed: %s", att_ecode2str(status));
- return;
- }
-
- vlen = dec_read_resp(pdu, plen, value, sizeof(value));
- if (vlen < 0) {
- error("ATT protocol error");
- return;
- }
-
- DBG("Report MAP:");
- for (i = 0; i < vlen;) {
- ssize_t ilen = 0;
- bool long_item = false;
-
- if (get_descriptor_item_info(&value[i], vlen - i, &ilen,
- &long_item)) {
- /* Report ID is short item with prefix 100001xx */
- if (!long_item && (value[i] & 0xfc) == 0x84)
- hogdev->has_report_id = TRUE;
-
- DBG("\t%s", item2string(itemstr, &value[i], ilen));
-
- i += ilen;
- } else {
- error("Report Map parsing failed at %d", i);
-
- /* Just print remaining items at once and break */
- DBG("\t%s", item2string(itemstr, &value[i], vlen - i));
- break;
- }
- }
-
- vendor_src = btd_device_get_vendor_src(hogdev->device);
- vendor = btd_device_get_vendor(hogdev->device);
- product = btd_device_get_product(hogdev->device);
- version = btd_device_get_version(hogdev->device);
- DBG("DIS information: vendor_src=0x%X, vendor=0x%X, product=0x%X, "
- "version=0x%X", vendor_src, vendor, product, version);
-
- /* create uHID device */
- memset(&ev, 0, sizeof(ev));
- ev.type = UHID_CREATE;
- if (device_name_known(hogdev->device))
- device_get_name(hogdev->device, (char *) ev.u.create.name,
- sizeof(ev.u.create.name));
- else
- strcpy((char *) ev.u.create.name, "bluez-hog-device");
- ba2str(btd_adapter_get_address(adapter), (char *) ev.u.create.phys);
- ba2str(device_get_address(hogdev->device), (char *) ev.u.create.uniq);
- ev.u.create.vendor = vendor;
- ev.u.create.product = product;
- ev.u.create.version = version;
- ev.u.create.country = hogdev->bcountrycode;
- ev.u.create.bus = BUS_BLUETOOTH;
- ev.u.create.rd_data = value;
- ev.u.create.rd_size = vlen;
-
- err = bt_uhid_send(hogdev->uhid, &ev);
- if (err < 0) {
- error("bt_uhid_send: %s", strerror(-err));
- return;
- }
-
- bt_uhid_register(hogdev->uhid, UHID_OUTPUT, forward_report, hogdev);
- bt_uhid_register(hogdev->uhid, UHID_SET_REPORT, set_report, hogdev);
- bt_uhid_register(hogdev->uhid, UHID_GET_REPORT, get_report, hogdev);
-}
-
-static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- uint8_t value[HID_INFO_SIZE];
- ssize_t vlen;
-
- if (status != 0) {
- error("HID Information read failed: %s",
- att_ecode2str(status));
- return;
- }
-
- vlen = dec_read_resp(pdu, plen, value, sizeof(value));
- if (vlen != 4) {
- error("ATT protocol error");
- return;
- }
-
- hogdev->bcdhid = get_le16(&value[0]);
- hogdev->bcountrycode = value[2];
- hogdev->flags = value[3];
-
- DBG("bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X",
- hogdev->bcdhid, hogdev->bcountrycode, hogdev->flags);
-}
-
-static void proto_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
-{
- struct hog_device *hogdev = user_data;
- uint8_t value;
- ssize_t vlen;
-
- if (status != 0) {
- error("Protocol Mode characteristic read failed: %s",
- att_ecode2str(status));
- return;
- }
-
- vlen = dec_read_resp(pdu, plen, &value, sizeof(value));
- if (vlen < 0) {
- error("ATT protocol error");
- return;
- }
-
- if (value == HOG_PROTO_MODE_BOOT) {
- uint8_t nval = HOG_PROTO_MODE_REPORT;
-
- DBG("HoG device 0x%04X is operating in Boot Procotol Mode",
- hogdev->id);
-
- gatt_write_cmd(hogdev->attrib, hogdev->proto_mode_handle, &nval,
- sizeof(nval), NULL, NULL);
- } else if (value == HOG_PROTO_MODE_REPORT)
- DBG("HoG device 0x%04X is operating in Report Protocol Mode",
- hogdev->id);
-}
-
-static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data)
-{
- struct hog_device *hogdev = user_data;
- struct gatt_primary *prim = hogdev->hog_primary;
- bt_uuid_t report_uuid, report_map_uuid, info_uuid;
- bt_uuid_t proto_mode_uuid, ctrlpt_uuid;
- struct report *report;
- GSList *l;
- uint16_t info_handle = 0, proto_mode_handle = 0;
-
- if (status != 0) {
- const char *str = att_ecode2str(status);
- DBG("Discover all characteristics failed: %s", str);
- return;
- }
-
- bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
- bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
- bt_uuid16_create(&info_uuid, HOG_INFO_UUID);
- bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID);
- bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID);
-
- for (l = chars; l; l = g_slist_next(l)) {
- struct gatt_char *chr, *next;
- bt_uuid_t uuid;
- uint16_t start, end;
-
- chr = l->data;
- next = l->next ? l->next->data : NULL;
-
- DBG("0x%04x UUID: %s properties: %02x",
- chr->handle, chr->uuid, chr->properties);
-
- bt_string_to_uuid(&uuid, chr->uuid);
-
- start = chr->value_handle + 1;
- end = (next ? next->handle - 1 : prim->range.end);
-
- if (bt_uuid_cmp(&uuid, &report_uuid) == 0) {
- report = g_new0(struct report, 1);
- report->hogdev = hogdev;
- report->decl = g_memdup(chr, sizeof(*chr));
- hogdev->reports = g_slist_append(hogdev->reports,
- report);
- discover_descriptor(hogdev->attrib, start, end, report);
- } else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0) {
- gatt_read_char(hogdev->attrib, chr->value_handle,
- report_map_read_cb, hogdev);
- discover_descriptor(hogdev->attrib, start, end, hogdev);
- } else if (bt_uuid_cmp(&uuid, &info_uuid) == 0)
- info_handle = chr->value_handle;
- else if (bt_uuid_cmp(&uuid, &proto_mode_uuid) == 0)
- proto_mode_handle = chr->value_handle;
- else if (bt_uuid_cmp(&uuid, &ctrlpt_uuid) == 0)
- hogdev->ctrlpt_handle = chr->value_handle;
- }
-
- if (proto_mode_handle) {
- hogdev->proto_mode_handle = proto_mode_handle;
- gatt_read_char(hogdev->attrib, proto_mode_handle,
- proto_mode_read_cb, hogdev);
- }
-
- if (info_handle)
- gatt_read_char(hogdev->attrib, info_handle, info_read_cb,
- hogdev);
-}
+static struct queue *devices = NULL;
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
- struct hog_device *hogdev = user_data;
- struct gatt_primary *prim = hogdev->hog_primary;
- GSList *l;
+ struct hog_device *dev = user_data;
DBG("HoG connected");
- hogdev->attrib = g_attrib_ref(attrib);
-
- if (hogdev->reports == NULL) {
- gatt_discover_char(hogdev->attrib, prim->range.start,
- prim->range.end, NULL,
- char_discovered_cb, hogdev);
- return;
- }
-
- for (l = hogdev->reports; l; l = l->next) {
- struct report *r = l->data;
-
- r->notifyid = g_attrib_register(hogdev->attrib,
- ATT_OP_HANDLE_NOTIFY,
- r->decl->value_handle,
- report_value_cb, r, NULL);
- }
+ bt_hog_attach(dev->hog, attrib);
}
static void attio_disconnected_cb(gpointer user_data)
{
- struct hog_device *hogdev = user_data;
- GSList *l;
+ struct hog_device *dev = user_data;
DBG("HoG disconnected");
- for (l = hogdev->reports; l; l = l->next) {
- struct report *r = l->data;
-
- g_attrib_unregister(hogdev->attrib, r->notifyid);
- }
-
- g_attrib_unref(hogdev->attrib);
- hogdev->attrib = NULL;
+ bt_hog_detach(dev->hog);
}
-static struct hog_device *hog_new_device(struct btd_device *device,
- uint16_t id)
-{
- struct hog_device *hogdev;
-
- hogdev = g_try_new0(struct hog_device, 1);
- if (!hogdev)
- return NULL;
-
- hogdev->id = id;
- hogdev->device = btd_device_ref(device);
-
- return hogdev;
-}
-
-static void report_free(void *data)
-{
- struct report *report = data;
- struct hog_device *hogdev = report->hogdev;
-
- if (hogdev->attrib)
- g_attrib_unregister(hogdev->attrib, report->notifyid);
-
- g_free(report->decl);
- g_free(report);
-}
-
-static void hog_free_device(struct hog_device *hogdev)
-{
- btd_device_unref(hogdev->device);
- g_slist_free_full(hogdev->reports, report_free);
- g_attrib_unref(hogdev->attrib);
- g_free(hogdev->hog_primary);
- g_free(hogdev);
-}
-
-static struct hog_device *hog_register_device(struct btd_device *device,
+static struct hog_device *hog_device_new(struct btd_device *device,
struct gatt_primary *prim)
{
- struct hog_device *hogdev;
+ struct hog_device *dev;
+ char name[248];
+ uint16_t vendor, product, version;
- hogdev = hog_new_device(device, prim->range.start);
- if (!hogdev)
- return NULL;
+ if (device_name_known(device))
+ device_get_name(device, name, sizeof(name));
+ else
+ strcpy(name, "bluez-hog-device");
- hogdev->uhid = bt_uhid_new_default();
- if (!hogdev->uhid) {
- error("bt_uhid_new_default: failed");
- hog_free_device(hogdev);
- return NULL;
- }
+ vendor = btd_device_get_vendor(device);
+ product = btd_device_get_product(device);
+ version = btd_device_get_version(device);
- hogdev->hog_primary = g_memdup(prim, sizeof(*prim));
+ DBG("name=%s vendor=0x%X, product=0x%X, version=0x%X", name, vendor,
+ product, version);
- hogdev->attioid = btd_device_add_attio_callback(device,
+ dev = new0(struct hog_device, 1);
+ dev->device = btd_device_ref(device);
+ dev->hog = bt_hog_new_default(name, vendor, product, version, prim);
+
+ /*
+ * TODO: Remove attio callback and use .accept once using
+ * bt_gatt_client.
+ */
+ dev->attioid = btd_device_add_attio_callback(device,
attio_connected_cb,
attio_disconnected_cb,
- hogdev);
+ dev);
- return hogdev;
-}
+ if (!devices)
+ devices = queue_new();
-static int hog_unregister_device(struct hog_device *hogdev)
-{
- btd_device_remove_attio_callback(hogdev->device, hogdev->attioid);
- bt_uhid_unref(hogdev->uhid);
- hog_free_device(hogdev);
+ queue_push_tail(devices, dev);
- return 0;
+ return dev;
}
-static int set_control_point(struct hog_device *hogdev, gboolean suspend)
+static void hog_device_free(void *data)
{
- uint8_t value = suspend ? 0x00 : 0x01;
-
- if (hogdev->attrib == NULL)
- return -ENOTCONN;
-
- DBG("0x%4X HID Control Point: %s", hogdev->id, suspend ?
- "Suspend" : "Exit Suspend");
-
- if (hogdev->ctrlpt_handle == 0)
- return -ENOTSUP;
+ struct hog_device *dev = data;
- gatt_write_cmd(hogdev->attrib, hogdev->ctrlpt_handle, &value,
- sizeof(value), NULL, NULL);
+ queue_remove(devices, dev);
+ if (queue_isempty(devices)) {
+ queue_destroy(devices, NULL);
+ devices = NULL;
+ }
- return 0;
+ btd_device_remove_attio_callback(dev->device, dev->attioid);
+ btd_device_unref(dev->device);
+ bt_hog_unref(dev->hog);
+ free(dev);
}
static void set_suspend(gpointer data, gpointer user_data)
{
- struct hog_device *hogdev = data;
+ struct hog_device *dev = data;
gboolean suspend = GPOINTER_TO_INT(user_data);
- set_control_point(hogdev, suspend);
+ bt_hog_set_control_point(dev->hog, suspend);
}
static void suspend_callback(void)
@@ -962,7 +157,7 @@ static void suspend_callback(void)
DBG("Suspending ...");
- g_slist_foreach(devices, set_suspend, GINT_TO_POINTER(suspend));
+ queue_foreach(devices, set_suspend, GINT_TO_POINTER(suspend));
}
static void resume_callback(void)
@@ -971,7 +166,7 @@ static void resume_callback(void)
DBG("Resuming ...");
- g_slist_foreach(devices, set_suspend, GINT_TO_POINTER(suspend));
+ queue_foreach(devices, set_suspend, GINT_TO_POINTER(suspend));
}
static int hog_probe(struct btd_service *service)
@@ -988,41 +183,28 @@ static int hog_probe(struct btd_service *service)
for (l = primaries; l; l = g_slist_next(l)) {
struct gatt_primary *prim = l->data;
- struct hog_device *hogdev;
+ struct hog_device *dev;
if (strcmp(prim->uuid, HOG_UUID) != 0)
continue;
- hogdev = hog_register_device(device, prim);
- if (hogdev == NULL)
- continue;
-
- devices = g_slist_append(devices, hogdev);
+ dev = hog_device_new(device, prim);
+ btd_service_set_user_data(service, dev);
+ return 0;
}
- return 0;
-}
-
-static void remove_device(gpointer a, gpointer b)
-{
- struct hog_device *hogdev = a;
- struct btd_device *device = b;
-
- if (hogdev->device != device)
- return;
-
- devices = g_slist_remove(devices, hogdev);
- hog_unregister_device(hogdev);
+ return -EINVAL;
}
static void hog_remove(struct btd_service *service)
{
+ struct hog_device *dev = btd_service_get_user_data(service);
struct btd_device *device = btd_service_get_device(service);
const char *path = device_get_path(device);
DBG("path %s", path);
- g_slist_foreach(devices, remove_device, device);
+ hog_device_free(dev);
}
static struct btd_profile hog_profile = {
diff --git a/profiles/input/suspend-none.c b/profiles/input/suspend-none.c
new file mode 100644
index 00000000..c619bb11
--- /dev/null
+++ b/profiles/input/suspend-none.c
@@ -0,0 +1,42 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ *
+ * 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 "src/log.h"
+#include "suspend.h"
+
+int suspend_init(suspend_event suspend, resume_event resume)
+{
+ DBG("");
+
+ return 0;
+}
+
+void suspend_exit(void)
+{
+ DBG("");
+}
diff --git a/profiles/network/bnep.c b/profiles/network/bnep.c
index a4cc00b6..95a8e74f 100644
--- a/profiles/network/bnep.c
+++ b/profiles/network/bnep.c
@@ -160,9 +160,7 @@ static int bnep_connadd(int sk, uint16_t role, char *dev)
req.sock = sk;
req.role = role;
-#ifdef __TIZEN_PATCH__
req.flags = (1 << BNEP_SETUP_RESPONSE);
-#endif
if (ioctl(ctl, BNEPCONNADD, &req) < 0) {
int err = -errno;
error("bnep: Failed to add device %s: %s(%d)",
@@ -174,7 +172,6 @@ static int bnep_connadd(int sk, uint16_t role, char *dev)
return 0;
}
-#ifdef __TIZEN_PATCH__
static uint32_t bnep_getsuppfeat(void)
{
uint32_t feat;
@@ -186,7 +183,6 @@ static uint32_t bnep_getsuppfeat(void)
return feat;
}
-#endif
static int bnep_if_up(const char *devname)
{
@@ -599,13 +595,9 @@ static uint16_t bnep_setup_decode(int sk, struct bnep_setup_conn_req *req,
uint8_t *dest, *source;
uint32_t val;
-#ifdef __TIZEN_PATCH__
if (((req->type != BNEP_CONTROL) &&
(req->type != (BNEP_CONTROL | BNEP_EXT_HEADER))) ||
req->ctrl != BNEP_SETUP_CONN_REQ)
-#else
- if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ)
-#endif
return BNEP_CONN_NOT_ALLOWED;
dest = req->service;
@@ -676,7 +668,6 @@ int bnep_conndel_wrapper(const bdaddr_t *dst)
}
#endif
-#ifdef __TIZEN_PATCH__
static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge,
char *iface, const bdaddr_t *addr,
uint8_t *setup_data, int len)
@@ -697,6 +688,15 @@ static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge,
goto reply;
}
+#ifndef __TIZEN_PATCH__
+ err = bnep_add_to_bridge(iface, bridge);
+ if (err < 0) {
+ bnep_conndel(addr);
+ rsp = BNEP_CONN_NOT_ALLOWED;
+ goto reply;
+ }
+#endif
+
err = bnep_if_up(iface);
if (err < 0) {
bnep_del_from_bridge(iface, bridge);
@@ -710,21 +710,18 @@ static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge,
reply:
if (bnep_send_ctrl_rsp(sk, BNEP_SETUP_CONN_RSP, rsp) < 0) {
err = -errno;
- error("bnep: send ctrl rsp error: %s (%d)", strerror(errno),
- errno);
+ error("bnep: send ctrl rsp error: %s (%d)", strerror(-err),
+ -err);
}
return err;
}
-#endif
int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr,
uint8_t *setup_data, int len)
{
int err;
-#ifdef __TIZEN_PATCH__
uint32_t feat;
-#endif
uint16_t rsp, dst;
struct bnep_setup_conn_req *req = (void *) setup_data;
@@ -748,14 +745,9 @@ int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr,
err = -rsp;
error("bnep: error while decoding setup connection request: %d",
rsp);
-#ifdef __TIZEN_PATCH__
goto failed;
-#else
- goto reply;
-#endif
}
-#ifdef __TIZEN_PATCH__
feat = bnep_getsuppfeat();
/*
@@ -766,40 +758,29 @@ int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr,
if (!feat || !(feat & (1 << BNEP_SETUP_RESPONSE)))
return bnep_server_add_legacy(sk, dst, bridge, iface, addr,
setup_data, len);
-#endif
err = bnep_connadd(sk, dst, iface);
if (err < 0) {
rsp = BNEP_CONN_NOT_ALLOWED;
-#ifdef __TIZEN_PATCH__
goto failed;
-#else
- goto reply;
-#endif
}
#ifndef __TIZEN_PATCH__
err = bnep_add_to_bridge(iface, bridge);
- if (err < 0) {
- bnep_conndel(addr);
- return err;
- }
+ if (err < 0)
+ goto failed_conn;
#endif
err = bnep_if_up(iface);
if (err < 0)
-#ifdef __TIZEN_PATCH__
goto failed_bridge;
-#else
- rsp = BNEP_CONN_NOT_ALLOWED;
-#endif
-#ifdef __TIZEN_PATCH__
return 0;
failed_bridge:
bnep_del_from_bridge(iface, bridge);
+failed_conn:
bnep_conndel(addr);
return err;
@@ -810,14 +791,6 @@ failed:
error("bnep: send ctrl rsp error: %s (%d)", strerror(-err),
-err);
}
-#else
-reply:
- if (bnep_send_ctrl_rsp(sk, BNEP_SETUP_CONN_RSP, rsp) < 0) {
- err = -errno;
- error("bnep: send ctrl rsp error: %s (%d)", strerror(errno),
- errno);
- }
-#endif
return err;
}
diff --git a/profiles/network/connection.c b/profiles/network/connection.c
index e19e945e..c86f679f 100644
--- a/profiles/network/connection.c
+++ b/profiles/network/connection.c
@@ -309,11 +309,8 @@ static DBusMessage *local_connect(DBusConnection *conn,
id = get_pan_srv_id(svc);
bt_uuid16_create(&uuid16, id);
-#ifdef __TIZEN_PATCH__
bt_uuid_to_uuid128(&uuid16, &uuid128);
-#else
- bt_uuid_to_uuid128(&uuid128, &uuid16);
-#endif
+
if (bt_uuid_to_string(&uuid128, uuid_str, MAX_LEN_UUID_STR) < 0)
return btd_error_invalid_args(msg);
@@ -459,9 +456,7 @@ static gboolean network_property_get_uuid(const GDBusPropertyTable *property,
struct network_peer *peer = data;
struct network_conn *nc;
char uuid_str[MAX_LEN_UUID_STR];
-#ifdef __TIZEN_PATCH__
const char *uuid = uuid_str;
-#endif
bt_uuid_t uuid16, uuid128;
nc = find_connection_by_state(peer->connections, CONNECTED);
@@ -469,18 +464,10 @@ static gboolean network_property_get_uuid(const GDBusPropertyTable *property,
return FALSE;
bt_uuid16_create(&uuid16, nc->id);
-#ifdef __TIZEN_PATCH__
bt_uuid_to_uuid128(&uuid16, &uuid128);
-#else
- bt_uuid_to_uuid128(&uuid128, &uuid16);
-#endif
bt_uuid_to_string(&uuid128, uuid_str, MAX_LEN_UUID_STR);
-#ifdef __TIZEN_PATCH__
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid);
-#else
- dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid_str);
-#endif
return TRUE;
}
diff --git a/profiles/network/server.c b/profiles/network/server.c
index 2450fbea..214bc214 100644
--- a/profiles/network/server.c
+++ b/profiles/network/server.c
@@ -419,16 +419,11 @@ static gboolean bnep_setup(GIOChannel *chan,
sk = g_io_channel_unix_get_fd(chan);
-#ifdef __TIZEN_PATCH__
/*
* BNEP_SETUP_CONNECTION_REQUEST_MSG should be read and left in case
* of kernel setup connection msg handling.
*/
n = recv(sk, packet, sizeof(packet), MSG_PEEK);
-#else
- /* Reading BNEP_SETUP_CONNECTION_REQUEST_MSG */
- n = read(sk, packet, sizeof(packet));
-#endif
if (n < 0) {
error("read(): %s(%d)", strerror(errno), errno);
return FALSE;
diff --git a/profiles/scanparam/scan.c b/profiles/scanparam/scan.c
index fbda8a8b..4015b3f8 100644
--- a/profiles/scanparam/scan.c
+++ b/profiles/scanparam/scan.c
@@ -40,10 +40,11 @@
#include "src/profile.h"
#include "src/service.h"
#include "src/shared/util.h"
+#include "src/shared/att.h"
+#include "src/shared/queue.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "attrib/gatt.h"
-#include "src/attio.h"
#define SCAN_INTERVAL_WIN_UUID 0x2A4F
#define SCAN_REFRESH_UUID 0x2A31
@@ -54,203 +55,209 @@
struct scan {
struct btd_device *device;
- GAttrib *attrib;
- struct att_range range;
- guint attioid;
- uint16_t interval;
- uint16_t window;
+ struct gatt_db *db;
+ struct bt_gatt_client *client;
+ struct gatt_db_attribute *attr;
uint16_t iwhandle;
- uint16_t refresh_handle;
guint refresh_cb_id;
};
-static void write_scan_params(GAttrib *attrib, uint16_t handle)
+static GSList *devices;
+
+static void scan_free(struct scan *scan)
+{
+ bt_gatt_client_unregister_notify(scan->client, scan->refresh_cb_id);
+ gatt_db_unref(scan->db);
+ bt_gatt_client_unref(scan->client);
+ btd_device_unref(scan->device);
+ g_free(scan);
+}
+
+static int cmp_device(gconstpointer a, gconstpointer b)
+{
+ const struct scan *scan = a;
+ const struct btd_device *device = b;
+
+ return scan->device == device ? 0 : -1;
+}
+
+static void write_scan_params(struct scan *scan)
{
uint8_t value[4];
put_le16(SCAN_INTERVAL, &value[0]);
put_le16(SCAN_WINDOW, &value[2]);
- gatt_write_cmd(attrib, handle, value, sizeof(value), NULL, NULL);
+ bt_gatt_client_write_without_response(scan->client, scan->iwhandle,
+ false, value, sizeof(value));
}
-static void refresh_value_cb(const uint8_t *pdu, uint16_t len,
- gpointer user_data)
+static void refresh_value_cb(uint16_t value_handle, const uint8_t *value,
+ uint16_t length, void *user_data)
{
struct scan *scan = user_data;
- DBG("Server requires refresh: %d", pdu[3]);
+ DBG("Server requires refresh: %d", value[3]);
- if (pdu[3] == SERVER_REQUIRES_REFRESH)
- write_scan_params(scan->attrib, scan->iwhandle);
+ if (value[3] == SERVER_REQUIRES_REFRESH)
+ write_scan_params(scan);
}
-static void ccc_written_cb(guint8 status, const guint8 *pdu,
- guint16 plen, gpointer user_data)
+static void refresh_ccc_written_cb(uint16_t att_ecode, void *user_data)
{
- struct scan *scan = user_data;
-
- if (status != 0) {
- error("Write Scan Refresh CCC failed: %s",
- att_ecode2str(status));
+ if (att_ecode != 0) {
+ error("Scan Refresh: notifications not enabled %s",
+ att_ecode2str(att_ecode));
return;
}
DBG("Scan Refresh: notification enabled");
-
- scan->refresh_cb_id = g_attrib_register(scan->attrib,
- ATT_OP_HANDLE_NOTIFY, scan->refresh_handle,
- refresh_value_cb, scan, NULL);
}
-static void discover_descriptor_cb(uint8_t status, GSList *descs,
- void *user_data)
+static void handle_refresh(struct scan *scan, uint16_t value_handle)
{
- struct scan *scan = user_data;
- struct gatt_desc *desc;
- uint8_t value[2];
+ DBG("Scan Refresh handle: 0x%04x", value_handle);
- if (status != 0) {
- error("Discover descriptors failed: %s", att_ecode2str(status));
- return;
- }
+ scan->refresh_cb_id = bt_gatt_client_register_notify(scan->client,
+ value_handle, refresh_ccc_written_cb,
+ refresh_value_cb, scan, NULL);
+}
+
+static void handle_iwin(struct scan *scan, uint16_t value_handle)
+{
+ scan->iwhandle = value_handle;
- /* There will be only one descriptor on list and it will be CCC */
- desc = descs->data;
+ DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle);
- put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
- gatt_write_char(scan->attrib, desc->handle, value, sizeof(value),
- ccc_written_cb, user_data);
+ write_scan_params(scan);
}
-static void refresh_discovered_cb(uint8_t status, GSList *chars,
+static void handle_characteristic(struct gatt_db_attribute *attr,
void *user_data)
{
struct scan *scan = user_data;
- struct gatt_char *chr;
- uint16_t start, end;
- bt_uuid_t uuid;
-
- if (status) {
- error("Scan Refresh %s", att_ecode2str(status));
- return;
- }
+ uint16_t value_handle;
+ bt_uuid_t uuid, scan_interval_wind_uuid, scan_refresh_uuid;
- if (!chars) {
- DBG("Scan Refresh not supported");
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL,
+ &uuid)) {
+ error("Failed to obtain characteristic data");
return;
}
- chr = chars->data;
+ bt_uuid16_create(&scan_interval_wind_uuid, SCAN_INTERVAL_WIN_UUID);
+ bt_uuid16_create(&scan_refresh_uuid, SCAN_REFRESH_UUID);
- DBG("Scan Refresh handle: 0x%04x", chr->value_handle);
+ if (bt_uuid_cmp(&scan_interval_wind_uuid, &uuid) == 0)
+ handle_iwin(scan, value_handle);
+ else if (bt_uuid_cmp(&scan_refresh_uuid, &uuid) == 0)
+ handle_refresh(scan, value_handle);
+ else {
+ char uuid_str[MAX_LEN_UUID_STR];
- start = chr->value_handle + 1;
- end = scan->range.end;
-
- if (start > end)
- return;
-
- scan->refresh_handle = chr->value_handle;
-
- bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
-
- gatt_discover_desc(scan->attrib, start, end, &uuid,
- discover_descriptor_cb, user_data);
+ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
+ DBG("Unsupported characteristic: %s", uuid_str);
+ }
}
-static void iwin_discovered_cb(uint8_t status, GSList *chars, void *user_data)
+static void foreach_scan_param_service(struct gatt_db_attribute *attr,
+ void *user_data)
{
struct scan *scan = user_data;
- struct gatt_char *chr;
- if (status) {
- error("Discover Scan Interval Window: %s",
- att_ecode2str(status));
+ if (scan->attr) {
+ error("More than one scan params service exists for this "
+ "device");
return;
}
- chr = chars->data;
- scan->iwhandle = chr->value_handle;
-
- DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle);
-
- write_scan_params(scan->attrib, scan->iwhandle);
+ scan->attr = attr;
+ gatt_db_service_foreach_char(scan->attr, handle_characteristic, scan);
}
-static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+static int scan_param_accept(struct btd_service *service)
{
- struct scan *scan = user_data;
- bt_uuid_t iwin_uuid, refresh_uuid;
+ struct btd_device *device = btd_service_get_device(service);
+ struct gatt_db *db = btd_device_get_gatt_db(device);
+ struct bt_gatt_client *client = btd_device_get_gatt_client(device);
+ bt_uuid_t scan_parameters_uuid;
+ struct scan *scan;
+ GSList *l;
+ char addr[18];
- scan->attrib = g_attrib_ref(attrib);
+ ba2str(device_get_address(device), addr);
+ DBG("Scan Parameters Client Driver profile accept (%s)", addr);
- if (scan->iwhandle) {
- write_scan_params(scan->attrib, scan->iwhandle);
- return;
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (!l) {
+ error("Scan Parameters service not handled by profile");
+ return -1;
}
- bt_uuid16_create(&iwin_uuid, SCAN_INTERVAL_WIN_UUID);
- bt_uuid16_create(&refresh_uuid, SCAN_REFRESH_UUID);
+ scan = l->data;
- gatt_discover_char(scan->attrib, scan->range.start, scan->range.end,
- &iwin_uuid, iwin_discovered_cb, scan);
+ /* Clean-up any old client/db and acquire the new ones */
+ scan->attr = NULL;
+ gatt_db_unref(scan->db);
+ bt_gatt_client_unref(scan->client);
- gatt_discover_char(scan->attrib, scan->range.start, scan->range.end,
- &refresh_uuid, refresh_discovered_cb, scan);
-}
-static void attio_disconnected_cb(gpointer user_data)
-{
- struct scan *scan = user_data;
+ scan->db = gatt_db_ref(db);
+ scan->client = bt_gatt_client_ref(client);
- g_attrib_unref(scan->attrib);
- scan->attrib = NULL;
+ bt_string_to_uuid(&scan_parameters_uuid, SCAN_PARAMETERS_UUID);
+ gatt_db_foreach_service(db, &scan_parameters_uuid,
+ foreach_scan_param_service, scan);
+
+ return 0;
}
-static int scan_register(struct btd_service *service, struct gatt_primary *prim)
+static void scan_param_remove(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
struct scan *scan;
+ GSList *l;
+ char addr[18];
- scan = g_new0(struct scan, 1);
- scan->device = btd_device_ref(device);
- scan->range = prim->range;
- scan->attioid = btd_device_add_attio_callback(device,
- attio_connected_cb,
- attio_disconnected_cb,
- scan);
-
- btd_service_set_user_data(service, scan);
+ ba2str(device_get_address(device), addr);
+ DBG("GAP profile remove (%s)", addr);
- return 0;
-}
-
-static void scan_param_remove(struct btd_service *service)
-{
- struct scan *scan = btd_service_get_user_data(service);
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (!l) {
+ error("GAP service not handled by profile");
+ return;
+ }
- if (scan->attrib != NULL && scan->refresh_cb_id > 0)
- g_attrib_unregister(scan->attrib, scan->refresh_cb_id);
+ scan = l->data;
- btd_device_remove_attio_callback(scan->device, scan->attioid);
- btd_device_unref(scan->device);
- g_attrib_unref(scan->attrib);
- g_free(scan);
+ devices = g_slist_remove(devices, scan);
+ scan_free(scan);
}
static int scan_param_probe(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
- struct gatt_primary *prim;
+ struct scan *scan;
+ GSList *l;
+ char addr[18];
- DBG("Probing Scan Parameters");
+ ba2str(device_get_address(device), addr);
+ DBG("Scan Parameters Client Driver profile probe (%s)", addr);
- prim = btd_device_get_primary(device, SCAN_PARAMETERS_UUID);
- if (!prim)
- return -EINVAL;
+ /* Ignore, if we were probed for this device already */
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (l) {
+ error("Profile probed twice for the same device!");
+ return -1;
+ }
- return scan_register(service, prim);
+ scan = g_new0(struct scan, 1);
+ if (!scan)
+ return -1;
+
+ scan->device = btd_device_ref(device);
+ devices = g_slist_append(devices, scan);
+ return 0;
}
static struct btd_profile scan_profile = {
@@ -258,6 +265,7 @@ static struct btd_profile scan_profile = {
.remote_uuid = SCAN_PARAMETERS_UUID,
.device_probe = scan_param_probe,
.device_remove = scan_param_remove,
+ .accept = scan_param_accept,
};
static int scan_param_init(void)
diff --git a/profiles/scanparam/scpp.c b/profiles/scanparam/scpp.c
new file mode 100644
index 00000000..df65d2c1
--- /dev/null
+++ b/profiles/scanparam/scpp.c
@@ -0,0 +1,355 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ *
+ * 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 "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+
+#include "profiles/scanparam/scpp.h"
+
+#define SCAN_INTERVAL_WIN_UUID 0x2A4F
+#define SCAN_REFRESH_UUID 0x2A31
+
+#define SCAN_INTERVAL 0x0060
+#define SCAN_WINDOW 0x0030
+#define SERVER_REQUIRES_REFRESH 0x00
+
+struct bt_scpp {
+ int ref_count;
+ GAttrib *attrib;
+ struct gatt_primary *primary;
+ uint16_t interval;
+ uint16_t window;
+ uint16_t iwhandle;
+ uint16_t refresh_handle;
+ guint refresh_cb_id;
+ struct queue *gatt_op;
+};
+
+static void discover_char(struct bt_scpp *scpp, GAttrib *attrib,
+ uint16_t start, uint16_t end,
+ bt_uuid_t *uuid, gatt_cb_t func,
+ gpointer user_data)
+{
+ unsigned int id;
+
+ id = gatt_discover_char(attrib, start, end, uuid, func, user_data);
+
+ queue_push_head(scpp->gatt_op, UINT_TO_PTR(id));
+}
+
+static void discover_desc(struct bt_scpp *scpp, GAttrib *attrib,
+ uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ gatt_cb_t func, gpointer user_data)
+{
+ unsigned int id;
+
+ id = gatt_discover_desc(attrib, start, end, uuid, func, user_data);
+
+ queue_push_head(scpp->gatt_op, UINT_TO_PTR(id));
+}
+
+static void write_char(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle,
+ const uint8_t *value, size_t vlen,
+ GAttribResultFunc func,
+ gpointer user_data)
+{
+ unsigned int id;
+
+ id = gatt_write_char(attrib, handle, value, vlen, func, user_data);
+
+ queue_push_head(scan->gatt_op, UINT_TO_PTR(id));
+}
+
+static void scpp_free(struct bt_scpp *scan)
+{
+ bt_scpp_detach(scan);
+
+ g_free(scan->primary);
+ queue_destroy(scan->gatt_op, NULL); /* cleared in bt_scpp_detach */
+ g_free(scan);
+}
+
+struct bt_scpp *bt_scpp_new(void *primary)
+{
+ struct bt_scpp *scan;
+
+ scan = g_try_new0(struct bt_scpp, 1);
+ if (!scan)
+ return NULL;
+
+ scan->interval = SCAN_INTERVAL;
+ scan->window = SCAN_WINDOW;
+
+ scan->gatt_op = queue_new();
+
+ if (primary)
+ scan->primary = g_memdup(primary, sizeof(*scan->primary));
+
+ return bt_scpp_ref(scan);
+}
+
+struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan)
+{
+ if (!scan)
+ return NULL;
+
+ __sync_fetch_and_add(&scan->ref_count, 1);
+
+ return scan;
+}
+
+void bt_scpp_unref(struct bt_scpp *scan)
+{
+ if (!scan)
+ return;
+
+ if (__sync_sub_and_fetch(&scan->ref_count, 1))
+ return;
+
+ scpp_free(scan);
+}
+
+static void write_scan_params(GAttrib *attrib, uint16_t handle,
+ uint16_t interval, uint16_t window)
+{
+ uint8_t value[4];
+
+ put_le16(interval, &value[0]);
+ put_le16(window, &value[2]);
+
+ gatt_write_cmd(attrib, handle, value, sizeof(value), NULL, NULL);
+}
+
+static void refresh_value_cb(const uint8_t *pdu, uint16_t len,
+ gpointer user_data)
+{
+ struct bt_scpp *scan = user_data;
+
+ DBG("Server requires refresh: %d", pdu[3]);
+
+ if (pdu[3] == SERVER_REQUIRES_REFRESH)
+ write_scan_params(scan->attrib, scan->iwhandle, scan->interval,
+ scan->window);
+}
+
+static void ccc_written_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct bt_scpp *scan = user_data;
+
+ if (status != 0) {
+ error("Write Scan Refresh CCC failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ DBG("Scan Refresh: notification enabled");
+
+ scan->refresh_cb_id = g_attrib_register(scan->attrib,
+ ATT_OP_HANDLE_NOTIFY, scan->refresh_handle,
+ refresh_value_cb, scan, NULL);
+}
+
+static void write_ccc(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle,
+ void *user_data)
+{
+ uint8_t value[2];
+
+ put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value);
+
+ write_char(scan, attrib, handle, value, sizeof(value), ccc_written_cb,
+ user_data);
+}
+
+static void discover_descriptor_cb(uint8_t status, GSList *descs,
+ void *user_data)
+{
+ struct bt_scpp *scan = user_data;
+ struct gatt_desc *desc;
+
+ 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;
+
+ write_ccc(scan, scan->attrib, desc->handle, scan);
+}
+
+static void refresh_discovered_cb(uint8_t status, GSList *chars,
+ void *user_data)
+{
+ struct bt_scpp *scan = user_data;
+ struct gatt_char *chr;
+ uint16_t start, end;
+ bt_uuid_t uuid;
+
+ if (status) {
+ error("Scan Refresh %s", att_ecode2str(status));
+ return;
+ }
+
+ if (!chars) {
+ DBG("Scan Refresh not supported");
+ return;
+ }
+
+ chr = chars->data;
+
+ DBG("Scan Refresh handle: 0x%04x", chr->value_handle);
+
+ start = chr->value_handle + 1;
+ end = scan->primary->range.end;
+
+ if (start > end)
+ return;
+
+ scan->refresh_handle = chr->value_handle;
+
+ bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+
+ discover_desc(scan, scan->attrib, start, end, &uuid,
+ discover_descriptor_cb, user_data);
+}
+
+static void iwin_discovered_cb(uint8_t status, GSList *chars, void *user_data)
+{
+ struct bt_scpp *scan = user_data;
+ struct gatt_char *chr;
+
+ if (status) {
+ error("Discover Scan Interval Window: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ chr = chars->data;
+ scan->iwhandle = chr->value_handle;
+
+ DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle);
+
+ write_scan_params(scan->attrib, scan->iwhandle, scan->interval,
+ scan->window);
+}
+
+bool bt_scpp_attach(struct bt_scpp *scan, void *attrib)
+{
+ bt_uuid_t iwin_uuid, refresh_uuid;
+
+ if (!scan || scan->attrib || !scan->primary)
+ return false;
+
+ scan->attrib = g_attrib_ref(attrib);
+
+ if (scan->iwhandle)
+ write_scan_params(scan->attrib, scan->iwhandle, scan->interval,
+ scan->window);
+ else {
+ bt_uuid16_create(&iwin_uuid, SCAN_INTERVAL_WIN_UUID);
+ discover_char(scan, scan->attrib, scan->primary->range.start,
+ scan->primary->range.end, &iwin_uuid,
+ iwin_discovered_cb, scan);
+ }
+
+ if (scan->refresh_handle)
+ scan->refresh_cb_id = g_attrib_register(scan->attrib,
+ ATT_OP_HANDLE_NOTIFY, scan->refresh_handle,
+ refresh_value_cb, scan, NULL);
+ else {
+ bt_uuid16_create(&refresh_uuid, SCAN_REFRESH_UUID);
+ discover_char(scan, scan->attrib, scan->primary->range.start,
+ scan->primary->range.end, &refresh_uuid,
+ refresh_discovered_cb, scan);
+ }
+
+ return true;
+}
+
+static void cancel_gatt_req(void *data, void *user_data)
+{
+ unsigned int id = PTR_TO_UINT(data);
+ struct bt_scpp *scan = user_data;
+
+ g_attrib_cancel(scan->attrib, id);
+}
+
+void bt_scpp_detach(struct bt_scpp *scan)
+{
+ if (!scan || !scan->attrib)
+ return;
+
+ if (scan->refresh_cb_id > 0) {
+ g_attrib_unregister(scan->attrib, scan->refresh_cb_id);
+ scan->refresh_cb_id = 0;
+ }
+
+ queue_foreach(scan->gatt_op, cancel_gatt_req, scan);
+ g_attrib_unref(scan->attrib);
+ scan->attrib = NULL;
+}
+
+bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value)
+{
+ if (!scan)
+ return false;
+
+ /* TODO: Check valid range */
+
+ scan->interval = value;
+
+ return true;
+}
+
+bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value)
+{
+ if (!scan)
+ return false;
+
+ /* TODO: Check valid range */
+
+ scan->window = value;
+
+ return true;
+}
diff --git a/profiles/scanparam/scpp.h b/profiles/scanparam/scpp.h
new file mode 100644
index 00000000..048fb9f2
--- /dev/null
+++ b/profiles/scanparam/scpp.h
@@ -0,0 +1,35 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ *
+ *
+ * This library is free software; you can rescpptribute 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 scpptributed 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
+ *
+ */
+
+struct bt_scpp;
+
+struct bt_scpp *bt_scpp_new(void *primary);
+
+struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan);
+void bt_scpp_unref(struct bt_scpp *scan);
+
+bool bt_scpp_attach(struct bt_scpp *scan, void *gatt);
+void bt_scpp_detach(struct bt_scpp *scan);
+
+bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value);
+bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value);