/* * 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 #include #include #include #include #include #include #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=[[,[,[,[,]]]]] */ 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; }