[PATCH v2 3/4] android/gatt: Add GATT Connect

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch introduce connect LE device functionality.

There is gatt_device representing remote le device. Each gatt device
has a list own list of clients as it is possible that more apps
would like to use same remote device.

Possible connect scenarios:

1.  There is no ACL connection to device:
Then new dev is put on conn_wait_queue and le scan is enabled.
Once device is found we do connect it.

Once device is connected then device is moved form conn_wait_queue to
conn_list and success event is sent to client(s) with conn_id

2. Device is already connected:
Then we update client list, reply with success and do send connect event.

3. For unregisterd clients or uknown conn_id, failed response is sent.
---
 android/Android.mk  |   4 +
 android/Makefile.am |   3 +
 android/gatt.c      | 475 +++++++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 463 insertions(+), 19 deletions(-)

diff --git a/android/Android.mk b/android/Android.mk
index 34e21ea..0e0932d 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -60,9 +60,13 @@ LOCAL_SRC_FILES := \
 	bluez/lib/sdp.c \
 	bluez/lib/bluetooth.c \
 	bluez/lib/hci.c \
+	bluez/lib/uuid.c \
 	bluez/btio/btio.c \
 	bluez/src/sdp-client.c \
 	bluez/profiles/network/bnep.c \
+	bluez/attrib/gattrib.c \
+	bluez/attrib/gatt.c \
+	bluez/attrib/att.c
 
 LOCAL_C_INCLUDES := \
 	$(call include-path-for, glib) \
diff --git a/android/Makefile.am b/android/Makefile.am
index e7de560..1d0747e 100644
--- a/android/Makefile.am
+++ b/android/Makefile.am
@@ -42,6 +42,9 @@ android_bluetoothd_SOURCES = android/main.c \
 				android/handsfree.h android/handsfree.c \
 				android/gatt.h android/gatt.c \
 				android/health.h android/health.c \
+				attrib/att.c attrib/att.h \
+				attrib/gatt.c attrib/gatt.h \
+				attrib/gattrib.c attrib/gattrib.h \
 				btio/btio.h btio/btio.c \
 				src/sdp-client.h src/sdp-client.c \
 				profiles/network/bnep.h profiles/network/bnep.c
diff --git a/android/gatt.c b/android/gatt.c
index 38f7c1b..9afdf4b 100644
--- a/android/gatt.c
+++ b/android/gatt.c
@@ -29,10 +29,13 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include <glib.h>
+#include <errno.h>
+#include <sys/socket.h>
 
 #include "ipc.h"
 #include "ipc-common.h"
 #include "lib/sdp.h"
+#include "lib/uuid.h"
 #include "bluetooth.h"
 #include "gatt.h"
 #include "src/log.h"
@@ -40,16 +43,38 @@
 #include "utils.h"
 #include "src/shared/util.h"
 #include "src/shared/queue.h"
+#include "attrib/gattrib.h"
+#include "attrib/att.h"
+#include "attrib/gatt.h"
+#include "btio/btio.h"
 
 struct gatt_client {
 	int32_t id;
 	uint8_t uuid[16];
 };
 
+struct gatt_device {
+	bdaddr_t bdaddr;
+	uint8_t bdaddr_type;
+
+	struct queue *clients;
+
+	bool connect_ready;
+	int32_t conn_id;
+
+	GAttrib *attrib;
+	GIOChannel *att_io;
+
+	guint watch_id;
+};
+
 static struct ipc *hal_ipc = NULL;
 static bdaddr_t adapter_addr;
+
 static struct queue *gatt_clients = NULL;
 static struct queue *scan_clients = NULL;
+static struct queue *conn_list	= NULL;		/* Connected devices */
+static struct queue *conn_wait_queue = NULL;	/* Devices waiting for connect */
 
 static bool match_client_by_uuid(const void *data, const void *user_data)
 {
@@ -72,29 +97,25 @@ static bool match_by_value(const void *data, const void *user_data)
 	return data == user_data;
 }
 
-static void le_device_found_handler(bdaddr_t *addr, uint8_t addr_type, int rssi,
-					uint16_t eir_len, const void *eir)
+static bool match_dev_by_bdaddr(const void *data, const void *user_data)
 {
-	uint8_t buf[IPC_MTU];
-	struct hal_ev_gatt_client_scan_result *ev = (void *) buf;
-	char bda[18];
+	const struct gatt_device *dev = data;
+	const bdaddr_t *addr = user_data;
 
-	if (queue_isempty(scan_clients))
-		return;
-
-	ba2str(addr, bda);
-	DBG("gatt: LE Device found: %s, rssi: %d, adv_data: %d", bda, rssi,
-							eir ? true : false);
+	return !bacmp(&dev->bdaddr, addr);
+}
 
