diff options
author | h.sandeep <h.sandeep@samsung.com> | 2016-04-07 09:39:01 +0530 |
---|---|---|
committer | Sandeep Hattiholi <h.sandeep@samsung.com> | 2016-04-12 01:16:48 -0700 |
commit | 9cc0d447358bad2c1be6e5c51d477d938e7ecbf5 (patch) | |
tree | f9849cf199e3b962a9e8d5f99826bc9def18fe85 /profiles | |
parent | 1b52151f0a5f30794e939592489a259808b4ba5a (diff) | |
download | bluez-9cc0d447358bad2c1be6e5c51d477d938e7ecbf5.tar.gz bluez-9cc0d447358bad2c1be6e5c51d477d938e7ecbf5.tar.bz2 bluez-9cc0d447358bad2c1be6e5c51d477d938e7ecbf5.zip |
Upgrade bluez5_37 :Merge the code from privatesubmit/tizen/20160414.030922accepted/tizen/wearable/20160414.091945accepted/tizen/tv/20160414.092045accepted/tizen/mobile/20160414.092019accepted/tizen/ivi/20160414.092030accepted/tizen/common/20160414.142549
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')
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); |