/* 
 | 
 * BlueALSA - transport.c 
 | 
 * Copyright (c) 2016-2018 Arkadiusz Bokowy 
 | 
 * 
 | 
 * This file is a part of bluez-alsa. 
 | 
 * 
 | 
 * This project is licensed under the terms of the MIT license. 
 | 
 * 
 | 
 */ 
 | 
  
 | 
#define _GNU_SOURCE 
 | 
#include "transport.h" 
 | 
  
 | 
#include <errno.h> 
 | 
#include <fcntl.h> 
 | 
#include <stdlib.h> 
 | 
#include <string.h> 
 | 
#include <unistd.h> 
 | 
#include <sys/ioctl.h> 
 | 
#include <sys/socket.h> 
 | 
#include <sys/types.h> 
 | 
  
 | 
#include <bluetooth/bluetooth.h> 
 | 
#include <bluetooth/hci.h> 
 | 
#include <bluetooth/hci_lib.h> 
 | 
  
 | 
#include <gio/gunixfdlist.h> 
 | 
  
 | 
#include "a2dp-codecs.h" 
 | 
#include "bluealsa.h" 
 | 
#include "ctl.h" 
 | 
#include "hfp.h" 
 | 
#include "io.h" 
 | 
#include "rfcomm.h" 
 | 
#include "utils.h" 
 | 
#include "shared/log.h" 
 | 
  
 | 
  
 | 
static const char *transport_type_to_string(enum ba_transport_type type) { 
 | 
    switch (type) { 
 | 
    case TRANSPORT_TYPE_A2DP: 
 | 
        return "A2DP"; 
 | 
    case TRANSPORT_TYPE_RFCOMM: 
 | 
        return "RFCOMM"; 
 | 
    case TRANSPORT_TYPE_SCO: 
 | 
        return "SCO"; 
 | 
    } 
 | 
    return "N/A"; 
 | 
} 
 | 
  
 | 
