/* * Copyright (c) 2015, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "fm_hci_helium" #include #include #include "bt_hci_bdroid.h" #include "bt_vendor_lib.h" #include "userial.h" #include "fm_hci.h" #include "wcnss_hci.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static int fm_hal_fd =0; #define FM_VND_SERVICE_START "wc_transport.start_fmhci" #define WAIT_TIMEOUT 200000 /* 200*1000us */ static void fm_hci_exit(void *arg); static int power(struct fm_hci_t *hci, fm_power_state_t state); static void event_notification(struct fm_hci_t *hci, uint16_t event) { pthread_mutex_lock(&hci->event_lock); ALOGI("%s: Notifying worker thread with event: %d", __func__, event); ready_events |= event; pthread_cond_broadcast(&hci->event_cond); pthread_mutex_unlock(&hci->event_lock); } static void rx_thread_exit_handler(int sig) { ALOGD("%s: sig = 0x%x", __func__, sig); if (sig == SIGUSR1) { ALOGE("Got the signal.. exiting"); pthread_exit(NULL); } } static int vendor_init(struct fm_hci_t *hci) { void *dlhandle = hci->dlhandle = NULL; unsigned char bdaddr[] = {0xaa, 0xbb, 0xcc, 0x11, 0x22, 0x33}; dlhandle = dlopen("libbt-vendor.so", RTLD_NOW); if (!dlhandle) { ALOGE("!!! Failed to load libbt-vendor.so !!!"); goto err; } hci->vendor = (bt_vendor_interface_t *) dlsym(dlhandle, "BLUETOOTH_VENDOR_LIB_INTERFACE"); if (!hci->vendor) { ALOGE("!!! Failed to get bt vendor interface !!!"); goto err; } ALOGI("FM-HCI: Registering the WCNSS HAL library by passing CBs and BD addr."); if (hci->vendor->init(&fm_vendor_callbacks, bdaddr) != FM_HC_STATUS_SUCCESS) { ALOGE("FM vendor interface init failed"); goto err; } return FM_HC_STATUS_SUCCESS; err: return FM_HC_STATUS_FAIL; } static void vendor_close(struct fm_hci_t *hci) { void *dlhandle = hci->dlhandle; if (hci->vendor) hci->vendor->cleanup(); if (dlhandle) { dlclose(dlhandle); dlhandle = NULL; } hci->vendor = NULL; } /* De-queues the FM CMD from the struct transmit_queue_t */ static void dequeue_fm_tx_cmd(struct fm_hci_t *hci) { struct transmit_queue_t *temp; uint16_t count = 0, len = 0; ALOGD("%s", __func__); while (1) { pthread_mutex_lock(&hci->tx_q_lock); temp = hci->first; if (!temp) { ALOGI("No FM CMD available in the Queue\n"); pthread_mutex_unlock(&hci->tx_q_lock); return; } else { hci->first = temp->next; } pthread_mutex_unlock(&hci->tx_q_lock); pthread_mutex_lock(&hci->credit_lock); wait_for_cmd_credits: while (hci->command_credits == 0) { pthread_cond_wait(&hci->cmd_credits_cond, &hci->credit_lock); } /* Check if we really got the command credits */ if (hci->command_credits) { len = sizeof(struct fm_command_header_t) + temp->hdr->len; again: /* Use socket 'fd' to send the command down to WCNSS Filter */ count = write(hci->fd, (uint8_t *)temp->hdr + count, len); if (count < len) { len -= count; goto again; } count = 0; /* Decrement cmd credits by '1' after sending the cmd*/ hci->command_credits--; if (temp->hdr) free(temp->hdr); free(temp); } else { if (!lib_running) { pthread_mutex_unlock(&hci->credit_lock); break; } goto wait_for_cmd_credits; } pthread_mutex_unlock(&hci->credit_lock); } } static int read_fm_event(struct fm_hci_t *hci, struct fm_event_header_t *pbuf, int len) { fd_set readFds; sigset_t sigmask, emptymask; int n = 0, ret = -1, evt_len = -1,status=0; volatile int fd = hci->fd; struct sigaction action; sigemptyset(&sigmask); sigaddset(&sigmask, SIGUSR1); if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) { ALOGE("failed to sigprocmask"); } memset(&action, 0, sizeof(struct sigaction)); sigemptyset(&action.sa_mask); action.sa_flags = 0; action.sa_handler = rx_thread_exit_handler; sigemptyset(&emptymask); if (sigaction(SIGUSR1, &action, NULL) < 0) { ALOGE("%s:sigaction failed", __func__); } while (lib_running) { FD_ZERO(&readFds); FD_SET(fd, &readFds); ALOGV("%s: Waiting for events from WCNSS FILTER...\n", __func__); /* Wait for event/data from WCNSS Filter */ n = pselect(fd+1, &readFds, NULL, NULL, NULL, &emptymask); if (n > 0) { /* Check if event is available or not */ if (FD_ISSET(fd, &readFds)) { ret = read(fd, (uint8_t *)pbuf, (size_t)(sizeof(struct fm_event_header_t) + MAX_FM_EVT_PARAMS)); if (0 == ret) { ALOGV("%s: read() returned '0' bytes\n", __func__); break; } else { ALOGV("%s: read() returned %d bytes of FM event/data\n", __func__, ret); while (ret > 0) { pthread_mutex_lock(&hci->credit_lock); if (pbuf->evt_code == FM_CMD_COMPLETE) { hci->command_credits = pbuf->params[0]; pthread_cond_signal(&hci->cmd_credits_cond); } else if (pbuf->evt_code == FM_CMD_STATUS) { hci->command_credits = pbuf->params[1]; pthread_cond_signal(&hci->cmd_credits_cond); } else if (pbuf->evt_code == FM_HW_ERR_EVENT) { ALOGI("%s: FM H/w Err Event Recvd. Event Code: 0x%2x", __func__, pbuf->evt_code); lib_running =0; hci->vendor->ssr_cleanup(0x22); status = power(hci, FM_RADIO_DISABLE); if (status < 0) { ALOGE("power off fm radio failed during SSR "); } } else { ALOGE("%s: Not CS/CC Event: Recvd. Event Code: 0x%2x", __func__, pbuf->evt_code); } pthread_mutex_unlock(&hci->credit_lock); evt_len = pbuf->evt_len; /* Notify 'hci_tx_thread' about availability of event or data */ ALOGI("%s: \nNotifying 'hci_tx_thread' availability of FM event or data...\n", __func__); event_notification(hci, HC_EVENT_RX); if (hci->cb && hci->cb->process_event) hci->cb->process_event(hci->private_data, (uint8_t *)pbuf); else ALOGE("%s: ASSERT $$$$$$ Callback function NULL $$$$$", __func__); ret = ret - (evt_len + 3); ALOGD("%s: Length of available bytes @ HCI Layer: %d", __func__, ret); if (ret > 0) { ALOGE("%s: Remaining bytes of event/data: %d", __func__, ret); pbuf = (struct fm_event_header_t *)&pbuf->params[evt_len]; } } } //end of processing the event } else ALOGV("%s: No data available, though select returned!!!\n", __func__); } else if (n < 0) { ALOGE("%s: select() failed with return value: %d", __func__, ret); lib_running =0; } else if (n == 0) ALOGE("%s: select() timeout!!!", __func__); } return ret; } static void *hci_read_thread(void *arg) { int length = 0; struct fm_hci_t *hci = (struct fm_hci_t *)arg; struct fm_event_header_t *evt_buf = (struct fm_event_header_t *) malloc(sizeof(struct fm_event_header_t) + MAX_FM_EVT_PARAMS); if (!evt_buf) { ALOGE("%s: Memory allocation failed for evt_buf", __func__); goto cleanup; } length = read_fm_event(hci, evt_buf, sizeof(struct fm_event_header_t) + MAX_FM_EVT_PARAMS); ALOGD("length=%d\n",length); if(length <=0) { lib_running =0; } goto exit; cleanup: lib_running = 0; hci = NULL; exit: ALOGV("%s: Leaving hci_read_thread()", __func__); if (evt_buf) free(evt_buf); pthread_exit(NULL); return arg; } int connect_to_local_fmsocket(char* name) { socklen_t len; int sk = -1; ALOGE("%s: ACCEPT ", __func__); sk = socket(AF_LOCAL, SOCK_STREAM, 0); if (sk < 0) { ALOGE("Socket creation failure"); return -1; } if(socket_local_client_connect(sk, name, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM) < 0) { ALOGE("failed to connect (%s)", strerror(errno)); close(sk); sk = -1; } else { ALOGE("%s: Connection succeeded\n", __func__); } return sk; } /* * Reads the FM-CMDs from the struct transmit_queue_t and sends it down to WCNSS Filter * Reads events sent by the WCNSS Filter and copies onto RX_Q */ static void* hci_tx_thread(void *arg) { uint16_t events; struct fm_hci_t *hci = (struct fm_hci_t *)arg; while (lib_running) { pthread_mutex_lock(&hci->event_lock); if (!(ready_events & HC_EVENT_TX)) pthread_cond_wait(&hci->event_cond, &hci->event_lock); ALOGE("%s: ready_events= %d", __func__, ready_events); events = ready_events; if (ready_events & HC_EVENT_TX) ready_events &= (~HC_EVENT_TX); if (ready_events & HC_EVENT_RX) ready_events &= (~HC_EVENT_RX); pthread_mutex_unlock(&hci->event_lock); if (events & HC_EVENT_TX) { dequeue_fm_tx_cmd(hci); } if (events & HC_EVENT_RX) { ALOGI("\n##### FM-HCI Task : EVENT_RX available #####\n"); } if (events & HC_EVENT_EXIT) { ALOGE("GOT HC_EVENT_EXIT.. exiting"); break; } } ALOGV("%s: ##### Exiting hci_tx_thread Worker thread!!! #####", __func__); return NULL; } void stop_fmhal_service() { char value[PROPERTY_VALUE_MAX] = {'\0'}; ALOGI("%s: Entry ", __func__); property_get(FM_VND_SERVICE_START, value, "false"); if (strcmp(value, "false") == 0) { ALOGI("%s: fmhal service has been stopped already", __func__); // return; } close(fm_hal_fd); fm_hal_fd = -1; property_set(FM_VND_SERVICE_START, "false"); property_set("wc_transport.fm_service_status", "0"); ALOGI("%s: Exit ", __func__); } void start_fmhal_service() { ALOGI("%s: Entry ", __func__); int i, init_success = 0; char value[PROPERTY_VALUE_MAX] = {'\0'}; property_get(FM_VND_SERVICE_START, value, false); if (strcmp(value, "true") == 0) { ALOGI("%s: hci_filter has been started already", __func__); return; } // property_set("wc_transport.fm_service_status", "0"); property_set(FM_VND_SERVICE_START, "true"); ALOGI("%s: %s set to true ", __func__, FM_VND_SERVICE_START ); for(i=0; i<45; i++) { property_get("wc_transport.fm_service_status", value, "0"); if (strcmp(value, "1") == 0) { ALOGI("%s: wc_transport.fm_service_status set to %s", __func__,value); init_success = 1; break; } else { usleep(WAIT_TIMEOUT); } } ALOGI("start_fmhal_service status:%d after %f seconds \n", init_success, 0.2*i); ALOGI("%s: Exit ", __func__); } static int start_tx_thread(struct fm_hci_t *hci) { struct sched_param param; int policy, result; ALOGI("FM-HCI: Creating the FM-HCI TASK..."); if (pthread_create(&hci->tx_thread, NULL, hci_tx_thread, hci) != 0) { ALOGE("pthread_create failed!"); lib_running = 0; return FM_HC_STATUS_FAIL; } if(pthread_getschedparam(hci->tx_thread, &policy, ¶m)==0) { policy = SCHED_NORMAL; #if (BTHC_LINUX_BASE_POLICY!=SCHED_NORMAL) param.sched_priority = BTHC_MAIN_THREAD_PRIORITY; #endif result = pthread_setschedparam(hci->tx_thread, policy, ¶m); if (result != 0) { ALOGW("libbt-hci init: pthread_setschedparam failed (%d)", \ result); } } else ALOGI("FM-HCI: Failed to get the Scheduling parameters!!!"); return FM_HC_STATUS_SUCCESS; } static void stop_tx_thread(struct fm_hci_t *hci) { int ret; ALOGV("%s++", __func__); if ((ret = pthread_kill(hci->tx_thread, SIGUSR1)) == FM_HC_STATUS_SUCCESS) { ALOGE("%s:pthread_join", __func__); if ((ret = pthread_join(hci->tx_thread, NULL)) != FM_HC_STATUS_SUCCESS) ALOGE("Error joining tx thread, error = %d (%s)", ret, strerror(ret)); } else { ALOGE("Error killing tx thread, error = %d (%s)", ret, strerror(ret)); } } static void *hci_mon_thread(void *arg) { struct fm_hci_t *hci = (struct fm_hci_t *)arg; uint16_t events; ALOGV("%s", __func__); while (lib_running) { pthread_mutex_lock(&hci->event_lock); if (!(ready_events & HC_EVENT_EXIT)) pthread_cond_wait(&hci->event_cond, &hci->event_lock); events = ready_events; if (ready_events & HC_EVENT_EXIT) ready_events &= (~HC_EVENT_EXIT); pthread_mutex_unlock(&hci->event_lock); ALOGD("events = 0x%x", events); if (events & HC_EVENT_EXIT) { ALOGD("Got Exit event.. Exiting HCI"); fm_hci_exit(hci); break; } } ALOGV("%s--", __func__); return NULL; } static int start_mon_thread(struct fm_hci_t *hci) { int ret = FM_HC_STATUS_SUCCESS; ALOGD("%s", __func__); if ((ret = pthread_create(&hci->mon_thread, NULL, hci_mon_thread, hci)) !=0) { ALOGE("pthread_create failed! status = %d (%s)", ret, strerror(ret)); lib_running = 0; } return ret; } static void stop_mon_thread(struct fm_hci_t *hci) { int ret; ALOGV("%s++", __func__); if ((ret = pthread_kill(hci->mon_thread, SIGUSR1)) == FM_HC_STATUS_SUCCESS) { ALOGE("%s:pthread_join", __func__); if ((ret = pthread_join(hci->mon_thread, NULL)) != FM_HC_STATUS_SUCCESS) ALOGE("Error joining mon thread, error = %d (%s)", ret, strerror(ret)); } else { ALOGE("Error killing mon thread, error = %d (%s)", ret, strerror(ret)); } } static int start_rx_thread(struct fm_hci_t *hci) { int ret = FM_HC_STATUS_SUCCESS; ALOGV("%s++", __func__); ALOGD("%s: Starting the userial read thread....", __func__); if ((ret = pthread_create(&hci->rx_thread, NULL, \ hci_read_thread, hci)) != 0) { ALOGE("pthread_create failed! status = %d (%s)", ret, strerror(ret)); lib_running = 0; } return ret; } static void stop_rx_thread(struct fm_hci_t *hci) { int ret; ALOGV("%s++", __func__); if ((ret = pthread_kill(hci->rx_thread, SIGUSR1)) == FM_HC_STATUS_SUCCESS) { ALOGE("%s:pthread_join", __func__); if ((ret = pthread_join(hci->rx_thread, NULL)) != FM_HC_STATUS_SUCCESS) ALOGE("Error joining rx thread, error = %d (%s)", ret, strerror(ret)); } else { ALOGE("Error killing rx thread, error = %d (%s)", ret, strerror(ret)); } } static int power(struct fm_hci_t *hci, fm_power_state_t state) { int i,opcode,ret; int init_success = 0; char value[PROPERTY_VALUE_MAX] = {'\0'}; if (fm_hal_fd) { if (state) opcode = 2; else { opcode = 1; } ALOGI("%s:opcode: %x", LOG_TAG, opcode); ret = write(fm_hal_fd,&opcode, 1); if (ret < 0) { ALOGE("failed to write fm hal socket"); } else { ret = FM_HC_STATUS_SUCCESS; } } else { ALOGE("Connect to socket failed .."); ret = -1; } if (state == FM_RADIO_DISABLE) { for (i=0; i<10; i++) { property_get("wc_transport.fm_power_status", value, "0"); if (strcmp(value, "0") == 0) { init_success = 1; break; } else { usleep(WAIT_TIMEOUT); } } ALOGI("fm power OFF status:%d after %f seconds \n", init_success, 0.2*i); stop_fmhal_service(); } if (state == FM_RADIO_ENABLE) { for (i=0; i<10; i++) { property_get("wc_transport.fm_power_status", value, "0"); if (strcmp(value, "1") == 0) { init_success = 1; break; } else { usleep(WAIT_TIMEOUT); } } ALOGI("fm power ON status:%d after %f seconds \n", init_success, 0.2*i); } return ret; } #define CH_MAX 3 static int serial_port_init(struct fm_hci_t *hci) { int i, ret; int fd_array[CH_MAX]; for (int i = 0; i < CH_MAX; i++) fd_array[i] = -1; ALOGI("%s: Opening the TTy Serial port...", __func__); ret = hci->vendor->op(BT_VND_OP_FM_USERIAL_OPEN, &fd_array); if (fd_array[0] == -1) { ALOGE("%s unable to open TTY serial port", __func__); goto err; } hci->fd = fd_array[0]; return FM_HC_STATUS_SUCCESS; err: return FM_HC_STATUS_FAIL; } static void serial_port_close(struct fm_hci_t *hci) { //TODO: what if hci/fm_vnd_if is null.. need to take lock and check ALOGI("%s: Closing the TTy Serial port!!!", __func__); hci->vendor->op(BT_VND_OP_FM_USERIAL_CLOSE, NULL); hci->fd = -1; } static int enqueue_fm_tx_cmd(struct fm_hci_t *hci, struct fm_command_header_t *pbuf) { struct transmit_queue_t *element = (struct transmit_queue_t *) malloc(sizeof(struct transmit_queue_t)); if (!element) { ALOGI("Failed to allocate memory for element!!\n"); return FM_HC_STATUS_NOMEM; } element->hdr = pbuf; element->next = NULL; pthread_mutex_lock(&hci->tx_q_lock); if (!hci->first) { hci->last = hci->first = element; } else { hci->last->next = element; hci->last = element; } ALOGI("%s: FM-CMD ENQUEUED SUCCESSFULLY", __func__); pthread_mutex_unlock(&hci->tx_q_lock); return FM_HC_STATUS_SUCCESS; } int fm_hci_transmit(void *hci, struct fm_command_header_t *buf) { int status = FM_HC_STATUS_FAIL; if (!hci || !buf) { ALOGE("NULL input arguments"); return FM_HC_STATUS_NULL_POINTER; } if ((status = enqueue_fm_tx_cmd((struct fm_hci_t *)hci, buf)) == FM_HC_STATUS_SUCCESS) event_notification(hci, HC_EVENT_TX); return status; } void fm_hci_close(void *arg) { ALOGV("%s close fm userial ", __func__); struct fm_hci_t *hci = (struct fm_hci_t *)arg; if (!hci) { ALOGE("NULL arguments"); return; } event_notification(hci, HC_EVENT_EXIT); } int fm_hci_init(fm_hci_hal_t *hci_hal) { int ret = FM_HC_STATUS_FAIL; struct fm_hci_t *hci = NULL; ALOGV("++%s", __func__); if (!hci_hal || !hci_hal->hal) { ALOGE("NULL input argument"); return FM_HC_STATUS_NULL_POINTER; } hci = malloc(sizeof(struct fm_hci_t)); if (!hci) { ALOGE("Failed to malloc hci context"); return FM_HC_STATUS_NOMEM; } memset(hci, 0, sizeof(struct fm_hci_t)); lib_running = 1; ready_events = 0; hci->command_credits = 1; hci->fd = -1; pthread_mutex_init(&hci->tx_q_lock, NULL); pthread_mutex_init(&hci->credit_lock, NULL); pthread_mutex_init(&hci->event_lock, NULL); pthread_cond_init(&hci->event_cond, NULL); pthread_cond_init(&hci->cmd_credits_cond, NULL); start_fmhal_service(); fm_hal_fd = connect_to_local_fmsocket("fmhal_sock"); if (fm_hal_fd == -1) { ALOGI("FM hal service socket connect failed.."); goto err_socket; } ALOGI("fm_hal_fd = %d", fm_hal_fd); ret = vendor_init(hci); if (ret) goto err_vendor; ret = power(hci, FM_RADIO_ENABLE); if (ret) goto err_power; ret = serial_port_init(hci); if (ret) goto err_serial; ret = start_mon_thread(hci); if (ret) goto err_thread_mon; ret = start_tx_thread(hci); if (ret) goto err_thread_tx; ret = start_rx_thread(hci); if (ret) goto err_thread_rx; hci->cb = hci_hal->cb; hci->private_data = hci_hal->hal; hci_hal->hci = hci; ALOGD("--%s success", __func__); return FM_HC_STATUS_SUCCESS; err_thread_rx: stop_rx_thread(hci); err_thread_tx: stop_tx_thread(hci); err_thread_mon: stop_mon_thread(hci); err_serial: serial_port_close(hci); err_power: power(hci, FM_RADIO_DISABLE); err_vendor: vendor_close(hci); err_socket: stop_fmhal_service(); pthread_mutex_destroy(&hci->tx_q_lock); pthread_mutex_destroy(&hci->credit_lock); pthread_mutex_destroy(&hci->event_lock); pthread_cond_destroy(&hci->event_cond); pthread_cond_destroy(&hci->cmd_credits_cond); lib_running = 0; ready_events = 0; hci->command_credits = 0; free(hci); ALOGE("--%s fail", __func__); return ret; } static void fm_hci_exit(void *arg) { struct fm_hci_t *hci = (struct fm_hci_t *)arg; ALOGE("%s", __func__); lib_running = 0; ready_events = HC_EVENT_EXIT; hci->command_credits = 0; serial_port_close(hci); power(hci, FM_RADIO_DISABLE);//need to address this vendor_close(hci); pthread_cond_broadcast(&hci->event_cond); pthread_cond_broadcast(&hci->cmd_credits_cond); stop_rx_thread(hci); stop_tx_thread(hci); ALOGD("Tx, Rx Threads join done"); pthread_mutex_destroy(&hci->tx_q_lock); pthread_mutex_destroy(&hci->credit_lock); pthread_mutex_destroy(&hci->event_lock); pthread_cond_destroy(&hci->event_cond); pthread_cond_destroy(&hci->cmd_credits_cond); free(hci); hci = NULL; }