1/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6#include <dbus/dbus.h>
7
8#include <errno.h>
9#include <stdint.h>
10#include <stdlib.h>
11#include <string.h>
12#include <syslog.h>
13#include <unistd.h>
14
15#include "cras_bt_device.h"
16#include "cras_bt_endpoint.h"
17#include "cras_bt_transport.h"
18#include "cras_bt_constants.h"
19#include "utlist.h"
20
21
22struct cras_bt_transport {
23	DBusConnection *conn;
24	char *object_path;
25	struct cras_bt_device *device;
26	enum cras_bt_device_profile profile;
27	int codec;
28	void *configuration;
29	int configuration_len;
30	enum cras_bt_transport_state state;
31	int fd;
32	uint16_t read_mtu;
33	uint16_t write_mtu;
34	int volume;
35
36	struct cras_bt_endpoint *endpoint;
37	struct cras_bt_transport *prev, *next;
38};
39
40static struct cras_bt_transport *transports;
41
42struct cras_bt_transport *cras_bt_transport_create(DBusConnection *conn,
43						   const char *object_path)
44{
45	struct cras_bt_transport *transport;
46
47	transport = calloc(1, sizeof(*transport));
48	if (transport == NULL)
49		return NULL;
50
51	transport->object_path = strdup(object_path);
52	if (transport->object_path == NULL) {
53		free(transport);
54		return NULL;
55	}
56
57	transport->conn = conn;
58	dbus_connection_ref(transport->conn);
59
60	transport->fd = -1;
61	transport->volume = -1;
62
63	DL_APPEND(transports, transport);
64
65	return transport;
66}
67
68void cras_bt_transport_set_endpoint(struct cras_bt_transport *transport,
69				    struct cras_bt_endpoint *endpoint) {
70	transport->endpoint = endpoint;
71}
72
73void cras_bt_transport_destroy(struct cras_bt_transport *transport)
74{
75	DL_DELETE(transports, transport);
76
77	dbus_connection_unref(transport->conn);
78
79	if (transport->fd >= 0)
80		close(transport->fd);
81
82	free(transport->object_path);
83	free(transport->configuration);
84	free(transport);
85}
86
87void cras_bt_transport_reset()
88{
89	while (transports) {
90		syslog(LOG_INFO, "Bluetooth Transport: %s removed",
91		       transports->object_path);
92		cras_bt_transport_destroy(transports);
93	}
94}
95
96
97struct cras_bt_transport *cras_bt_transport_get(const char *object_path)
98{
99	struct cras_bt_transport *transport;
100
101	DL_FOREACH(transports, transport) {
102		if (strcmp(transport->object_path, object_path) == 0)
103			return transport;
104	}
105
106	return NULL;
107}
108
109size_t cras_bt_transport_get_list(
110	struct cras_bt_transport ***transport_list_out)
111{
112	struct cras_bt_transport *transport;
113	struct cras_bt_transport **transport_list = NULL;
114	size_t num_transports = 0;
115
116	DL_FOREACH(transports, transport) {
117		struct cras_bt_transport **tmp;
118
119		tmp = realloc(transport_list,
120			      sizeof(transport_list[0]) * (num_transports + 1));
121		if (!tmp) {
122			free(transport_list);
123			return -ENOMEM;
124		}
125
126		transport_list = tmp;
127		transport_list[num_transports++] = transport;
128	}
129
130	*transport_list_out = transport_list;
131	return num_transports;
132}
133
134const char *cras_bt_transport_object_path(
135	const struct cras_bt_transport *transport)
136{
137	return transport->object_path;
138}
139
140struct cras_bt_device *cras_bt_transport_device(
141	const struct cras_bt_transport *transport)
142{
143	return transport->device;
144}
145
146enum cras_bt_device_profile cras_bt_transport_profile(
147	const struct cras_bt_transport *transport)
148{
149	return transport->profile;
150}
151
152int cras_bt_transport_configuration(const struct cras_bt_transport *transport,
153				    void *configuration, int len)
154{
155	if (len < transport->configuration_len)
156		return -ENOSPC;
157
158	memcpy(configuration, transport->configuration,
159	       transport->configuration_len);
160
161	return 0;
162}
163
164enum cras_bt_transport_state cras_bt_transport_state(
165	const struct cras_bt_transport *transport)
166{
167	return transport->state;
168}
169
170int cras_bt_transport_fd(const struct cras_bt_transport *transport)
171{
172	return transport->fd;
173}
174
175uint16_t cras_bt_transport_write_mtu(const struct cras_bt_transport *transport)
176{
177	return transport->write_mtu;
178}
179
180static enum cras_bt_transport_state cras_bt_transport_state_from_string(
181	const char *value)
182{
183	if (strcmp("idle", value) == 0)
184		return CRAS_BT_TRANSPORT_STATE_IDLE;
185	else if (strcmp("pending", value) == 0)
186		return CRAS_BT_TRANSPORT_STATE_PENDING;
187	else if (strcmp("active", value) == 0)
188		return CRAS_BT_TRANSPORT_STATE_ACTIVE;
189	else
190		return CRAS_BT_TRANSPORT_STATE_IDLE;
191}
192
193static void cras_bt_transport_state_changed(struct cras_bt_transport *transport)
194{
195	if (transport->endpoint &&
196	    transport->endpoint->transport_state_changed)
197		transport->endpoint->transport_state_changed(
198				transport->endpoint,
199				transport);
200}
201
202/* Updates bt_device when certain transport property has changed. */
203static void cras_bt_transport_update_device(struct cras_bt_transport *transport)
204{
205	if (!transport->device)
206		return;
207
208	/* When the transport has non-negaive volume, it means the remote
209	 * BT audio devices supports AVRCP absolute volume. Set the flag in bt
210	 * device to use hardware volume. Also map the volume value from 0-127
211	 * to 0-100.
212	 */
213	if (transport->volume != -1) {
214		cras_bt_device_set_use_hardware_volume(transport->device, 1);
215		cras_bt_device_update_hardware_volume(
216				transport->device,
217				transport->volume * 100 / 127);
218	}
219}
220
221void cras_bt_transport_update_properties(
222	struct cras_bt_transport *transport,
223	DBusMessageIter *properties_array_iter,
224	DBusMessageIter *invalidated_array_iter)
225{
226	while (dbus_message_iter_get_arg_type(properties_array_iter) !=
227	       DBUS_TYPE_INVALID) {
228		DBusMessageIter properties_dict_iter, variant_iter;
229		const char *key;
230		int type;
231
232		dbus_message_iter_recurse(properties_array_iter,
233					  &properties_dict_iter);
234
235		dbus_message_iter_get_basic(&properties_dict_iter, &key);
236		dbus_message_iter_next(&properties_dict_iter);
237
238		dbus_message_iter_recurse(&properties_dict_iter, &variant_iter);
239		type = dbus_message_iter_get_arg_type(&variant_iter);
240
241		if (type == DBUS_TYPE_STRING) {
242			const char *value;
243
244			dbus_message_iter_get_basic(&variant_iter, &value);
245
246			if (strcmp(key, "UUID") == 0) {
247				transport->profile =
248					cras_bt_device_profile_from_uuid(value);
249
250			} else if (strcmp(key, "State") == 0) {
251				enum cras_bt_transport_state old_state =
252					transport->state;
253				transport->state =
254					cras_bt_transport_state_from_string(
255						value);
256				if (transport->state != old_state)
257					cras_bt_transport_state_changed(
258						transport);
259			}
260
261		} else if (type == DBUS_TYPE_BYTE) {
262			int value;
263
264			dbus_message_iter_get_basic(&variant_iter, &value);
265
266			if (strcmp(key, "Codec") == 0)
267				transport->codec = value;
268		} else if (type == DBUS_TYPE_OBJECT_PATH) {
269			const char *obj_path;
270
271			/* Property: object Device [readonly] */
272			dbus_message_iter_get_basic(&variant_iter, &obj_path);
273			transport->device = cras_bt_device_get(obj_path);
274			if (!transport->device) {
275				syslog(LOG_ERR, "Device %s not found at update"
276				       "transport properties",
277				       obj_path);
278				transport->device =
279					cras_bt_device_create(transport->conn,
280							      obj_path);
281				cras_bt_transport_update_device(transport);
282			}
283		} else if (strcmp(
284				dbus_message_iter_get_signature(&variant_iter),
285				"ay") == 0 &&
286			   strcmp(key, "Configuration") == 0) {
287			DBusMessageIter value_iter;
288			char *value;
289			int len;
290
291			dbus_message_iter_recurse(&variant_iter, &value_iter);
292			dbus_message_iter_get_fixed_array(&value_iter,
293							  &value, &len);
294
295			free(transport->configuration);
296			transport->configuration_len = 0;
297
298			transport->configuration = malloc(len);
299			if (transport->configuration) {
300				memcpy(transport->configuration, value, len);
301				transport->configuration_len = len;
302			}
303
304		} else if (strcmp(key, "Volume") == 0) {
305			uint16_t volume;
306
307			dbus_message_iter_get_basic(&variant_iter, &volume);
308			transport->volume = volume;
309			cras_bt_transport_update_device(transport);
310		}
311
312		dbus_message_iter_next(properties_array_iter);
313	}
314
315	while (invalidated_array_iter &&
316	       dbus_message_iter_get_arg_type(invalidated_array_iter) !=
317	       DBUS_TYPE_INVALID) {
318		const char *key;
319
320		dbus_message_iter_get_basic(invalidated_array_iter, &key);
321
322		if (strcmp(key, "Device") == 0) {
323			transport->device = NULL;
324		} else if (strcmp(key, "UUID") == 0) {
325			transport->profile = 0;
326		} else if (strcmp(key, "State") == 0) {
327			transport->state = CRAS_BT_TRANSPORT_STATE_IDLE;
328		} else if (strcmp(key, "Codec") == 0) {
329			transport->codec = 0;
330		} else if (strcmp(key, "Configuration") == 0) {
331			free(transport->configuration);
332			transport->configuration = NULL;
333			transport->configuration_len = 0;
334		}
335
336		dbus_message_iter_next(invalidated_array_iter);
337	}
338}
339
340static void on_transport_volume_set(DBusPendingCall *pending_call, void *data)
341{
342	DBusMessage *reply;
343
344	reply = dbus_pending_call_steal_reply(pending_call);
345	dbus_pending_call_unref(pending_call);
346
347	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
348		syslog(LOG_ERR, "Set absolute volume returned error: %s",
349		       dbus_message_get_error_name(reply));
350	dbus_message_unref(reply);
351}
352
353int cras_bt_transport_set_volume(struct cras_bt_transport *transport,
354				 uint16_t volume)
355{
356	const char *key = "Volume";
357	const char *interface = BLUEZ_INTERFACE_MEDIA_TRANSPORT;
358	DBusMessage *method_call;
359	DBusMessageIter message_iter, variant;
360	DBusPendingCall *pending_call;
361
362	method_call = dbus_message_new_method_call(
363		BLUEZ_SERVICE,
364		transport->object_path,
365		DBUS_INTERFACE_PROPERTIES,
366		"Set");
367	if (!method_call)
368		return -ENOMEM;
369
370	dbus_message_iter_init_append(method_call, &message_iter);
371
372	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_STRING,
373				       &interface);
374	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_STRING, &key);
375
376	dbus_message_iter_open_container(&message_iter, DBUS_TYPE_VARIANT,
377					 DBUS_TYPE_UINT16_AS_STRING, &variant);
378	dbus_message_iter_append_basic(&variant, DBUS_TYPE_UINT16, &volume);
379	dbus_message_iter_close_container(&message_iter, &variant);
380
381	if (!dbus_connection_send_with_reply(transport->conn, method_call,
382					     &pending_call,
383					     DBUS_TIMEOUT_USE_DEFAULT)) {
384		dbus_message_unref(method_call);
385		return -ENOMEM;
386	}
387
388	dbus_message_unref(method_call);
389	if (!pending_call)
390		return -EIO;
391
392	if (!dbus_pending_call_set_notify(pending_call,
393					  on_transport_volume_set,
394					  NULL, NULL)) {
395		dbus_pending_call_cancel(pending_call);
396		dbus_pending_call_unref(pending_call);
397		return -ENOMEM;
398	}
399
400	return 0;
401}
402
403int cras_bt_transport_acquire(struct cras_bt_transport *transport)
404{
405	DBusMessage *method_call, *reply;
406	DBusError dbus_error;
407
408	if (transport->fd >= 0)
409		return 0;
410
411	method_call = dbus_message_new_method_call(
412		BLUEZ_SERVICE,
413		transport->object_path,
414		BLUEZ_INTERFACE_MEDIA_TRANSPORT,
415		"Acquire");
416	if (!method_call)
417		return -ENOMEM;
418
419	dbus_error_init(&dbus_error);
420
421	reply = dbus_connection_send_with_reply_and_block(
422		transport->conn,
423		method_call,
424		DBUS_TIMEOUT_USE_DEFAULT,
425		&dbus_error);
426	if (!reply) {
427		syslog(LOG_ERR, "Failed to acquire transport %s: %s",
428		       transport->object_path, dbus_error.message);
429		dbus_error_free(&dbus_error);
430		dbus_message_unref(method_call);
431		return -EIO;
432	}
433
434	dbus_message_unref(method_call);
435
436	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
437		syslog(LOG_ERR, "Acquire returned error: %s",
438		       dbus_message_get_error_name(reply));
439		dbus_message_unref(reply);
440		return -EIO;
441	}
442
443	if (!dbus_message_get_args(reply, &dbus_error,
444				   DBUS_TYPE_UNIX_FD, &(transport->fd),
445				   DBUS_TYPE_UINT16, &(transport->read_mtu),
446				   DBUS_TYPE_UINT16, &(transport->write_mtu),
447				   DBUS_TYPE_INVALID)) {
448		syslog(LOG_ERR, "Bad Acquire reply received: %s",
449		       dbus_error.message);
450		dbus_error_free(&dbus_error);
451		dbus_message_unref(reply);
452		return -EINVAL;
453	}
454
455	dbus_message_unref(reply);
456	return 0;
457}
458
459int cras_bt_transport_try_acquire(struct cras_bt_transport *transport)
460{
461	DBusMessage *method_call, *reply;
462	DBusError dbus_error;
463	int fd, read_mtu, write_mtu;
464
465	method_call = dbus_message_new_method_call(
466			BLUEZ_SERVICE,
467			transport->object_path,
468			BLUEZ_INTERFACE_MEDIA_TRANSPORT,
469			"TryAcquire");
470	if (!method_call)
471		return -ENOMEM;
472
473	dbus_error_init(&dbus_error);
474
475	reply = dbus_connection_send_with_reply_and_block(
476		transport->conn,
477		method_call,
478		DBUS_TIMEOUT_USE_DEFAULT,
479		&dbus_error);
480	if (!reply) {
481		syslog(LOG_ERR, "Failed to try acquire transport %s: %s",
482		       transport->object_path, dbus_error.message);
483		dbus_error_free(&dbus_error);
484		dbus_message_unref(method_call);
485		return -EIO;
486	}
487
488	dbus_message_unref(method_call);
489
490	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
491		syslog(LOG_ERR, "TryAcquire returned error: %s",
492		       dbus_message_get_error_name(reply));
493		dbus_message_unref(reply);
494		return -EIO;
495	}
496
497	if (!dbus_message_get_args(reply, &dbus_error,
498				   DBUS_TYPE_UNIX_FD, &fd,
499				   DBUS_TYPE_UINT16, &read_mtu,
500				   DBUS_TYPE_UINT16, &write_mtu,
501				   DBUS_TYPE_INVALID)) {
502		syslog(LOG_ERR, "Bad TryAcquire reply received: %s",
503		       dbus_error.message);
504		dbus_error_free(&dbus_error);
505		dbus_message_unref(reply);
506		return -EINVAL;
507	}
508
509	/* Done TryAcquired the transport so it won't be released in bluez,
510	 * no need for the new file descriptor so close it. */
511	if (transport->fd != fd)
512		close(fd);
513
514	dbus_message_unref(reply);
515	return 0;
516}
517
518/* Callback to trigger when transport release completed. */
519static void cras_bt_on_transport_release(DBusPendingCall *pending_call,
520					 void *data)
521{
522	DBusMessage *reply;
523
524	reply = dbus_pending_call_steal_reply(pending_call);
525	dbus_pending_call_unref(pending_call);
526
527	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
528		syslog(LOG_WARNING, "Release transport returned error: %s",
529		       dbus_message_get_error_name(reply));
530		dbus_message_unref(reply);
531		return;
532	}
533
534	dbus_message_unref(reply);
535}
536
537int cras_bt_transport_release(struct cras_bt_transport *transport,
538			      unsigned int blocking)
539{
540	DBusMessage *method_call, *reply;
541	DBusPendingCall *pending_call;
542	DBusError dbus_error;
543
544	if (transport->fd < 0)
545		return 0;
546
547	/* Close the transport on our end no matter whether or not the server
548	 * gives us an error.
549	 */
550	close(transport->fd);
551	transport->fd = -1;
552
553	method_call = dbus_message_new_method_call(
554		BLUEZ_SERVICE,
555		transport->object_path,
556		BLUEZ_INTERFACE_MEDIA_TRANSPORT,
557		"Release");
558	if (!method_call)
559		return -ENOMEM;
560
561	if (blocking) {
562		dbus_error_init(&dbus_error);
563
564		reply = dbus_connection_send_with_reply_and_block(
565			transport->conn,
566			method_call,
567			DBUS_TIMEOUT_USE_DEFAULT,
568			&dbus_error);
569		if (!reply) {
570			syslog(LOG_ERR, "Failed to release transport %s: %s",
571			       transport->object_path, dbus_error.message);
572			dbus_error_free(&dbus_error);
573			dbus_message_unref(method_call);
574			return -EIO;
575		}
576
577		dbus_message_unref(method_call);
578
579		if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
580			syslog(LOG_ERR, "Release returned error: %s",
581			       dbus_message_get_error_name(reply));
582			dbus_message_unref(reply);
583			return -EIO;
584		}
585
586		dbus_message_unref(reply);
587	} else {
588		if (!dbus_connection_send_with_reply(
589				transport->conn,
590				method_call,
591				&pending_call,
592				DBUS_TIMEOUT_USE_DEFAULT)) {
593			dbus_message_unref(method_call);
594			return -ENOMEM;
595		}
596
597		dbus_message_unref(method_call);
598		if (!pending_call)
599			return -EIO;
600
601		if (!dbus_pending_call_set_notify(pending_call,
602						  cras_bt_on_transport_release,
603						  transport, NULL)) {
604			dbus_pending_call_cancel(pending_call);
605			dbus_pending_call_unref(pending_call);
606			return -ENOMEM;
607		}
608	}
609	return 0;
610}
611