static int io_thread_create(struct ba_transport *t) { 
 | 
  
 | 
    void *(*routine)(void *) = NULL; 
 | 
    int ret; 
 | 
  
 | 
    switch (t->type) { 
 | 
    case TRANSPORT_TYPE_A2DP: 
 | 
        if (t->profile == BLUETOOTH_PROFILE_A2DP_SOURCE) 
 | 
            switch (t->codec) { 
 | 
            case A2DP_CODEC_SBC: 
 | 
                routine = io_thread_a2dp_source_sbc; 
 | 
                break; 
 | 
#if ENABLE_MPEG 
 | 
            case A2DP_CODEC_MPEG12: 
 | 
                break; 
 | 
#endif 
 | 
#if ENABLE_AAC 
 | 
            case A2DP_CODEC_MPEG24: 
 | 
                routine = io_thread_a2dp_source_aac; 
 | 
                break; 
 | 
#endif 
 | 
#if ENABLE_APTX 
 | 
            case A2DP_CODEC_VENDOR_APTX: 
 | 
                routine = io_thread_a2dp_source_aptx; 
 | 
                break; 
 | 
#endif 
 | 
#if ENABLE_LDAC 
 | 
            case A2DP_CODEC_VENDOR_LDAC: 
 | 
                routine = io_thread_a2dp_source_ldac; 
 | 
                break; 
 | 
#endif 
 | 
            default: 
 | 
                warn("Codec not supported: %u", t->codec); 
 | 
            } 
 | 
        if (t->profile == BLUETOOTH_PROFILE_A2DP_SINK) 
 | 
            switch (t->codec) { 
 | 
            case A2DP_CODEC_SBC: 
 | 
                routine = io_thread_a2dp_sink_sbc; 
 | 
                break; 
 | 
#if ENABLE_MPEG 
 | 
            case A2DP_CODEC_MPEG12: 
 | 
                break; 
 | 
#endif 
 | 
#if ENABLE_AAC 
 | 
            case A2DP_CODEC_MPEG24: 
 | 
                routine = io_thread_a2dp_sink_aac; 
 | 
                break; 
 | 
#endif 
 | 
            default: 
 | 
                warn("Codec not supported: %u", t->codec); 
 | 
            } 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_RFCOMM: 
 | 
        routine = rfcomm_thread; 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_SCO: 
 | 
        routine = io_thread_sco; 
 | 
        break; 
 | 
    } 
 | 
  
 | 
    if (routine == NULL) 
 | 
        return -1; 
 | 
  
 | 
    if ((ret = pthread_create(&t->thread, NULL, routine, t)) != 0) { 
 | 
        error("Couldn't create IO thread: %s", strerror(ret)); 
 | 
        t->thread = config.main_thread; 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    pthread_setname_np(t->thread, "baio"); 
 | 
    debug("Created new IO thread: %s: %s", 
 | 
            transport_type_to_string(t->type), 
 | 
            bluetooth_profile_to_string(t->profile)); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
struct ba_device *device_new(int hci_dev_id, const bdaddr_t *addr, const char *name) { 
 | 
  
 | 
    struct ba_device *d; 
 | 
  
 | 
    if ((d = calloc(1, sizeof(*d))) == NULL) 
 | 
        return NULL; 
 | 
  
 | 
    d->hci_dev_id = hci_dev_id; 
 | 
    bacpy(&d->addr, addr); 
 | 
  
 | 
    strncpy(d->name, name, sizeof(d->name)); 
 | 
    d->name[sizeof(d->name) - 1] = '\0'; 
 | 
  
 | 
    d->transports = g_hash_table_new_full(g_str_hash, g_str_equal, 
 | 
            NULL, (GDestroyNotify)transport_free); 
 | 
  
 | 
    return d; 
 | 
} 
 | 
  
 | 
void device_free(struct ba_device *d) { 
 | 
  
 | 
    if (d == NULL) 
 | 
        return; 
 | 
  
 | 
    /* XXX: Modification-safe remove-all loop. 
 | 
     * 
 | 
     * By the usage of a standard g_hash_table_remove_all() function, one 
 | 
     * has to comply to the license warranty, which states that anything 
 | 
     * can happen. In our case it is true to the letter - SIGSEGV is 100% 
 | 
     * guaranteed. 
 | 
     * 
 | 
     * Our transport structure holds reference to some other transport 
 | 
     * structure within the same hash-table. Unfortunately, such a usage 
 | 
     * is not supported. Almost every GLib-2.0 function facilitates cache, 
 | 
     * which backfires at us if we modify hash-table from the inside of 
 | 
     * the destroy function. However, it is possible to "iterate" over 
 | 
     * a hash-table in a pop-like matter - reinitialize iterator after 
 | 
     * every modification. And voila - modification-safe remove loop. */ 
 | 
    for (;;) { 
 | 
  
 | 
        GHashTableIter iter; 
 | 
        struct ba_transport *t; 
 | 
  
 | 
        g_hash_table_iter_init(&iter, d->transports); 
 | 
        if (!g_hash_table_iter_next(&iter, NULL, (gpointer)&t)) 
 | 
            break; 
 | 
  
 | 
        transport_free(t); 
 | 
    } 
 | 
  
 | 
    g_hash_table_unref(d->transports); 
 | 
    free(d); 
 | 
} 
 | 
  
 | 
struct ba_device *device_get(GHashTable *devices, const char *key) { 
 | 
  
 | 
    struct ba_device *d; 
 | 
    char name[sizeof(d->name)]; 
 | 
    GVariant *property; 
 | 
    bdaddr_t addr; 
 | 
  
 | 
    if ((d = g_hash_table_lookup(devices, key)) != NULL) 
 | 
        return d; 
 | 
  
 | 
    g_dbus_device_path_to_bdaddr(key, &addr); 
 | 
    ba2str(&addr, name); 
 | 
  
 | 
    /* get local (user editable) Bluetooth device name */ 
 | 
    if ((property = g_dbus_get_property(config.dbus, "org.bluez", key, 
 | 
                    "org.bluez.Device1", "Alias")) != NULL) { 
 | 
        strncpy(name, g_variant_get_string(property, NULL), sizeof(name) - 1); 
 | 
        name[sizeof(name) - 1] = '\0'; 
 | 
        g_variant_unref(property); 
 | 
    } 
 | 
  
 | 
    d = device_new(config.hci_dev.dev_id, &addr, name); 
 | 
    g_hash_table_insert(devices, g_strdup(key), d); 
 | 
    return d; 
 | 
} 
 | 
  
 | 
struct ba_device *device_lookup(GHashTable *devices, const char *key) { 
 | 
    return g_hash_table_lookup(devices, key); 
 | 
} 
 | 
  
 | 
bool device_remove(GHashTable *devices, const char *key) { 
 | 
    return g_hash_table_remove(devices, key); 
 | 
} 
 | 
  
 | 
void device_set_battery_level(struct ba_device *d, uint8_t value) { 
 | 
    d->battery.enabled = true; 
 | 
    d->battery.level = value; 
 | 
    bluealsa_ctl_event(BA_EVENT_UPDATE_BATTERY); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Create new transport. 
 | 
 * 
 | 
 * @param device Pointer to the device structure. 
 | 
 * @param type Transport type. 
 | 
 * @param dbus_owner D-Bus service, which owns this transport. 
 | 
 * @param dbus_path D-Bus service path for this transport. 
 | 
 * @param profile Bluetooth profile. 
 | 
 * @param codec Used audio codec. 
 | 
 * @return On success, the pointer to the newly allocated transport structure 
 | 
 *   is returned. If error occurs, NULL is returned and the errno variable is 
 | 
 *   set to indicated the cause of the error. */ 
 | 
struct ba_transport *transport_new( 
 | 
        struct ba_device *device, 
 | 
        enum ba_transport_type type, 
 | 
        const char *dbus_owner, 
 | 
        const char *dbus_path, 
 | 
        enum bluetooth_profile profile, 
 | 
        uint16_t codec) { 
 | 
  
 | 
    struct ba_transport *t; 
 | 
    int err; 
 | 
  
 | 
    if ((t = calloc(1, sizeof(*t))) == NULL) 
 | 
        goto fail; 
 | 
  
 | 
    t->device = device; 
 | 
    t->type = type; 
 | 
  
 | 
    t->profile = profile; 
 | 
    t->codec = codec; 
 | 
  
 | 
    /* HSP supports CVSD only */ 
 | 
    if (profile == BLUETOOTH_PROFILE_HSP_HS || profile == BLUETOOTH_PROFILE_HSP_AG) 
 | 
        t->codec = HFP_CODEC_CVSD; 
 | 
  
 | 
    pthread_mutex_init(&t->mutex, NULL); 
 | 
  
 | 
    t->state = TRANSPORT_IDLE; 
 | 
    t->thread = config.main_thread; 
 | 
  
 | 
    t->bt_fd = -1; 
 | 
    t->sig_fd[0] = -1; 
 | 
    t->sig_fd[1] = -1; 
 | 
  
 | 
    if ((t->dbus_owner = strdup(dbus_owner)) == NULL) 
 | 
        goto fail; 
 | 
    if ((t->dbus_path = strdup(dbus_path)) == NULL) 
 | 
        goto fail; 
 | 
  
 | 
    if (pipe(t->sig_fd) == -1) 
 | 
        goto fail; 
 | 
  
 | 
    g_hash_table_insert(device->transports, t->dbus_path, t); 
 | 
    return t; 
 | 
  
 | 
fail: 
 | 
    err = errno; 
 | 
    transport_free(t); 
 | 
    errno = err; 
 | 
    return NULL; 
 | 
} 
 | 
  
 | 
struct ba_transport *transport_new_a2dp( 
 | 
        struct ba_device *device, 
 | 
        const char *dbus_owner, 
 | 
        const char *dbus_path, 
 | 
        enum bluetooth_profile profile, 
 | 
        uint16_t codec, 
 | 
        const uint8_t *config, 
 | 
        size_t config_size) { 
 | 
  
 | 
    struct ba_transport *t; 
 | 
  
 | 
    if ((t = transport_new(device, TRANSPORT_TYPE_A2DP, 
 | 
                    dbus_owner, dbus_path, profile, codec)) == NULL) 
 | 
        return NULL; 
 | 
  
 | 
    t->a2dp.ch1_volume = 127; 
 | 
    t->a2dp.ch2_volume = 127; 
 | 
  
 | 
    if (config_size > 0) { 
 | 
        t->a2dp.cconfig = malloc(config_size); 
 | 
        t->a2dp.cconfig_size = config_size; 
 | 
        memcpy(t->a2dp.cconfig, config, config_size); 
 | 
    } 
 | 
  
 | 
    t->a2dp.pcm.fd = -1; 
 | 
    t->a2dp.pcm.client = -1; 
 | 
    pthread_cond_init(&t->a2dp.pcm.drained, NULL); 
 | 
    pthread_mutex_init(&t->a2dp.pcm.drained_mn, NULL); 
 | 
  
 | 
    bluealsa_ctl_event(BA_EVENT_TRANSPORT_ADDED); 
 | 
    return t; 
 | 
} 
 | 
  
 | 
struct ba_transport *transport_new_rfcomm( 
 | 
        struct ba_device *device, 
 | 
        const char *dbus_owner, 
 | 
        const char *dbus_path, 
 | 
        enum bluetooth_profile profile) { 
 | 
  
 | 
    gchar *dbus_path_sco = NULL; 
 | 
    struct ba_transport *t, *t_sco; 
 | 
  
 | 
    if ((t = transport_new(device, TRANSPORT_TYPE_RFCOMM, 
 | 
                    dbus_owner, dbus_path, profile, -1)) == NULL) 
 | 
        goto fail; 
 | 
  
 | 
    dbus_path_sco = g_strdup_printf("%s/sco", dbus_path); 
 | 
    if ((t_sco = transport_new(device, TRANSPORT_TYPE_SCO, 
 | 
                    dbus_owner, dbus_path_sco, profile, HFP_CODEC_UNDEFINED)) == NULL) 
 | 
        goto fail; 
 | 
  
 | 
    t->rfcomm.sco = t_sco; 
 | 
    t_sco->sco.rfcomm = t; 
 | 
  
 | 
    t_sco->sco.spk_gain = 15; 
 | 
    t_sco->sco.mic_gain = 15; 
 | 
  
 | 
    t_sco->sco.spk_pcm.fd = -1; 
 | 
    t_sco->sco.spk_pcm.client = -1; 
 | 
    pthread_cond_init(&t_sco->sco.spk_pcm.drained, NULL); 
 | 
    pthread_mutex_init(&t_sco->sco.spk_pcm.drained_mn, NULL); 
 | 
  
 | 
    t_sco->sco.mic_pcm.fd = -1; 
 | 
    t_sco->sco.mic_pcm.client = -1; 
 | 
    pthread_cond_init(&t_sco->sco.mic_pcm.drained, NULL); 
 | 
    pthread_mutex_init(&t_sco->sco.mic_pcm.drained_mn, NULL); 
 | 
  
 | 
    bluealsa_ctl_event(BA_EVENT_TRANSPORT_ADDED); 
 | 
    return t; 
 | 
  
 | 
fail: 
 | 
    if (dbus_path_sco != NULL) 
 | 
        g_free(dbus_path_sco); 
 | 
    transport_free(t); 
 | 
    return NULL; 
 | 
} 
 | 
  
 | 
void transport_free(struct ba_transport *t) { 
 | 
  
 | 
    if (t == NULL || t->state == TRANSPORT_LIMBO) 
 | 
        return; 
 | 
  
 | 
    t->state = TRANSPORT_LIMBO; 
 | 
    debug("Freeing transport: %s: %s (%s)", 
 | 
            transport_type_to_string(t->type), 
 | 
            bluetooth_profile_to_string(t->profile), 
 | 
            bluetooth_a2dp_codec_to_string(t->codec)); 
 | 
  
 | 
    /* If the transport is active, prior to releasing resources, we have to 
 | 
     * terminate the IO thread (or at least make sure it is not running any 
 | 
     * more). Not doing so might result in an undefined behavior or even a 
 | 
     * race condition (closed and reused file descriptor). */ 
 | 
    transport_pthread_cancel(t->thread); 
 | 
  
 | 
    /* if possible, try to release resources gracefully */ 
 | 
    if (t->release != NULL) 
 | 
        t->release(t); 
 | 
  
 | 
    if (t->bt_fd != -1) 
 | 
        close(t->bt_fd); 
 | 
    if (t->sig_fd[0] != -1) 
 | 
        close(t->sig_fd[0]); 
 | 
    if (t->sig_fd[1] != -1) 
 | 
        close(t->sig_fd[1]); 
 | 
  
 | 
    pthread_mutex_destroy(&t->mutex); 
 | 
  
 | 
    /* free type-specific resources */ 
 | 
    switch (t->type) { 
 | 
    case TRANSPORT_TYPE_A2DP: 
 | 
        transport_release_pcm(&t->a2dp.pcm); 
 | 
        pthread_cond_destroy(&t->a2dp.pcm.drained); 
 | 
        pthread_mutex_destroy(&t->a2dp.pcm.drained_mn); 
 | 
        free(t->a2dp.cconfig); 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_RFCOMM: 
 | 
        memset(&t->device->battery, 0, sizeof(t->device->battery)); 
 | 
        memset(&t->device->xapl, 0, sizeof(t->device->xapl)); 
 | 
        transport_free(t->rfcomm.sco); 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_SCO: 
 | 
        transport_release_pcm(&t->sco.spk_pcm); 
 | 
        pthread_cond_destroy(&t->sco.spk_pcm.drained); 
 | 
        pthread_mutex_destroy(&t->sco.spk_pcm.drained_mn); 
 | 
        transport_release_pcm(&t->sco.mic_pcm); 
 | 
        pthread_cond_destroy(&t->sco.mic_pcm.drained); 
 | 
        pthread_mutex_destroy(&t->sco.mic_pcm.drained_mn); 
 | 
        t->sco.rfcomm->rfcomm.sco = NULL; 
 | 
        break; 
 | 
    } 
 | 
  
 | 
    /* If the free action was called on the behalf of the destroy notification, 
 | 
     * removing a value from the hash-table shouldn't hurt - it would have been 
 | 
     * removed anyway. */ 
 | 
    g_hash_table_steal(t->device->transports, t->dbus_path); 
 | 
  
 | 
    bluealsa_ctl_event(BA_EVENT_TRANSPORT_REMOVED); 
 | 
  
 | 
    free(t->dbus_owner); 
 | 
    free(t->dbus_path); 
 | 
    free(t); 
 | 
} 
 | 
  
 | 
struct ba_transport *transport_lookup(GHashTable *devices, const char *dbus_path) { 
 | 
  
 | 
    GHashTableIter iter; 
 | 
    struct ba_device *d; 
 | 
    struct ba_transport *t; 
 | 
  
 | 
    g_hash_table_iter_init(&iter, devices); 
 | 
    while (g_hash_table_iter_next(&iter, NULL, (gpointer)&d)) { 
 | 
        if ((t = g_hash_table_lookup(d->transports, dbus_path)) != NULL) 
 | 
            return t; 
 | 
    } 
 | 
  
 | 
    return NULL; 
 | 
} 
 | 
  
 | 
struct ba_transport *transport_lookup_pcm_client(GHashTable *devices, int client) { 
 | 
  
 | 
    GHashTableIter iter_d, iter_t; 
 | 
    struct ba_device *d; 
 | 
    struct ba_transport *t; 
 | 
  
 | 
    g_hash_table_iter_init(&iter_d, devices); 
 | 
    while (g_hash_table_iter_next(&iter_d, NULL, (gpointer)&d)) { 
 | 
        g_hash_table_iter_init(&iter_t, d->transports); 
 | 
        while (g_hash_table_iter_next(&iter_t, NULL, (gpointer)&t)) { 
 | 
            switch (t->type) { 
 | 
            case TRANSPORT_TYPE_A2DP: 
 | 
                if (t->a2dp.pcm.client == client) 
 | 
                    return t; 
 | 
                break; 
 | 
            case TRANSPORT_TYPE_RFCOMM: 
 | 
                break; 
 | 
            case TRANSPORT_TYPE_SCO: 
 | 
                if (t->sco.spk_pcm.client == client) 
 | 
                    return t; 
 | 
                if (t->sco.mic_pcm.client == client) 
 | 
                    return t; 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return NULL; 
 | 
} 
 | 
  
 | 
bool transport_remove(GHashTable *devices, const char *dbus_path) { 
 | 
  
 | 
    GHashTableIter iter; 
 | 
    struct ba_device *d; 
 | 
    struct ba_transport *t; 
 | 
  
 | 
    g_hash_table_iter_init(&iter, devices); 
 | 
    while (g_hash_table_iter_next(&iter, NULL, (gpointer)&d)) { 
 | 
        /* Disassociate D-Bus owner before further actions. This will ensure, 
 | 
         * that we will not generate errors by using non-existent interface. */ 
 | 
        if ((t = g_hash_table_lookup(d->transports, dbus_path)) != NULL) { 
 | 
            free(t->dbus_owner); 
 | 
            t->dbus_owner = NULL; 
 | 
        } 
 | 
        if (g_hash_table_remove(d->transports, dbus_path)) { 
 | 
            if (g_hash_table_size(d->transports) == 0) 
 | 
                g_hash_table_iter_remove(&iter); 
 | 
            return true; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return false; 
 | 
} 
 | 
  
 | 
int transport_send_signal(struct ba_transport *t, enum ba_transport_signal sig) { 
 | 
    return write(t->sig_fd[1], &sig, sizeof(sig)); 
 | 
} 
 | 
  
 | 
int transport_send_rfcomm(struct ba_transport *t, const char command[32]) { 
 | 
  
 | 
    char msg[sizeof(enum ba_transport_signal) + 32]; 
 | 
  
 | 
    ((enum ba_transport_signal *)msg)[0] = TRANSPORT_SEND_RFCOMM; 
 | 
    memcpy(&msg[sizeof(enum ba_transport_signal)], command, 32); 
 | 
  
 | 
    return write(t->sig_fd[1], msg, sizeof(msg)); 
 | 
} 
 | 
  
 | 
unsigned int transport_get_channels(const struct ba_transport *t) { 
 | 
  
 | 
    switch (t->type) { 
 | 
    case TRANSPORT_TYPE_A2DP: 
 | 
        switch (t->codec) { 
 | 
        case A2DP_CODEC_SBC: 
 | 
            switch (((a2dp_sbc_t *)t->a2dp.cconfig)->channel_mode) { 
 | 
            case SBC_CHANNEL_MODE_MONO: 
 | 
                return 1; 
 | 
            case SBC_CHANNEL_MODE_STEREO: 
 | 
            case SBC_CHANNEL_MODE_JOINT_STEREO: 
 | 
            case SBC_CHANNEL_MODE_DUAL_CHANNEL: 
 | 
                return 2; 
 | 
            } 
 | 
            break; 
 | 
#if ENABLE_MPEG 
 | 
        case A2DP_CODEC_MPEG12: 
 | 
            switch (((a2dp_mpeg_t *)t->a2dp.cconfig)->channel_mode) { 
 | 
            case MPEG_CHANNEL_MODE_MONO: 
 | 
                return 1; 
 | 
            case MPEG_CHANNEL_MODE_STEREO: 
 | 
            case MPEG_CHANNEL_MODE_JOINT_STEREO: 
 | 
            case MPEG_CHANNEL_MODE_DUAL_CHANNEL: 
 | 
                return 2; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
#if ENABLE_AAC 
 | 
        case A2DP_CODEC_MPEG24: 
 | 
            switch (((a2dp_aac_t *)t->a2dp.cconfig)->channels) { 
 | 
            case AAC_CHANNELS_1: 
 | 
                return 1; 
 | 
            case AAC_CHANNELS_2: 
 | 
                return 2; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
#if ENABLE_APTX 
 | 
        case A2DP_CODEC_VENDOR_APTX: 
 | 
            switch (((a2dp_aptx_t *)t->a2dp.cconfig)->channel_mode) { 
 | 
            case APTX_CHANNEL_MODE_MONO: 
 | 
                return 1; 
 | 
            case APTX_CHANNEL_MODE_STEREO: 
 | 
                return 2; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
#if ENABLE_LDAC 
 | 
        case A2DP_CODEC_VENDOR_LDAC: 
 | 
            switch (((a2dp_ldac_t *)t->a2dp.cconfig)->channel_mode) { 
 | 
            case LDAC_CHANNEL_MODE_MONO: 
 | 
                return 1; 
 | 
            case LDAC_CHANNEL_MODE_STEREO: 
 | 
            case LDAC_CHANNEL_MODE_DUAL_CHANNEL: 
 | 
                return 2; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
        } 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_RFCOMM: 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_SCO: 
 | 
        return 1; 
 | 
    } 
 | 
  
 | 
    /* the number of channels is unspecified */ 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
unsigned int transport_get_sampling(const struct ba_transport *t) { 
 | 
  
 | 
    switch (t->type) { 
 | 
    case TRANSPORT_TYPE_A2DP: 
 | 
        switch (t->codec) { 
 | 
        case A2DP_CODEC_SBC: 
 | 
            switch (((a2dp_sbc_t *)t->a2dp.cconfig)->frequency) { 
 | 
            case SBC_SAMPLING_FREQ_16000: 
 | 
                return 16000; 
 | 
            case SBC_SAMPLING_FREQ_32000: 
 | 
                return 32000; 
 | 
            case SBC_SAMPLING_FREQ_44100: 
 | 
                return 44100; 
 | 
            case SBC_SAMPLING_FREQ_48000: 
 | 
                return 48000; 
 | 
            } 
 | 
            break; 
 | 
#if ENABLE_MPEG 
 | 
        case A2DP_CODEC_MPEG12: 
 | 
            switch (((a2dp_mpeg_t *)t->a2dp.cconfig)->frequency) { 
 | 
            case MPEG_SAMPLING_FREQ_16000: 
 | 
                return 16000; 
 | 
            case MPEG_SAMPLING_FREQ_22050: 
 | 
                return 22050; 
 | 
            case MPEG_SAMPLING_FREQ_24000: 
 | 
                return 24000; 
 | 
            case MPEG_SAMPLING_FREQ_32000: 
 | 
                return 32000; 
 | 
            case MPEG_SAMPLING_FREQ_44100: 
 | 
                return 44100; 
 | 
            case MPEG_SAMPLING_FREQ_48000: 
 | 
                return 48000; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
#if ENABLE_AAC 
 | 
        case A2DP_CODEC_MPEG24: 
 | 
            switch (AAC_GET_FREQUENCY(*(a2dp_aac_t *)t->a2dp.cconfig)) { 
 | 
            case AAC_SAMPLING_FREQ_8000: 
 | 
                return 8000; 
 | 
            case AAC_SAMPLING_FREQ_11025: 
 | 
                return 11025; 
 | 
            case AAC_SAMPLING_FREQ_12000: 
 | 
                return 12000; 
 | 
            case AAC_SAMPLING_FREQ_16000: 
 | 
                return 16000; 
 | 
            case AAC_SAMPLING_FREQ_22050: 
 | 
                return 22050; 
 | 
            case AAC_SAMPLING_FREQ_24000: 
 | 
                return 24000; 
 | 
            case AAC_SAMPLING_FREQ_32000: 
 | 
                return 32000; 
 | 
            case AAC_SAMPLING_FREQ_44100: 
 | 
                return 44100; 
 | 
            case AAC_SAMPLING_FREQ_48000: 
 | 
                return 48000; 
 | 
            case AAC_SAMPLING_FREQ_64000: 
 | 
                return 64000; 
 | 
            case AAC_SAMPLING_FREQ_88200: 
 | 
                return 88200; 
 | 
            case AAC_SAMPLING_FREQ_96000: 
 | 
                return 96000; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
#if ENABLE_APTX 
 | 
        case A2DP_CODEC_VENDOR_APTX: 
 | 
            switch (((a2dp_aptx_t *)t->a2dp.cconfig)->frequency) { 
 | 
            case APTX_SAMPLING_FREQ_16000: 
 | 
                return 16000; 
 | 
            case APTX_SAMPLING_FREQ_32000: 
 | 
                return 32000; 
 | 
            case APTX_SAMPLING_FREQ_44100: 
 | 
                return 44100; 
 | 
            case APTX_SAMPLING_FREQ_48000: 
 | 
                return 48000; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
#if ENABLE_LDAC 
 | 
        case A2DP_CODEC_VENDOR_LDAC: 
 | 
            switch (((a2dp_ldac_t *)t->a2dp.cconfig)->frequency) { 
 | 
            case LDAC_SAMPLING_FREQ_44100: 
 | 
                return 44100; 
 | 
            case LDAC_SAMPLING_FREQ_48000: 
 | 
                return 48000; 
 | 
            case LDAC_SAMPLING_FREQ_88200: 
 | 
                return 88200; 
 | 
            case LDAC_SAMPLING_FREQ_96000: 
 | 
                return 96000; 
 | 
            case LDAC_SAMPLING_FREQ_176400: 
 | 
                return 176400; 
 | 
            case LDAC_SAMPLING_FREQ_192000: 
 | 
                return 192000; 
 | 
            } 
 | 
            break; 
 | 
#endif 
 | 
        } 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_RFCOMM: 
 | 
        break; 
 | 
    case TRANSPORT_TYPE_SCO: 
 | 
        switch (t->codec) { 
 | 
        case HFP_CODEC_CVSD: 
 | 
            return 8000; 
 | 
        case HFP_CODEC_MSBC: 
 | 
            return 16000; 
 | 
        default: 
 | 
            debug("Unsupported SCO codec: %#x", t->codec); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* the sampling frequency is unspecified */ 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int transport_set_volume(struct ba_transport *t, uint8_t ch1_muted, uint8_t ch2_muted, 
 | 
        uint8_t ch1_volume, uint8_t ch2_volume) { 
 | 
  
 | 
    debug("Setting volume for %s profile %d: %d<>%d [%c%c]", batostr_(&t->device->addr), 
 | 
            t->profile, ch1_volume, ch2_volume, ch1_muted ? 'M' : 'O', ch2_muted ? 'M' : 'O'); 
 | 
  
 | 
    switch (t->type) { 
 | 
    case TRANSPORT_TYPE_A2DP: 
 | 
  
 | 
        t->a2dp.ch1_muted = ch1_muted; 
 | 
        t->a2dp.ch2_muted = ch2_muted; 
 | 
        t->a2dp.ch1_volume = ch1_volume; 
 | 
        t->a2dp.ch2_volume = ch2_volume; 
 | 
  
 | 
        if (config.a2dp.volume) { 
 | 
            uint16_t volume = (ch1_muted | ch2_muted) ? 0 : MIN(ch1_volume, ch2_volume); 
 | 
            g_dbus_set_property(config.dbus, t->dbus_owner, t->dbus_path, 
 | 
                    "org.bluez.MediaTransport1", "Volume", g_variant_new_uint16(volume)); 
 | 
        } 
 | 
  
 | 
        break; 
 | 
  
 | 
    case TRANSPORT_TYPE_RFCOMM: 
 | 
        break; 
 | 
  
 | 
    case TRANSPORT_TYPE_SCO: 
 | 
  
 | 
        t->sco.spk_muted = ch1_muted; 
 | 
        t->sco.mic_muted = ch2_muted; 
 | 
        t->sco.spk_gain = ch1_volume; 
 | 
        t->sco.mic_gain = ch2_volume; 
 | 
  
 | 
        /* notify associated RFCOMM transport */ 
 | 
        transport_send_signal(t->sco.rfcomm, TRANSPORT_SET_VOLUME); 
 | 
  
 | 
        break; 
 | 
  
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int transport_set_state(struct ba_transport *t, enum ba_transport_state state) { 
 | 
    debug("State transition: %d -> %d", t->state, state); 
 | 
  
 | 
    if (t->state == state) 
 | 
        return 0; 
 | 
  
 | 
    /* For the A2DP sink profile, the IO thread can not be created until the 
 | 
     * BT transport is acquired, otherwise thread initialized will fail. */ 
 | 
    if (t->profile == BLUETOOTH_PROFILE_A2DP_SINK && 
 | 
            t->state == TRANSPORT_IDLE && state != TRANSPORT_PENDING) 
 | 
        return 0; 
 | 
  
 | 
    int ret = 0; 
 | 
  
 | 
    t->state = state; 
 | 
  
 | 
    switch (state) { 
 | 
    case TRANSPORT_IDLE: 
 | 
        transport_pthread_cancel(t->thread); 
 | 
        break; 
 | 
    case TRANSPORT_PENDING: 
 | 
        /* When transport is marked as pending, try to acquire transport, but only 
 | 
         * if we are handing A2DP sink profile. For source profile, transport has 
 | 
         * to be acquired by our controller (during the PCM open request). */ 
 | 
        if (t->profile == BLUETOOTH_PROFILE_A2DP_SINK) 
 | 
            ret = transport_acquire_bt_a2dp(t); 
 | 
        break; 
 | 
    case TRANSPORT_ACTIVE: 
 | 
    case TRANSPORT_PAUSED: 
 | 
        if (pthread_equal(t->thread, config.main_thread)) 
 | 
            ret = io_thread_create(t); 
 | 
        break; 
 | 
    case TRANSPORT_LIMBO: 
 | 
        break; 
 | 
    } 
 | 
  
 | 
    /* something went wrong, so go back to idle */ 
 | 
    if (ret == -1) 
 | 
        return transport_set_state(t, TRANSPORT_IDLE); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
int transport_set_state_from_string(struct ba_transport *t, const char *state) { 
 | 
  
 | 
    if (strcmp(state, "idle") == 0) 
 | 
        transport_set_state(t, TRANSPORT_IDLE); 
 | 
    else if (strcmp(state, "pending") == 0) 
 | 
        transport_set_state(t, TRANSPORT_PENDING); 
 | 
    else if (strcmp(state, "active") == 0) 
 | 
        transport_set_state(t, TRANSPORT_ACTIVE); 
 | 
    else { 
 | 
        warn("Invalid state: %s", state); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int transport_drain_pcm(struct ba_transport *t) { 
 | 
  
 | 
    struct ba_pcm *pcm = NULL; 
 | 
  
 | 
    switch (t->profile) { 
 | 
    case BLUETOOTH_PROFILE_NULL: 
 | 
    case BLUETOOTH_PROFILE_A2DP_SINK: 
 | 
        break; 
 | 
    case BLUETOOTH_PROFILE_A2DP_SOURCE: 
 | 
        pcm = &t->a2dp.pcm; 
 | 
        break; 
 | 
    case BLUETOOTH_PROFILE_HSP_AG: 
 | 
    case BLUETOOTH_PROFILE_HFP_AG: 
 | 
        pcm = &t->sco.spk_pcm; 
 | 
        break; 
 | 
    case BLUETOOTH_PROFILE_HSP_HS: 
 | 
    case BLUETOOTH_PROFILE_HFP_HF: 
 | 
        break; 
 | 
    } 
 | 
  
 | 
    if (pcm == NULL || t->state != TRANSPORT_ACTIVE) 
 | 
        return 0; 
 | 
  
 | 
    pthread_mutex_lock(&pcm->drained_mn); 
 | 
  
 | 
    transport_send_signal(t, TRANSPORT_PCM_SYNC); 
 | 
    pthread_cond_wait(&pcm->drained, &pcm->drained_mn); 
 | 
  
 | 
    pthread_mutex_unlock(&pcm->drained_mn); 
 | 
  
 | 
    /* TODO: Asynchronous transport release. 
 | 
     * 
 | 
     * Unfortunately, BlueZ does not provide API for internal buffer drain. 
 | 
     * Also, there is no specification for Bluetooth playback drain. In order 
 | 
     * to make sure, that all samples are played out, we have to wait some 
 | 
     * arbitrary time before releasing transport. In order to make it right, 
 | 
     * there is a requirement for an asynchronous release mechanism, which 
 | 
     * is not implemented - it requires a little bit of refactoring. */ 
 | 
    usleep(200000); 
 | 
  
 | 
    debug("PCM drained"); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int transport_acquire_bt_a2dp(struct ba_transport *t) { 
 | 
  
 | 
    GDBusMessage *msg, *rep; 
 | 
    GUnixFDList *fd_list; 
 | 
    GError *err = NULL; 
 | 
  
 | 
    /* Check whether transport is already acquired - keep-alive mode. */ 
 | 
    if (t->bt_fd != -1) { 
 | 
        debug("Reusing transport: %d", t->bt_fd); 
 | 
        goto final; 
 | 
    } 
 | 
  
 | 
    msg = g_dbus_message_new_method_call(t->dbus_owner, t->dbus_path, "org.bluez.MediaTransport1", 
 | 
            t->state == TRANSPORT_PENDING ? "TryAcquire" : "Acquire"); 
 | 
  
 | 
    if ((rep = g_dbus_connection_send_message_with_reply_sync(config.dbus, msg, 
 | 
                    G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL) 
 | 
        goto fail; 
 | 
  
 | 
    if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) { 
 | 
        g_dbus_message_to_gerror(rep, &err); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    g_variant_get(g_dbus_message_get_body(rep), "(hqq)", (int32_t *)&t->bt_fd, 
 | 
            (uint16_t *)&t->mtu_read, (uint16_t *)&t->mtu_write); 
 | 
  
 | 
    fd_list = g_dbus_message_get_unix_fd_list(rep); 
 | 
    t->bt_fd = g_unix_fd_list_get(fd_list, 0, &err); 
 | 
    t->release = transport_release_bt_a2dp; 
 | 
  
 | 
    /* Minimize audio delay and increase responsiveness (seeking, stopping) by 
 | 
     * decreasing the BT socket output buffer. We will use a tripled write MTU 
 | 
     * value, in order to prevent tearing due to temporal heavy load. */ 
 | 
    size_t size = t->mtu_write * 3; 
 | 
    if (setsockopt(t->bt_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1) 
 | 
        warn("Couldn't set socket output buffer size: %s", strerror(errno)); 
 | 
  
 | 
    if (ioctl(t->bt_fd, TIOCOUTQ, &t->a2dp.bt_fd_coutq_init) == -1) 
 | 
        warn("Couldn't get socket queued bytes: %s", strerror(errno)); 
 | 
  
 | 
    debug("New transport: %d (MTU: R:%zu W:%zu)", t->bt_fd, t->mtu_read, t->mtu_write); 
 | 
  
 | 
fail: 
 | 
    g_object_unref(msg); 
 | 
    if (rep != NULL) 
 | 
        g_object_unref(rep); 
 | 
    if (err != NULL) { 
 | 
        error("Couldn't acquire transport: %s", err->message); 
 | 
        g_error_free(err); 
 | 
    } 
 | 
  
 | 
final: 
 | 
    return t->bt_fd; 
 | 
} 
 | 
  
 | 
int transport_release_bt_a2dp(struct ba_transport *t) { 
 | 
  
 | 
    GDBusMessage *msg = NULL, *rep = NULL; 
 | 
    GError *err = NULL; 
 | 
    int ret = -1; 
 | 
  
 | 
    /* If the transport has not been acquired, or it has been released already, 
 | 
     * there is no need to release it again. In fact, trying to release already 
 | 
     * closed transport will result in returning error message. */ 
 | 
    if (t->bt_fd == -1) 
 | 
        return 0; 
 | 
  
 | 
    debug("Releasing transport: %s (%s)", 
 | 
            bluetooth_profile_to_string(t->profile), 
 | 
            bluetooth_a2dp_codec_to_string(t->codec)); 
 | 
  
 | 
    /* If the state is idle, it means that either transport was not acquired, or 
 | 
     * was released by the BlueZ. In both cases there is no point in a explicit 
 | 
     * release request. It might even return error (e.g. not authorized). */ 
 | 
    if (t->state != TRANSPORT_IDLE && t->dbus_owner != NULL) { 
 | 
  
 | 
        msg = g_dbus_message_new_method_call(t->dbus_owner, t->dbus_path, 
 | 
                "org.bluez.MediaTransport1", "Release"); 
 | 
  
 | 
        if ((rep = g_dbus_connection_send_message_with_reply_sync(config.dbus, msg, 
 | 
                        G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL) 
 | 
            goto fail; 
 | 
  
 | 
        if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) { 
 | 
            g_dbus_message_to_gerror(rep, &err); 
 | 
            if (err->code == G_DBUS_ERROR_NO_REPLY) { 
 | 
                /* If BlueZ is already terminated (or is terminating), we won't receive 
 | 
                 * any response. Do not treat such a case as an error - omit logging. */ 
 | 
                g_error_free(err); 
 | 
                err = NULL; 
 | 
            } 
 | 
            else 
 | 
                goto fail; 
 | 
        } 
 | 
  
 | 
    } 
 | 
  
 | 
    debug("Closing BT: %d", t->bt_fd); 
 | 
  
 | 
    ret = 0; 
 | 
    t->release = NULL; 
 | 
    close(t->bt_fd); 
 | 
    t->bt_fd = -1; 
 | 
  
 | 
fail: 
 | 
    if (msg != NULL) 
 | 
        g_object_unref(msg); 
 | 
    if (rep != NULL) 
 | 
        g_object_unref(rep); 
 | 
    if (err != NULL) { 
 | 
        error("Couldn't release transport: %s", err->message); 
 | 
        g_error_free(err); 
 | 
    } 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
int transport_release_bt_rfcomm(struct ba_transport *t) { 
 | 
  
 | 
    if (t->bt_fd == -1) 
 | 
        return 0; 
 | 
  
 | 
    debug("Closing RFCOMM: %d", t->bt_fd); 
 | 
  
 | 
    t->release = NULL; 
 | 
    shutdown(t->bt_fd, SHUT_RDWR); 
 | 
    close(t->bt_fd); 
 | 
    t->bt_fd = -1; 
 | 
  
 | 
    /* BlueZ does not trigger profile disconnection signal when the Bluetooth 
 | 
     * link has been lost (e.g. device power down). However, it is required to 
 | 
     * remove transport from the transport pool before reconnecting. */ 
 | 
    transport_free(t); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int transport_acquire_bt_sco(struct ba_transport *t) { 
 | 
  
 | 
    struct hci_dev_info di; 
 | 
  
 | 
    if (t->bt_fd != -1) 
 | 
        return t->bt_fd; 
 | 
  
 | 
    if (hci_devinfo(t->device->hci_dev_id, &di) == -1) { 
 | 
        error("Couldn't get HCI device info: %s", strerror(errno)); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    if ((t->bt_fd = hci_open_sco(&di, &t->device->addr, t->codec != HFP_CODEC_CVSD)) == -1) { 
 | 
        error("Couldn't open SCO link: %s", strerror(errno)); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    t->mtu_read = di.sco_mtu; 
 | 
    t->mtu_write = di.sco_mtu; 
 | 
    t->release = transport_release_bt_sco; 
 | 
  
 | 
    /* XXX: It seems, that the MTU values returned by the HCI interface 
 | 
     *      are incorrect (or our interpretation of them is incorrect). */ 
 | 
    t->mtu_read = 48; 
 | 
    t->mtu_write = 48; 
 | 
  
 | 
    debug("New SCO link: %d (MTU: R:%zu W:%zu)", t->bt_fd, t->mtu_read, t->mtu_write); 
 | 
  
 | 
    return t->bt_fd; 
 | 
} 
 | 
  
 | 
int transport_acquire_bt_sco2(struct ba_transport *t, int asock) 
 | 
{ 
 | 
  
 | 
    struct hci_dev_info di; 
 | 
  
 | 
    if (t->bt_fd != -1) 
 | 
        return t->bt_fd; 
 | 
  
 | 
    t->bt_fd = asock; 
 | 
  
 | 
    if (hci_devinfo(t->device->hci_dev_id, &di) == -1) { 
 | 
        error("Couldn't get HCI device info: %s", strerror(errno)); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    t->mtu_read = di.sco_mtu; 
 | 
    t->mtu_write = di.sco_mtu; 
 | 
    t->release = transport_release_bt_sco; 
 | 
  
 | 
    /* XXX: It seems, that the MTU values returned by the HCI interface 
 | 
     *      are incorrect (or our interpretation of them is incorrect). */ 
 | 
    t->mtu_read = 48; 
 | 
    t->mtu_write = 48; 
 | 
  
 | 
    debug("New SCO link: %d (MTU: R:%zu W:%zu)", t->bt_fd, t->mtu_read, t->mtu_write); 
 | 
  
 | 
    return t->bt_fd; 
 | 
} 
 | 
  
 | 
int transport_release_bt_sco(struct ba_transport *t) { 
 | 
  
 | 
    if (t->bt_fd == -1) 
 | 
        return 0; 
 | 
  
 | 
    debug("Closing SCO: %d", t->bt_fd); 
 | 
  
 | 
    t->release = NULL; 
 | 
    shutdown(t->bt_fd, SHUT_RDWR); 
 | 
    close(t->bt_fd); 
 | 
    t->bt_fd = -1; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int transport_release_pcm(struct ba_pcm *pcm) { 
 | 
  
 | 
    int oldstate; 
 | 
  
 | 
    /* Transport IO workers are managed using thread cancellation mechanism, 
 | 
     * so we have to take into account a possibility of cancellation during the 
 | 
     * execution. In this release function it is important to perform actions 
 | 
     * atomically. Since close call is a cancellation point, it is required to 
 | 
     * temporally disable cancellation. For a better understanding of what is 
 | 
     * going on, see the io_thread_read_pcm() function. */ 
 | 
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); 
 | 
  
 | 
    if (pcm->fd != -1) { 
 | 
        debug("Closing PCM: %d", pcm->fd); 
 | 
        close(pcm->fd); 
 | 
        pcm->fd = -1; 
 | 
    } 
 | 
  
 | 
    pthread_setcancelstate(oldstate, NULL); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Synchronous transport thread cancellation. */ 
 | 
void transport_pthread_cancel(pthread_t thread) { 
 | 
  
 | 
    if (pthread_equal(thread, pthread_self())) 
 | 
        return; 
 | 
    if (pthread_equal(thread, config.main_thread)) 
 | 
        return; 
 | 
  
 | 
    int err; 
 | 
    if ((err = pthread_cancel(thread)) != 0) 
 | 
        warn("Couldn't cancel transport thread: %s", strerror(err)); 
 | 
    if ((err = pthread_join(thread, NULL)) != 0) 
 | 
        warn("Couldn't join transport thread: %s", strerror(err)); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Wrapper for release callback, which can be used by the pthread cleanup. 
 | 
 * 
 | 
 * This function CAN be used with transport_pthread_cleanup_lock() in order 
 | 
 * to guard transport critical section during cleanup process. */ 
 | 
void transport_pthread_cleanup(struct ba_transport *t) { 
 | 
  
 | 
    /* During the normal operation mode, the release callback should not 
 | 
     * be NULL. Hence, we will relay on this callback - file descriptors 
 | 
     * are closed in it. */ 
 | 
    if (t->release != NULL) 
 | 
        t->release(t); 
 | 
  
 | 
    /* Make sure, that after termination, this thread handler will not 
 | 
     * be used anymore. */ 
 | 
    t->thread = config.main_thread; 
 | 
  
 | 
    transport_pthread_cleanup_unlock(t); 
 | 
  
 | 
    /* XXX: If the order of the cleanup push is right, this function will 
 | 
     *      indicate the end of the IO/RFCOMM thread. */ 
 | 
    debug("Exiting IO thread"); 
 | 
} 
 | 
  
 | 
int transport_pthread_cleanup_lock(struct ba_transport *t) { 
 | 
    int ret = pthread_mutex_lock(&t->mutex); 
 | 
    t->cleanup_lock = true; 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
int transport_pthread_cleanup_unlock(struct ba_transport *t) { 
 | 
    if (!t->cleanup_lock) 
 | 
        return 0; 
 | 
    t->cleanup_lock = false; 
 | 
    return pthread_mutex_unlock(&t->mutex); 
 | 
} 
 |