-	bdaddr2android(addr, ev->bda);
-	ev->rssi = rssi;
-	ev->len = eir_len;
+static bool match_dev_connect_ready(const void *data, const void *user_data)
+{
+	const struct gatt_device *dev = data;
 
-	memcpy(ev->adv_data, eir, ev->len);
+	return dev->connect_ready;
+}
 
-	ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT,
-						HAL_EV_GATT_CLIENT_SCAN_RESULT,
-						sizeof(ev) + eir_len, ev);
+static void destroy_device(struct gatt_device *dev)
+{
+	queue_destroy(dev->clients, NULL);
+	free(dev);
 }
 
 static void handle_client_register(const void *buf, uint16_t len)
@@ -172,6 +193,253 @@ failed:
 					HAL_OP_GATT_CLIENT_UNREGISTER, status);
 }
 
+static void connection_cleanup(struct gatt_device *device)
+{
+	if (device->watch_id) {
+		g_source_remove(device->watch_id);
+		device->watch_id = 0;
+	}
+
+	if (device->att_io) {
+		g_io_channel_shutdown(device->att_io, FALSE, NULL);
+		g_io_channel_unref(device->att_io);
+		device->att_io = NULL;
+	}
+
+	if (device->attrib) {
+		GAttrib *attrib = device->attrib;
+		device->attrib = NULL;
+		g_attrib_cancel_all(attrib);
+		g_attrib_unref(attrib);
+	}
+}
+
+static void send_disconnect_notify(void *data, void *user_data)
+{
+	struct hal_ev_gatt_client_disconnect ev;
+	struct gatt_device *dev = user_data;
+
+	ev.client_if = PTR_TO_INT(data);
+	ev.conn_id = dev->conn_id;
+	ev.status = HAL_STATUS_SUCCESS;
+	bdaddr2android(&dev->bdaddr, &ev.bda);
+
+	ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT,
+			HAL_EV_GATT_CLIENT_DISCONNECT, sizeof(ev), &ev);
+}
+
+static bool is_device_wating_for_connect(const bdaddr_t *addr, uint8_t addr_type)
+{
+	struct gatt_device *dev;
+
+	DBG("");
+
+	dev = queue_find(conn_wait_queue, match_dev_by_bdaddr, (void *)addr);
+	if (!dev)
+		return false;
+
+	dev->bdaddr_type = addr_type;
+
+	/* Mark that this device is ready for connect.
+	 * Need it because will continue with connect after scan is stopped
+	 */
+	dev->connect_ready = true;
+
+	return true;
+}
+
+static void bt_le_discovery_stop_cb(void);
+
+static void le_device_found_handler(bdaddr_t *addr, uint8_t addr_type,
+						int rssi, uint16_t eir_len,
+							const void *eir)
+{
+	uint8_t buf[IPC_MTU];
+	struct hal_ev_gatt_client_scan_result *ev = (void *) buf;
+	char bda[18];
+
+	if (queue_isempty(scan_clients))
+		goto connect;
+
+	ba2str(addr, bda);
+	DBG("gatt: LE Device found: %s, rssi: %d, adv_data: %d", bda, rssi,
+									!!eir);
+
+	bdaddr2android(addr, ev->bda);
+	ev->rssi = rssi;
+	ev->len = eir_len;
+
+	memcpy(ev->adv_data, eir, ev->len);
+
+	ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT,
+						HAL_EV_GATT_CLIENT_SCAN_RESULT,
+						sizeof(ev) + eir_len, ev);
+
+connect:
+	if (!is_device_wating_for_connect(addr, addr_type))
+		return;
+
+	/* We are ok to perform connect now. Stop discovery
+	* and once it is stopped continue with creating ACL
+	*/
+	bt_le_discovery_stop(bt_le_discovery_stop_cb);
+}
+
+static gboolean disconnected_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	bdaddr_t *addr = user_data;
+	struct gatt_device *dev;
+	int sock, err = 0;
+	socklen_t len;
+
+	sock = g_io_channel_unix_get_fd(io);
+	len = sizeof(err);
+	getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len);
+
+	DBG("%s (%d)", strerror(err), err);
+
+	dev = queue_remove_if(conn_list, match_dev_by_bdaddr, addr);
+	connection_cleanup(dev);
+
+	/* Keep scanning/re-connection active if disconnection reason
+	 * is connection timeout, remote user terminated connection or local
+	 * initiated disconnection.
+	 */
+	if (err == ETIMEDOUT || err == ECONNRESET || err == ECONNABORTED) {
+		if (!queue_push_tail(conn_wait_queue, dev)) {
+			error("gatt: Cannot push data");
+		} else {
+			bt_le_discovery_start(le_device_found_handler);
+			return FALSE;
+		}
+	}
+
+	queue_foreach(dev->clients, send_disconnect_notify, dev);
+	destroy_device(dev);
+
+	return FALSE;
+}
+
+static void send_client_connect_notify(void *data, void *user_data)
+{
+	struct hal_ev_gatt_client_connect *ev = user_data;
+	int32_t id = PTR_TO_INT(data);
+
+	ev->client_if = id;
+
+	ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT,
+				HAL_EV_GATT_CLIENT_CONNECT, sizeof(*ev), ev);
+
+}
+static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
+{
+	bdaddr_t *addr = user_data;
+	struct gatt_device *dev;
+	struct hal_ev_gatt_client_connect ev;
+	GAttrib *attrib;
+	static uint32_t conn_id = 0;
+	uint8_t status;
+
+	/* Take device form conn waiting queue */
+	dev = queue_remove_if(conn_wait_queue, match_dev_by_bdaddr, addr);
+	if (!dev) {
+		error("gatt: Device not on the connect wait queue!?");
+		g_io_channel_shutdown(io, TRUE, NULL);
+		return;
+	}
+
+	g_io_channel_unref(dev->att_io);
+	dev->att_io = NULL;
+
+	/* Set address and client id in the event */
+	bdaddr2android(&dev->bdaddr, &ev.bda);
+
+	if (gerr) {
+		error("gatt: connection failed %s", gerr->message);
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	attrib = g_attrib_new(io);
+	if (!attrib) {
+		error("gatt: unable to create new GAttrib instance");
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	dev->attrib = attrib;
+	dev->watch_id = g_io_add_watch(io, G_IO_HUP, disconnected_cb, dev);
+	dev->conn_id = ++conn_id;
+
+	/* Move gatt device from connect queue to conn_list */
+	if (!queue_push_tail(conn_list, dev)) {
+		error("gatt: Cannot push dev on conn_list");
+		connection_cleanup(dev);
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	status = HAL_STATUS_SUCCESS;
+	goto reply;
+
+reply:
+	ev.conn_id = dev ? conn_id : 0;
+	ev.status = status;
+
+	queue_foreach(dev->clients, send_client_connect_notify, &ev);
+
+	/* If connection did not succeed, destroy device */
+	if (status)
+		destroy_device(dev);
+}
+
+static int connect_le(struct gatt_device *dev)
+{
+	BtIOSecLevel sec_level;
+	GIOChannel *io;
+	GError *gerr = NULL;
+	char addr[18];
+
+	ba2str(&dev->bdaddr, addr);
+
+	/* There is one connection attempt going on */
+	if (dev->att_io) {
+		info("gatt: connection to dev %s is ongoing", addr);
+		return -EALREADY;
+	}
+
+	DBG("Connection attempt to: %s", addr);
+
+	/*TODO: If we are bonded then we should use higier sec level */
+	sec_level = BT_IO_SEC_LOW;
+
+	/*
+	 * This connection will help us catch any PDUs that comes before
+	 * pairing finishes
+	 */
+	io = bt_io_connect(connect_cb, dev, NULL, &gerr,
+			BT_IO_OPT_SOURCE_BDADDR,
+			&adapter_addr,
+			BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC,
+			BT_IO_OPT_DEST_BDADDR, &dev->bdaddr,
+			BT_IO_OPT_DEST_TYPE, dev->bdaddr_type,
+			BT_IO_OPT_CID, ATT_CID,
+			BT_IO_OPT_SEC_LEVEL, sec_level,
+			BT_IO_OPT_INVALID);
+	if (!io) {
+		error("gatt: Failed bt_io_connect(%s): %s", addr,
+							gerr->message);
+		g_error_free(gerr);
+		return -EIO;
+	}
+
+	/* Keep this, so we can cancel the connection */
+	dev->att_io = io;
+
+	return 0;
+}
+
 static void handle_client_scan(const void *buf, uint16_t len)
 {
 	const struct hal_cmd_gatt_client_scan *cmd = buf;
@@ -229,12 +497,166 @@ reply:
 									status);
 }
 
+static int connect_next_dev(void)
+{
+	struct gatt_device *dev;
+
+	DBG("");
+
+	if (queue_isempty(conn_wait_queue))
+		return 0;
+
+	/* Discovery has been stopped because there is connection waiting */
+	dev = queue_find(conn_wait_queue, match_dev_connect_ready, NULL);
+	if (!dev)
+		/* Lets try again. */
+		return -1;
+
+	dev->connect_ready = false;
+
+	return connect_le(dev);
+}
+
+static void bt_le_discovery_stop_cb(void)
+{
+	DBG("");
+
+	/* Check now if there is any device ready to connect*/
+	if (connect_next_dev() < 0)
+		bt_le_discovery_start(le_device_found_handler);
+}
+
+static struct gatt_device *find_device(bdaddr_t *addr)
+{
+	struct gatt_device *dev;
+
+	dev = queue_find(conn_list, match_dev_by_bdaddr, addr);
+	if (dev)
+		return dev;
+
+	dev = queue_find(conn_wait_queue, match_dev_by_bdaddr, addr);
+	if (dev)
+		return dev;
+
+	return NULL;
+}
+
 static void handle_client_connect(const void *buf, uint16_t len)
 {
+	const struct hal_cmd_gatt_client_connect *cmd = buf;
+	struct gatt_device *dev = NULL;
+	void *l;
+	bdaddr_t addr;
+	uint8_t status;
+	bool send_notify = false;
+
 	DBG("");
 
+	/* Check if client is registered */
+	l = queue_find(gatt_clients, match_client_by_id,
+						INT_TO_PTR(cmd->client_if));
+	if (!l) {
+		error("gatt: Client id %d not found", cmd->client_if);
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	android2bdaddr(&cmd->bdaddr, &addr);
+
+	/* We do support many clients for one device connection so lets check
+	  * If device is connected or in connecting state just update list of
+	  * clients
+	  */
+	dev = find_device(&addr);
+	if (dev) {
+
+		status = HAL_STATUS_SUCCESS;
+
+		/* Remeber to send dummy notification event  if we area
+		 * connected
+		 */
+		if (dev->conn_id)
+			send_notify = true;
+
+		if (queue_find(dev->clients, match_by_value,
+						INT_TO_PTR(cmd->client_if)))
+				goto reply;
+
+		/* Store another client */
+		if (!queue_push_tail(dev->clients,
+						INT_TO_PTR(cmd->client_if))) {
+			error("gatt: Cannot push client on gatt device list");
+			status = HAL_STATUS_FAILED;
+			goto reply;
+		}
+
+		goto reply;
+	}
+
+	/* Lets create new gatt device and put it on conn_wait_queue.
+	  * Once it is connected we move it to conn_list
+	  */
+	dev = new0(struct gatt_device, 1);
+	if (!dev) {
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	memcpy(&dev->bdaddr, &addr, sizeof(bdaddr_t));
+
+	/* Create queue to keep list of clients for given device*/
+	dev->clients = queue_new();
+	if (!dev->clients) {
+		error("gatt: Cannot create client queue");
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	/* Update client list of device */
+	if (!queue_push_tail(dev->clients, INT_TO_PTR(cmd->client_if))) {
+		error("gatt: Cannot push client on the client queue!?");
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	/* Start le scan if not started */
+	if (queue_isempty(scan_clients)) {
+		if (!bt_le_discovery_start(le_device_found_handler)) {
+			error("gatt: Could not start scan");
+			status = HAL_STATUS_FAILED;
+			goto reply;
+		}
+	}
+
+	if (!queue_push_tail(conn_wait_queue, dev)) {
+		error("gatt: Cannot push device on conn_wait_queue");
+		status = HAL_STATUS_FAILED;
+		goto reply;
+	}
+
+	status = HAL_STATUS_SUCCESS;
+	goto reply;
+
+reply:
 	ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, HAL_OP_GATT_CLIENT_CONNECT,
-							HAL_STATUS_FAILED);
+								status);
+
+	/* Send dummy notification since ACL is already up*/
+	if (send_notify) {
+		struct hal_ev_gatt_client_connect ev;
+
+		ev.conn_id = dev->conn_id;
+		ev.status = HAL_STATUS_SUCCESS;
+		ev.client_if = cmd->client_if;
+		bdaddr2android(&addr, &ev.bda);
+
+		ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT,
+						HAL_EV_GATT_CLIENT_CONNECT,
+						sizeof(ev), &ev);
+	}
+
+	if (status && dev)
+		destroy_device(dev);
 }
 
 static void handle_client_disconnect(const void *buf, uint16_t len)
@@ -610,6 +1032,18 @@ bool bt_gatt_register(struct ipc *ipc, const bdaddr_t *addr)
 
 	hal_ipc = ipc;
 
+	conn_list = queue_new();
+	if (!conn_list) {
+		error("gatt: Can not create conn queue");
+		return false;
+	}
+
+	conn_wait_queue = queue_new();
+	if (!conn_wait_queue) {
+		error("gatt: Can not create conn queue");
+		return false;
+	}
+
 	ipc_register(hal_ipc, HAL_SERVICE_ID_GATT, cmd_handlers,
 						G_N_ELEMENTS(cmd_handlers));
 
@@ -637,4 +1071,7 @@ void bt_gatt_unregister(void)
 
 	ipc_unregister(hal_ipc, HAL_SERVICE_ID_GATT);
 	hal_ipc = NULL;
+
+	queue_destroy(conn_list, NULL);
+	queue_destroy(conn_wait_queue, NULL);
 }
-- 
1.8.4

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux