/*
|
* BlueALSA - rfcomm.c
|
* Copyright (c) 2016-2018 Arkadiusz Bokowy
|
* 2017 Juha Kuikka
|
*
|
* This file is a part of bluez-alsa.
|
*
|
* This project is licensed under the terms of the MIT license.
|
*
|
*/
|
|
#include "rfcomm.h"
|
|
#include <errno.h>
|
#include <poll.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <unistd.h>
|
#include <sys/socket.h>
|
#include <sys/un.h>
|
|
#include "bluealsa.h"
|
#include "ctl.h"
|
#include "utils.h"
|
#include "shared/defs.h"
|
#include "shared/log.h"
|
|
|
/**
|
* Structure used for buffered reading from the RFCOMM. */
|
struct at_reader {
|
struct bt_at at;
|
char buffer[256];
|
/* pointer to the next message within the buffer */
|
char *next;
|
};
|
|
/* RockChip add this code for cooperating with rk deviceio */
|
static int rockchip_send_msg_to_deviceiolib(char * buff);
|
|
/**
|
* Read AT message.
|
*
|
* Upon error it is required to set the next pointer of the reader structure
|
* to NULL. Otherwise, this function might fail indefinitely.
|
*
|
* @param fd RFCOMM socket file descriptor.
|
* @param reader Pointer to initialized reader structure.
|
* @return On success this function returns 0. Otherwise, -1 is returned and
|
* errno is set to indicate the error. */
|
static int rfcomm_read_at(int fd, struct at_reader *reader) {
|
|
char *buffer = reader->buffer;
|
char *msg = reader->next;
|
char *tmp;
|
|
/* In case of reading more than one message from the RFCOMM, we have to
|
* parse all of them before we can read from the socket once more. */
|
if (msg == NULL) {
|
|
ssize_t len;
|
|
retry:
|
if ((len = read(fd, buffer, sizeof(reader->buffer))) == -1) {
|
if (errno == EINTR)
|
goto retry;
|
return -1;
|
}
|
|
buffer[len] = '\0';
|
msg = buffer;
|
}
|
|
/* parse AT message received from the RFCOMM */
|
if ((tmp = at_parse(msg, &reader->at)) == NULL) {
|
reader->next = msg;
|
errno = EBADMSG;
|
return -1;
|
}
|
|
if (strstr(at_type2str(reader->at.type), "RESP") && strstr(reader->at.value, "RING"))
|
rockchip_send_msg_to_deviceiolib("hfp_hf_ring");
|
else if (strstr(at_type2str(reader->at.type), "RESP") && strstr(reader->at.command, "+BCS"))
|
rockchip_send_msg_to_deviceiolib(msg);
|
else if (strstr(at_type2str(reader->at.type), "RESP") && strstr(reader->at.command, "+CIEV"))
|
rockchip_send_msg_to_deviceiolib(msg);
|
|
reader->next = tmp[0] != '\0' ? tmp : NULL;
|
return 0;
|
}
|
|
/**
|
* Write AT message.
|
*
|
* @param fd RFCOMM socket file descriptor.
|
* @param type Type of the AT message.
|
* @param command AT command or response code.
|
* @param value AT value or NULL if not applicable.
|
* @return On success this function returns 0. Otherwise, -1 is returned and
|
* errno is set to indicate the error. */
|
static int rfcomm_write_at(int fd, enum bt_at_type type, const char *command,
|
const char *value) {
|
|
char msg[256];
|
size_t len;
|
|
debug("Sending AT message: %s: command:%s, value:%s",
|
at_type2str(type), command, value);
|
|
at_build(msg, type, command, value);
|
len = strlen(msg);
|
|
retry:
|
if (write(fd, msg, len) == -1) {
|
if (errno == EINTR)
|
goto retry;
|
return -1;
|
}
|
|
return 0;
|
}
|
|
static int rockchip_send_msg_to_deviceiolib(char *msg)
|
{
|
struct sockaddr_un serverAddr;
|
int snd_cnt = 1;
|
int sockfd;
|
char buff[100] = {0};
|
|
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
if (sockfd < 0) {
|
printf("FUNC:%s create sockfd failed!\n", __func__);
|
return -1;
|
}
|
|
serverAddr.sun_family = AF_UNIX;
|
strcpy(serverAddr.sun_path, "/tmp/rk_deviceio_rfcomm_status");
|
memset(buff, 0, sizeof(buff));
|
sprintf(buff, "rfcomm status:%s;", msg);
|
|
while(snd_cnt--) {
|
sendto(sockfd, buff, strlen(buff), MSG_DONTWAIT, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
|
usleep(1000); //5ms
|
}
|
|
close(sockfd);
|
return 0;
|
}
|
|
/**
|
* HFP set state wrapper for debugging purposes. */
|
static void rfcomm_set_hfp_state(struct rfcomm_conn *c, enum hfp_state state) {
|
debug("HFP state transition: %d -> %d", c->state, state);
|
c->state = state;
|
}
|
|
/**
|
* Handle AT command response code. */
|
static int rfcomm_handler_resp_ok_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
/* advance service level connection state */
|
if (strcmp(at->value, "OK") == 0) {
|
rfcomm_set_hfp_state(c, c->state + 1);
|
return 0;
|
}
|
/* indicate caller that error has occurred */
|
if (strcmp(at->value, "ERROR") == 0) {
|
errno = ENOTSUP;
|
return -1;
|
}
|
|
return 0;
|
}
|
|
/**
|
* TEST: Standard indicator update AT command */
|
static int rfcomm_handler_cind_test_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
(void)at;
|
const int fd = c->t->bt_fd;
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIND",
|
"(\"call\",(0,1))"
|
",(\"callsetup\",(0-3))"
|
",(\"service\",(0-1))"
|
",(\"signal\",(0-5))"
|
",(\"roam\",(0-1))"
|
",(\"battchg\",(0-5))"
|
",(\"callheld\",(0-2))"
|
) == -1)
|
return -1;
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
if (c->state < HFP_SLC_CIND_TEST_OK)
|
rfcomm_set_hfp_state(c, HFP_SLC_CIND_TEST_OK);
|
|
return 0;
|
}
|
|
/**
|
* GET: Standard indicator update AT command */
|
static int rfcomm_handler_cind_get_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
(void)at;
|
const int fd = c->t->bt_fd;
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIND", "0,0,0,0,0,0,0") == -1)
|
return -1;
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
if (c->state < HFP_SLC_CIND_GET_OK)
|
rfcomm_set_hfp_state(c, HFP_SLC_CIND_GET_OK);
|
|
return 0;
|
}
|
|
/**
|
* RESP: Standard indicator update AT command */
|
static int rfcomm_handler_cind_resp_test_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
/* parse response for the +CIND TEST command */
|
if (at_parse_cind(at->value, c->hfp_ind_map) == -1)
|
warn("Couldn't parse AG indicators");
|
if (c->state < HFP_SLC_CIND_TEST)
|
rfcomm_set_hfp_state(c, HFP_SLC_CIND_TEST);
|
return 0;
|
}
|
|
/**
|
* RESP: Standard indicator update AT command */
|
static int rfcomm_handler_cind_resp_get_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
char *tmp = at->value;
|
size_t i;
|
|
/* parse response for the +CIND GET command */
|
for (i = 0; i < ARRAYSIZE(c->hfp_ind_map); i++) {
|
t->rfcomm.hfp_inds[c->hfp_ind_map[i]] = atoi(tmp);
|
if (c->hfp_ind_map[i] == HFP_IND_BATTCHG)
|
device_set_battery_level(t->device, atoi(tmp) * 100 / 5);
|
if ((tmp = strchr(tmp, ',')) == NULL)
|
break;
|
tmp += 1;
|
}
|
|
if (c->state < HFP_SLC_CIND_GET)
|
rfcomm_set_hfp_state(c, HFP_SLC_CIND_GET);
|
|
return 0;
|
}
|
|
/**
|
* SET: Standard event reporting activation/deactivation AT command */
|
static int rfcomm_handler_cmer_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
(void)at;
|
const int fd = c->t->bt_fd;
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
if (c->state < HFP_SLC_CMER_SET_OK)
|
rfcomm_set_hfp_state(c, HFP_SLC_CMER_SET_OK);
|
|
return 0;
|
}
|
|
/**
|
* RESP: Standard indicator events reporting unsolicited result code */
|
static int rfcomm_handler_ciev_resp_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
unsigned int index;
|
unsigned int value;
|
|
if (sscanf(at->value, "%u,%u", &index, &value) == 2) {
|
t->rfcomm.hfp_inds[c->hfp_ind_map[index - 1]] = value;
|
switch (c->hfp_ind_map[index - 1]) {
|
case HFP_IND_CALL:
|
if (value == 1)
|
transport_send_signal(t->rfcomm.sco,
|
TRANSPORT_PCM_OPEN);
|
/* Don't trigger SCO thread to disconnect SCO link */
|
/* else if (value == 0)
|
* transport_send_signal(t->rfcomm.sco,
|
* TRANSPORT_PCM_CLOSE);
|
*/
|
break;
|
case HFP_IND_CALLSETUP:
|
break;
|
case HFP_IND_BATTCHG:
|
device_set_battery_level(t->device, value * 100 / 5);
|
break;
|
default:
|
break;
|
}
|
}
|
|
return 0;
|
}
|
|
/**
|
* SET: Bluetooth Indicators Activation */
|
static int rfcomm_handler_bia_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
(void)at;
|
/* We are not sending any indicators to the HF, however support for the
|
* +BIA command is mandatory for the AG, so acknowledge the message. */
|
if (rfcomm_write_at(c->t->bt_fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
return 0;
|
}
|
|
/**
|
* SET: Bluetooth Retrieve Supported Features */
|
static int rfcomm_handler_brsf_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
const int fd = t->bt_fd;
|
char tmp[16];
|
|
t->rfcomm.hfp_features = atoi(at->value);
|
|
/* Codec negotiation is not supported in the HF, hence no
|
* wideband audio support. AT+BAC will not be sent. */
|
if (!(t->rfcomm.hfp_features & HFP_HF_FEAT_CODEC))
|
t->rfcomm.sco->codec = HFP_CODEC_CVSD;
|
|
sprintf(tmp, "%u", config.hfp.features_rfcomm_ag);
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, "+BRSF", tmp) == -1)
|
return -1;
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
if (c->state < HFP_SLC_BRSF_SET_OK)
|
rfcomm_set_hfp_state(c, HFP_SLC_BRSF_SET_OK);
|
|
return 0;
|
}
|
|
/**
|
* RESP: Bluetooth Retrieve Supported Features */
|
static int rfcomm_handler_brsf_resp_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
t->rfcomm.hfp_features = atoi(at->value);
|
|
/* codec negotiation is not supported in the AG */
|
if (!(t->rfcomm.hfp_features & HFP_AG_FEAT_CODEC))
|
t->rfcomm.sco->codec = HFP_CODEC_CVSD;
|
|
if (c->state < HFP_SLC_BRSF_SET)
|
rfcomm_set_hfp_state(c, HFP_SLC_BRSF_SET);
|
|
return 0;
|
}
|
|
/**
|
* SET: Gain of Microphone */
|
static int rfcomm_handler_vgm_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
struct ba_transport * const t = c->t;
|
const int fd = t->bt_fd;
|
|
t->rfcomm.sco->sco.mic_gain = c->mic_gain = atoi(at->value);
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
bluealsa_ctl_event(BA_EVENT_UPDATE_VOLUME);
|
return 0;
|
}
|
|
/**
|
* SET: Gain of Speaker */
|
static int rfcomm_handler_vgs_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
struct ba_transport * const t = c->t;
|
const int fd = t->bt_fd;
|
|
t->rfcomm.sco->sco.spk_gain = c->spk_gain = atoi(at->value);
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
bluealsa_ctl_event(BA_EVENT_UPDATE_VOLUME);
|
return 0;
|
}
|
|
/**
|
* SET: Bluetooth Response and Hold Feature */
|
static int rfcomm_handler_btrh_get_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
(void)at;
|
/* Currently, we are not supporting Respond & Hold feature, so just
|
* acknowledge this GET request without reporting +BTRH status. */
|
if (rfcomm_write_at(c->t->bt_fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
return 0;
|
}
|
|
/**
|
* SET: Bluetooth Codec Selection */
|
static int rfcomm_handler_bcs_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
const int fd = t->bt_fd;
|
|
if (t->rfcomm.sco->codec != atoi(at->value)) {
|
warn("Codec not acknowledged: %d != %s", t->rfcomm.sco->codec, at->value);
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "ERROR") == -1)
|
return -1;
|
return 0;
|
}
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
if (c->state < HFP_CC_BCS_SET_OK)
|
rfcomm_set_hfp_state(c, HFP_CC_BCS_SET_OK);
|
|
return 0;
|
}
|
|
static int rfcomm_handler_resp_bcs_ok_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
struct ba_transport * const t = c->t;
|
|
/* XXX: this param should be read from rtl8xxx_config */
|
struct pcmif_param {
|
uint8_t pcmifctrl1[2];
|
uint8_t pcmifctrl2[2];
|
uint8_t pcmifctrl3[2];
|
uint8_t pcmconv;
|
uint8_t scoconv;
|
uint8_t hci_ext_codec;
|
} pcmif = {
|
0x83, 0x10,
|
0x00, 0x00,
|
0x12, 0x80,
|
0x00,
|
0x00,
|
0x01
|
};
|
|
if (rfcomm_handler_resp_ok_cb(c, at) != 0)
|
return -1;
|
|
if (t->rfcomm.sco->codec == HFP_CODEC_MSBC) {
|
pcmif.hci_ext_codec = 0x41;
|
pcmif.pcmifctrl3[1] = 0x04;
|
} else if (t->rfcomm.sco->codec == HFP_CODEC_CVSD) {
|
pcmif.hci_ext_codec = 0x01;
|
pcmif.pcmifctrl3[1] = 0x80;
|
}
|
|
if (hci_submit_cmd_wait(0x3f, 0x93, (uint8_t *)&pcmif,
|
sizeof(pcmif)) < 0) {
|
error("Couldn't set controller pcm interface");
|
return -1;
|
}
|
|
/* When codec selection is completed, notify connected clients, that
|
* transport has been changed. Note, that this event might be emitted
|
* for an active transport - codec switching. */
|
bluealsa_ctl_event(BA_EVENT_TRANSPORT_CHANGED);
|
return 0;
|
}
|
|
/**
|
* RESP: Bluetooth Codec Selection */
|
static int rfcomm_handler_bcs_resp_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
static const struct rfcomm_handler handler = {
|
AT_TYPE_RESP, "", rfcomm_handler_resp_bcs_ok_cb };
|
struct ba_transport * const t = c->t;
|
const int fd = t->bt_fd;
|
struct ba_transport *t_sco;
|
struct bt_voice voice = {
|
.setting = BT_VOICE_CVSD_16BIT,
|
};
|
|
t->rfcomm.sco->codec = atoi(at->value);
|
|
/* Change voice setting according to codec */
|
if (t->rfcomm.sco->codec == HFP_CODEC_MSBC)
|
voice.setting = 0x0063;
|
t_sco = t->rfcomm.sco;
|
if (setsockopt(t_sco->sco.listen_fd, SOL_BLUETOOTH, BT_VOICE,
|
&voice, sizeof(voice)) == -1) {
|
error("setsockopt BT_VOICE error %d, %s", errno,
|
strerror(errno));
|
return 0;
|
}
|
|
if (rfcomm_write_at(fd, AT_TYPE_CMD_SET, "+BCS", at->value) == -1)
|
return -1;
|
|
c->handler = &handler;
|
|
if (c->state < HFP_CC_BCS_SET)
|
rfcomm_set_hfp_state(c, HFP_CC_BCS_SET);
|
|
return 0;
|
}
|
|
/**
|
* SET: Bluetooth Available Codecs */
|
static int rfcomm_handler_bac_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
(void)at;
|
const int fd = c->t->bt_fd;
|
|
/* In case some headsets send BAC even if we don't advertise
|
* support for it. In such case, just OK and ignore. */
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
|
if (c->state < HFP_SLC_BAC_SET_OK)
|
rfcomm_set_hfp_state(c, HFP_SLC_BAC_SET_OK);
|
|
return 0;
|
}
|
|
/**
|
* SET: Apple Ext: Report a headset state change */
|
static int rfcomm_handler_iphoneaccev_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
struct ba_device * const d = t->device;
|
const int fd = t->bt_fd;
|
|
char *ptr = at->value;
|
size_t count = atoi(strsep(&ptr, ","));
|
char tmp;
|
|
while (count-- && ptr != NULL)
|
switch (tmp = *strsep(&ptr, ",")) {
|
case '1':
|
if (ptr != NULL)
|
device_set_battery_level(d, atoi(strsep(&ptr, ",")) * 100 / 9);
|
break;
|
case '2':
|
if (ptr != NULL)
|
d->xapl.accev_docked = atoi(strsep(&ptr, ","));
|
break;
|
default:
|
warn("Unsupported IPHONEACCEV key: %c", tmp);
|
strsep(&ptr, ",");
|
}
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
|
return -1;
|
return 0;
|
}
|
|
/**
|
* SET: Apple Ext: Enable custom AT commands from an accessory */
|
static int rfcomm_handler_xapl_set_cb(struct rfcomm_conn *c, const struct bt_at *at) {
|
|
struct ba_transport * const t = c->t;
|
struct ba_device * const d = t->device;
|
const int fd = t->bt_fd;
|
|
const char *resp = "+XAPL=BlueALSA,0";
|
unsigned int vendor, product;
|
unsigned int version, features;
|
|
if (sscanf(at->value, "%x-%x-%u,%u", &vendor, &product, &version, &features) == 4) {
|
d->xapl.vendor_id = vendor;
|
d->xapl.product_id = product;
|
d->xapl.version = version;
|
d->xapl.features = features;
|
}
|
else {
|
warn("Invalid XAPL value: %s", at->value);
|
resp = "ERROR";
|
}
|
|
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, resp) == -1)
|
return -1;
|
return 0;
|
}
|
|
static const struct rfcomm_handler rfcomm_handler_resp_ok = {
|
AT_TYPE_RESP, "", rfcomm_handler_resp_ok_cb };
|
static const struct rfcomm_handler rfcomm_handler_cind_test = {
|
AT_TYPE_CMD_TEST, "+CIND", rfcomm_handler_cind_test_cb };
|
static const struct rfcomm_handler rfcomm_handler_cind_get = {
|
AT_TYPE_CMD_GET, "+CIND", rfcomm_handler_cind_get_cb };
|
static const struct rfcomm_handler rfcomm_handler_cind_resp_test = {
|
AT_TYPE_RESP, "+CIND", rfcomm_handler_cind_resp_test_cb };
|
static const struct rfcomm_handler rfcomm_handler_cind_resp_get = {
|
AT_TYPE_RESP, "+CIND", rfcomm_handler_cind_resp_get_cb };
|
static const struct rfcomm_handler rfcomm_handler_cmer_set = {
|
AT_TYPE_CMD_SET, "+CMER", rfcomm_handler_cmer_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_ciev_resp = {
|
AT_TYPE_RESP, "+CIEV", rfcomm_handler_ciev_resp_cb };
|
static const struct rfcomm_handler rfcomm_handler_bia_set = {
|
AT_TYPE_CMD_SET, "+BIA", rfcomm_handler_bia_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_brsf_set = {
|
AT_TYPE_CMD_SET, "+BRSF", rfcomm_handler_brsf_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_brsf_resp = {
|
AT_TYPE_RESP, "+BRSF", rfcomm_handler_brsf_resp_cb };
|
static const struct rfcomm_handler rfcomm_handler_vgm_set = {
|
AT_TYPE_CMD_SET, "+VGM", rfcomm_handler_vgm_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_vgs_set = {
|
AT_TYPE_CMD_SET, "+VGS", rfcomm_handler_vgs_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_btrh_get = {
|
AT_TYPE_CMD_GET, "+BTRH", rfcomm_handler_btrh_get_cb };
|
static const struct rfcomm_handler rfcomm_handler_bcs_set = {
|
AT_TYPE_CMD_SET, "+BCS", rfcomm_handler_bcs_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_bcs_resp = {
|
AT_TYPE_RESP, "+BCS", rfcomm_handler_bcs_resp_cb };
|
static const struct rfcomm_handler rfcomm_handler_bac_set = {
|
AT_TYPE_CMD_SET, "+BAC", rfcomm_handler_bac_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_iphoneaccev_set = {
|
AT_TYPE_CMD_SET, "+IPHONEACCEV", rfcomm_handler_iphoneaccev_set_cb };
|
static const struct rfcomm_handler rfcomm_handler_xapl_set = {
|
AT_TYPE_CMD_SET, "+XAPL", rfcomm_handler_xapl_set_cb };
|
|
/**
|
* Get callback (if available) for given AT message. */
|
static rfcomm_callback *rfcomm_get_callback(const struct bt_at *at) {
|
|
static const struct rfcomm_handler *handlers[] = {
|
&rfcomm_handler_cind_test,
|
&rfcomm_handler_cind_get,
|
&rfcomm_handler_cmer_set,
|
&rfcomm_handler_ciev_resp,
|
&rfcomm_handler_bia_set,
|
&rfcomm_handler_brsf_set,
|
&rfcomm_handler_vgm_set,
|
&rfcomm_handler_vgs_set,
|
&rfcomm_handler_btrh_get,
|
&rfcomm_handler_bcs_set,
|
&rfcomm_handler_bcs_resp,
|
&rfcomm_handler_bac_set,
|
&rfcomm_handler_iphoneaccev_set,
|
&rfcomm_handler_xapl_set,
|
};
|
|
size_t i;
|
|
for (i = 0; i < ARRAYSIZE(handlers); i++) {
|
if (handlers[i]->type != at->type)
|
continue;
|
if (strcmp(handlers[i]->command, at->command) != 0)
|
continue;
|
return handlers[i]->callback;
|
}
|
|
return NULL;
|
}
|
|
void *rfcomm_thread(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
/* initialize structure used for synchronization */
|
struct rfcomm_conn conn = {
|
.state = HFP_DISCONNECTED,
|
.state_prev = HFP_DISCONNECTED,
|
.mic_gain = t->rfcomm.sco->sco.mic_gain,
|
.spk_gain = t->rfcomm.sco->sco.spk_gain,
|
.t = t,
|
};
|
|
struct at_reader reader = { .next = NULL };
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ t->bt_fd, POLLIN, 0 },
|
};
|
|
debug("Starting RFCOMM loop: %s",
|
bluetooth_profile_to_string(t->profile));
|
for (;;) {
|
|
/* During normal operation, RFCOMM should block indefinitely. However,
|
* in the HFP-HF mode, service level connection has to be initialized
|
* by ourself. In order to do this reliably, we have to assume, that
|
* AG might not receive our message and will not send proper response.
|
* Hence, we will incorporate timeout, after which we will send our
|
* AT command once more. */
|
int timeout = -1;
|
|
rfcomm_callback *callback;
|
char tmp[16];
|
|
if (conn.state != HFP_CONNECTED) {
|
|
/* If some progress has been made in the SLC procedure, reset the
|
* retries counter. */
|
if (conn.state != conn.state_prev) {
|
conn.state_prev = conn.state;
|
conn.retries = 0;
|
}
|
|
/* If the maximal number of retries has been reached, terminate the
|
* connection. Trying indefinitely will only use up our resources. */
|
if (conn.retries > RFCOMM_SLC_RETRIES) {
|
errno = ETIMEDOUT;
|
goto ioerror;
|
}
|
|
if (t->profile == BLUETOOTH_PROFILE_HFP_HF)
|
switch (conn.state) {
|
case HFP_DISCONNECTED:
|
sprintf(tmp, "%u", config.hfp.features_rfcomm_hf);
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_SET, "+BRSF", tmp) == -1)
|
goto ioerror;
|
conn.handler = &rfcomm_handler_brsf_resp;
|
break;
|
case HFP_SLC_BRSF_SET:
|
conn.handler = &rfcomm_handler_resp_ok;
|
break;
|
case HFP_SLC_BRSF_SET_OK:
|
if (t->rfcomm.hfp_features & HFP_AG_FEAT_CODEC) {
|
/* XXX: If mSBC is supported, please change 1 to 1,2 */
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_SET, "+BAC", "1,2") == -1)
|
goto ioerror;
|
conn.handler = &rfcomm_handler_resp_ok;
|
break;
|
}
|
/* fall-through */
|
case HFP_SLC_BAC_SET_OK:
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_TEST, "+CIND", NULL) == -1)
|
goto ioerror;
|
conn.handler = &rfcomm_handler_cind_resp_test;
|
break;
|
case HFP_SLC_CIND_TEST:
|
conn.handler = &rfcomm_handler_resp_ok;
|
break;
|
case HFP_SLC_CIND_TEST_OK:
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_GET, "+CIND", NULL) == -1)
|
goto ioerror;
|
conn.handler = &rfcomm_handler_cind_resp_get;
|
break;
|
case HFP_SLC_CIND_GET:
|
conn.handler = &rfcomm_handler_resp_ok;
|
break;
|
case HFP_SLC_CIND_GET_OK:
|
/* Activate indicator events reporting. The +CMER specification is
|
* as follows: AT+CMER=[<mode>[,<keyp>[,<disp>[,<ind>[,<bfr>]]]]] */
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_CMD_SET, "+CMER", "3,0,0,1,0") == -1)
|
goto ioerror;
|
conn.handler = &rfcomm_handler_resp_ok;
|
break;
|
case HFP_SLC_CMER_SET_OK:
|
rfcomm_set_hfp_state(&conn, HFP_SLC_CONNECTED);
|
rockchip_send_msg_to_deviceiolib("hfp_slc_connected");
|
/* fall-through */
|
case HFP_SLC_CONNECTED:
|
if (t->rfcomm.hfp_features & HFP_AG_FEAT_CODEC)
|
break;
|
/* fall-through */
|
case HFP_CC_BCS_SET:
|
case HFP_CC_BCS_SET_OK:
|
case HFP_CC_CONNECTED:
|
rfcomm_set_hfp_state(&conn, HFP_CONNECTED);
|
rockchip_send_msg_to_deviceiolib("hfp_hf_connected");
|
/* fall-through */
|
case HFP_CONNECTED:
|
bluealsa_ctl_event(BA_EVENT_TRANSPORT_ADDED);
|
}
|
|
if (t->profile == BLUETOOTH_PROFILE_HFP_AG)
|
switch (conn.state) {
|
case HFP_DISCONNECTED:
|
case HFP_SLC_BRSF_SET:
|
case HFP_SLC_BRSF_SET_OK:
|
case HFP_SLC_BAC_SET_OK:
|
case HFP_SLC_CIND_TEST:
|
case HFP_SLC_CIND_TEST_OK:
|
case HFP_SLC_CIND_GET:
|
case HFP_SLC_CIND_GET_OK:
|
break;
|
case HFP_SLC_CMER_SET_OK:
|
rfcomm_set_hfp_state(&conn, HFP_SLC_CONNECTED);
|
/* fall-through */
|
case HFP_SLC_CONNECTED:
|
if (t->rfcomm.hfp_features & HFP_HF_FEAT_CODEC) {
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RESP, "+BCS", "1") == -1)
|
goto ioerror;
|
t->rfcomm.sco->codec = HFP_CODEC_CVSD;
|
conn.handler = &rfcomm_handler_bcs_set;
|
break;
|
}
|
/* fall-through */
|
case HFP_CC_BCS_SET:
|
case HFP_CC_BCS_SET_OK:
|
case HFP_CC_CONNECTED:
|
rfcomm_set_hfp_state(&conn, HFP_CONNECTED);
|
/* fall-through */
|
case HFP_CONNECTED:
|
bluealsa_ctl_event(BA_EVENT_TRANSPORT_ADDED);
|
}
|
|
if (conn.handler != NULL) {
|
timeout = RFCOMM_SLC_TIMEOUT;
|
conn.retries++;
|
}
|
|
}
|
|
/* skip poll() since we've got unprocessed data */
|
if (reader.next != NULL)
|
goto read;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
switch (poll(pfds, ARRAYSIZE(pfds), timeout)) {
|
case 0:
|
debug("RFCOMM poll timeout");
|
continue;
|
case -1:
|
if (errno == EINTR)
|
continue;
|
error("RFCOMM poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
|
switch (sig) {
|
case TRANSPORT_SET_VOLUME:
|
if (conn.mic_gain != t->rfcomm.sco->sco.mic_gain) {
|
char tmp[16];
|
int gain = conn.mic_gain = t->rfcomm.sco->sco.mic_gain;
|
debug("Setting microphone gain: %d", gain);
|
sprintf(tmp, "+VGM=%d", gain);
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RESP, NULL, tmp) == -1)
|
goto ioerror;
|
}
|
if (conn.spk_gain != t->rfcomm.sco->sco.spk_gain) {
|
char tmp[16];
|
int gain = conn.spk_gain = t->rfcomm.sco->sco.spk_gain;
|
debug("Setting speaker gain: %d", gain);
|
sprintf(tmp, "+VGS=%d", gain);
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RESP, NULL, tmp) == -1)
|
goto ioerror;
|
}
|
break;
|
case TRANSPORT_SEND_RFCOMM: {
|
char cmd[32];
|
if (read(pfds[0].fd, cmd, sizeof(cmd)) != sizeof(cmd))
|
warn("Couldn't read RFCOMM command: %s", strerror(errno));
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RAW, cmd, NULL) == -1)
|
goto ioerror;
|
break;
|
}
|
default:
|
break;
|
}
|
|
}
|
|
if (pfds[1].revents & POLLIN) {
|
/* read data from the RFCOMM */
|
|
read:
|
if (rfcomm_read_at(pfds[1].fd, &reader) == -1)
|
switch (errno) {
|
case EBADMSG:
|
warn("Invalid AT message: %s", reader.next);
|
reader.next = NULL;
|
continue;
|
default:
|
goto ioerror;
|
}
|
|
/* use predefined callback, otherwise get generic one */
|
if (conn.handler != NULL && conn.handler->type == reader.at.type &&
|
strcmp(conn.handler->command, reader.at.command) == 0) {
|
callback = conn.handler->callback;
|
conn.handler = NULL;
|
}
|
else
|
callback = rfcomm_get_callback(&reader.at);
|
|
if (callback != NULL) {
|
if (callback(&conn, &reader.at) == -1)
|
goto ioerror;
|
}
|
else {
|
warn("Unsupported AT message: %s: command:%s, value:%s",
|
at_type2str(reader.at.type), reader.at.command, reader.at.value);
|
if (reader.at.type != AT_TYPE_RESP)
|
if (rfcomm_write_at(pfds[1].fd, AT_TYPE_RESP, NULL, "ERROR") == -1)
|
goto ioerror;
|
}
|
|
}
|
else if (pfds[1].revents & (POLLERR | POLLHUP)) {
|
errno = ECONNRESET;
|
goto ioerror;
|
}
|
|
continue;
|
|
ioerror:
|
switch (errno) {
|
case ECONNABORTED:
|
case ECONNRESET:
|
case ENOTCONN:
|
case ENOTSUP:
|
case ETIMEDOUT:
|
/* exit the thread upon socket disconnection */
|
debug("RFCOMM disconnected: %s", strerror(errno));
|
rockchip_send_msg_to_deviceiolib("hfp_slc_disconnected");
|
goto fail;
|
default:
|
error("RFCOMM IO error: %s", strerror(errno));
|
}
|
}
|
|
fail:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|