| /* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <dbus/dbus.h> |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <sys/socket.h> |
| #include <sys/ioctl.h> |
| |
| #include "bluetooth.h" |
| #include "cras_bt_adapter.h" |
| #include "cras_bt_constants.h" |
| #include "utlist.h" |
| |
| /* |
| * Object to represent a bluetooth adapter on the system. Used to query the |
| * capabilities regarding certain bluetooth audio. |
| * Members: |
| * conn - The dbus connection used to send message to bluetoothd. |
| * object_path - Object path of the bluetooth adapter. |
| * address - The BT address of this adapter. |
| * name - The readable name of this adapter. |
| * bluetooth_class - The bluetooth class of device. |
| * powered - Powered on or off. |
| * bus_type - Type of bus this adapter runs on. |
| * wide_band_speech - If this adapter supports wide band speech. |
| */ |
| struct cras_bt_adapter { |
| DBusConnection *conn; |
| char *object_path; |
| char *address; |
| char *name; |
| uint32_t bluetooth_class; |
| int powered; |
| int bus_type; |
| int wide_band_speech; |
| |
| struct cras_bt_adapter *prev, *next; |
| }; |
| |
| static struct cras_bt_adapter *adapters; |
| |
| static int cras_bt_adapter_query_bus_type(struct cras_bt_adapter *adapter) |
| { |
| static const char *hci_str = "hci"; |
| struct hci_dev_info dev_info; |
| char *pos; |
| int ctl, err; |
| |
| /* Object path [variable prefix]/{hci0,hci1,...} */ |
| pos = strstr(adapter->object_path, hci_str); |
| if (!pos) |
| return -1; |
| |
| ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); |
| if (ctl < 0) { |
| syslog(LOG_ERR, "Error creating HCI ctl socket"); |
| return -1; |
| } |
| |
| /* dev_id = 0 for hci0 */ |
| dev_info.type = 0; |
| dev_info.dev_id = atoi(pos + 3); |
| err = ioctl(ctl, HCIGETDEVINFO, (void *)&dev_info); |
| if (err) { |
| syslog(LOG_ERR, "HCI get dev info error %s", strerror(errno)); |
| close(ctl); |
| return -1; |
| } |
| if ((dev_info.type & 0x0f) < HCI_BUS_MAX) |
| adapter->bus_type = (dev_info.type & 0x0f); |
| |
| close(ctl); |
| return 0; |
| } |
| |
| struct cras_bt_adapter *cras_bt_adapter_create(DBusConnection *conn, |
| const char *object_path) |
| { |
| struct cras_bt_adapter *adapter; |
| |
| adapter = calloc(1, sizeof(*adapter)); |
| if (adapter == NULL) |
| return NULL; |
| |
| adapter->conn = conn; |
| adapter->object_path = strdup(object_path); |
| if (adapter->object_path == NULL) { |
| free(adapter); |
| return NULL; |
| } |
| |
| DL_APPEND(adapters, adapter); |
| |
| /* Set bus type to USB as default when query fails. */ |
| if (cras_bt_adapter_query_bus_type(adapter)) |
| adapter->bus_type = HCI_USB; |
| |
| return adapter; |
| } |
| |
| void cras_bt_adapter_destroy(struct cras_bt_adapter *adapter) |
| { |
| DL_DELETE(adapters, adapter); |
| |
| free(adapter->object_path); |
| free(adapter->address); |
| free(adapter->name); |
| free(adapter); |
| } |
| |
| void cras_bt_adapter_reset() |
| { |
| while (adapters) { |
| syslog(LOG_INFO, "Bluetooth Adapter: %s removed", |
| adapters->address); |
| cras_bt_adapter_destroy(adapters); |
| } |
| } |
| |
| struct cras_bt_adapter *cras_bt_adapter_get(const char *object_path) |
| { |
| struct cras_bt_adapter *adapter; |
| |
| if (object_path == NULL) |
| return NULL; |
| |
| DL_FOREACH (adapters, adapter) { |
| if (strcmp(adapter->object_path, object_path) == 0) |
| return adapter; |
| } |
| |
| return NULL; |
| } |
| |
| size_t cras_bt_adapter_get_list(struct cras_bt_adapter ***adapter_list_out) |
| { |
| struct cras_bt_adapter *adapter; |
| struct cras_bt_adapter **adapter_list = NULL; |
| size_t num_adapters = 0; |
| |
| DL_FOREACH (adapters, adapter) { |
| struct cras_bt_adapter **tmp; |
| |
| tmp = realloc(adapter_list, |
| sizeof(adapter_list[0]) * (num_adapters + 1)); |
| if (!tmp) { |
| free(adapter_list); |
| return -ENOMEM; |
| } |
| |
| adapter_list = tmp; |
| adapter_list[num_adapters++] = adapter; |
| } |
| |
| *adapter_list_out = adapter_list; |
| return num_adapters; |
| } |
| |
| const char *cras_bt_adapter_object_path(const struct cras_bt_adapter *adapter) |
| { |
| return adapter->object_path; |
| } |
| |
| const char *cras_bt_adapter_address(const struct cras_bt_adapter *adapter) |
| { |
| return adapter->address; |
| } |
| |
| const char *cras_bt_adapter_name(const struct cras_bt_adapter *adapter) |
| { |
| return adapter->name; |
| } |
| |
| int cras_bt_adapter_powered(const struct cras_bt_adapter *adapter) |
| { |
| return adapter->powered; |
| } |
| |
| int cras_bt_adapter_wbs_supported(struct cras_bt_adapter *adapter) |
| { |
| return adapter->wide_band_speech; |
| } |
| |
| static void bt_adapter_set_powered(struct cras_bt_adapter *adapter, int powered) |
| { |
| adapter->powered = powered; |
| if (powered) |
| cras_bt_adapter_get_supported_capabilities(adapter); |
| } |
| |
| void cras_bt_adapter_update_properties(struct cras_bt_adapter *adapter, |
| DBusMessageIter *properties_array_iter, |
| DBusMessageIter *invalidated_array_iter) |
| { |
| while (dbus_message_iter_get_arg_type(properties_array_iter) != |
| DBUS_TYPE_INVALID) { |
| DBusMessageIter properties_dict_iter, variant_iter; |
| const char *key; |
| int type; |
| |
| dbus_message_iter_recurse(properties_array_iter, |
| &properties_dict_iter); |
| |
| dbus_message_iter_get_basic(&properties_dict_iter, &key); |
| dbus_message_iter_next(&properties_dict_iter); |
| |
| dbus_message_iter_recurse(&properties_dict_iter, &variant_iter); |
| type = dbus_message_iter_get_arg_type(&variant_iter); |
| |
| if (type == DBUS_TYPE_STRING) { |
| const char *value; |
| |
| dbus_message_iter_get_basic(&variant_iter, &value); |
| |
| if (strcmp(key, "Address") == 0) { |
| free(adapter->address); |
| adapter->address = strdup(value); |
| |
| } else if (strcmp(key, "Alias") == 0) { |
| free(adapter->name); |
| adapter->name = strdup(value); |
| } |
| |
| } else if (type == DBUS_TYPE_UINT32) { |
| uint32_t value; |
| |
| dbus_message_iter_get_basic(&variant_iter, &value); |
| |
| if (strcmp(key, "Class") == 0) |
| adapter->bluetooth_class = value; |
| |
| } else if (type == DBUS_TYPE_BOOLEAN) { |
| int value; |
| |
| dbus_message_iter_get_basic(&variant_iter, &value); |
| |
| if (strcmp(key, "Powered") == 0) |
| bt_adapter_set_powered(adapter, value); |
| } |
| |
| dbus_message_iter_next(properties_array_iter); |
| } |
| |
| while (invalidated_array_iter && |
| dbus_message_iter_get_arg_type(invalidated_array_iter) != |
| DBUS_TYPE_INVALID) { |
| const char *key; |
| |
| dbus_message_iter_get_basic(invalidated_array_iter, &key); |
| |
| if (strcmp(key, "Address") == 0) { |
| free(adapter->address); |
| adapter->address = NULL; |
| } else if (strcmp(key, "Alias") == 0) { |
| free(adapter->name); |
| adapter->name = NULL; |
| } else if (strcmp(key, "Class") == 0) { |
| adapter->bluetooth_class = 0; |
| } else if (strcmp(key, "Powered") == 0) { |
| adapter->powered = 0; |
| } |
| |
| dbus_message_iter_next(invalidated_array_iter); |
| } |
| } |
| |
| int cras_bt_adapter_on_usb(struct cras_bt_adapter *adapter) |
| { |
| return !!(adapter->bus_type == HCI_USB); |
| } |
| |
| /* |
| * Expect to receive supported capabilities in reply, like below format: |
| * array [ |
| * dict entry( |
| * string "wide band speech" |
| * variant |
| * boolean <value> |
| * ) |
| * ] |
| */ |
| static void on_get_supported_capabilities_reply(DBusPendingCall *pending_call, |
| void *data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter message_iter, capabilities; |
| struct cras_bt_adapter *adapter; |
| |
| reply = dbus_pending_call_steal_reply(pending_call); |
| dbus_pending_call_unref(pending_call); |
| |
| if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { |
| syslog(LOG_ERR, |
| "GetSupportedCapabilities message replied error: %s", |
| dbus_message_get_error_name(reply)); |
| goto get_supported_capabilities_err; |
| } |
| |
| if (!dbus_message_iter_init(reply, &message_iter)) { |
| syslog(LOG_ERR, "GetSupportedCapabilities reply doesn't have" |
| "argument"); |
| goto get_supported_capabilities_err; |
| } |
| |
| DL_FOREACH (adapters, adapter) { |
| if (adapter == (struct cras_bt_adapter *)data) |
| break; |
| } |
| if (NULL == adapter) |
| goto get_supported_capabilities_err; |
| |
| dbus_message_iter_recurse(&message_iter, &capabilities); |
| |
| while (dbus_message_iter_get_arg_type(&capabilities) != |
| DBUS_TYPE_INVALID) { |
| DBusMessageIter cap_dict_iter, variant_iter; |
| const char *key; |
| int type; |
| |
| dbus_message_iter_recurse(&capabilities, &cap_dict_iter); |
| |
| dbus_message_iter_get_basic(&cap_dict_iter, &key); |
| dbus_message_iter_next(&cap_dict_iter); |
| |
| dbus_message_iter_recurse(&cap_dict_iter, &variant_iter); |
| type = dbus_message_iter_get_arg_type(&variant_iter); |
| |
| if (type == DBUS_TYPE_BOOLEAN) { |
| int value; |
| |
| dbus_message_iter_get_basic(&variant_iter, &value); |
| |
| if (strcmp(key, "wide band speech") == 0) |
| adapter->wide_band_speech = value; |
| } |
| |
| dbus_message_iter_next(&capabilities); |
| } |
| |
| get_supported_capabilities_err: |
| dbus_message_unref(reply); |
| } |
| |
| int cras_bt_adapter_get_supported_capabilities(struct cras_bt_adapter *adapter) |
| { |
| DBusMessage *method_call; |
| DBusError dbus_error; |
| DBusPendingCall *pending_call; |
| |
| method_call = dbus_message_new_method_call(BLUEZ_SERVICE, |
| adapter->object_path, |
| BLUEZ_INTERFACE_ADAPTER, |
| "GetSupportedCapabilities"); |
| if (!method_call) |
| return -ENOMEM; |
| |
| dbus_error_init(&dbus_error); |
| if (!dbus_connection_send_with_reply(adapter->conn, method_call, |
| &pending_call, |
| DBUS_TIMEOUT_USE_DEFAULT)) { |
| dbus_message_unref(method_call); |
| syslog(LOG_ERR, |
| "Failed to send GetSupportedCapabilities message"); |
| return -EIO; |
| } |
| |
| dbus_message_unref(method_call); |
| if (!dbus_pending_call_set_notify(pending_call, |
| on_get_supported_capabilities_reply, |
| adapter, NULL)) { |
| dbus_pending_call_cancel(pending_call); |
| dbus_pending_call_unref(pending_call); |
| return -EIO; |
| } |
| return 0; |
| } |