/****************************************************************************** * * Copyright (C) 2009-2012 Broadcom Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ /****************************************************************************** * * Filename: bt_hci_bdroid.c * * Description: Bluedroid Bluetooth Host/Controller interface library * implementation * ******************************************************************************/ #define LOG_TAG "bt_hci_bdroid" #include <utils/Log.h> #include <pthread.h> #include "bt_hci_bdroid.h" #include "bt_vendor_lib.h" #include "utils.h" #include "hci.h" #include "userial.h" #include "bt_utils.h" #include <sys/prctl.h> #ifndef BTHC_DBG #define BTHC_DBG FALSE #endif #if (BTHC_DBG == TRUE) #define BTHCDBG(param, ...) {ALOGD(param, ## __VA_ARGS__);} #else #define BTHCDBG(param, ...) {} #endif /* Vendor epilog process timeout period */ #ifndef EPILOG_TIMEOUT_MS #define EPILOG_TIMEOUT_MS 3000 // 3 seconds #endif /****************************************************************************** ** Externs ******************************************************************************/ extern bt_vendor_interface_t *bt_vnd_if; extern int num_hci_cmd_pkts; void lpm_init(void); void lpm_cleanup(void); void lpm_enable(uint8_t turn_on); void lpm_wake_deassert(void); void lpm_allow_bt_device_sleep(void); void lpm_wake_assert(void); void init_vnd_if(unsigned char *local_bdaddr); void btsnoop_open(char *p_path); void btsnoop_close(void); /****************************************************************************** ** Variables ******************************************************************************/ bt_hc_callbacks_t *bt_hc_cbacks = NULL; BUFFER_Q tx_q; tHCI_IF *p_hci_if; volatile uint8_t fwcfg_acked; /****************************************************************************** ** Local type definitions ******************************************************************************/ /* Host/Controller lib thread control block */ typedef struct { pthread_t worker_thread; pthread_mutex_t mutex; pthread_cond_t cond; uint8_t epilog_timer_created; timer_t epilog_timer_id; } bt_hc_cb_t; /****************************************************************************** ** Static Variables ******************************************************************************/ static bt_hc_cb_t hc_cb; static volatile uint8_t lib_running = 0; static volatile uint16_t ready_events = 0; static volatile uint8_t tx_cmd_pkts_pending = FALSE; /****************************************************************************** ** Functions ******************************************************************************/ static void *bt_hc_worker_thread(void *arg); void bthc_signal_event(uint16_t event) { pthread_mutex_lock(&hc_cb.mutex); ready_events |= event; pthread_cond_signal(&hc_cb.cond); pthread_mutex_unlock(&hc_cb.mutex); } /******************************************************************************* ** ** Function epilog_wait_timeout ** ** Description Timeout thread of epilog watchdog timer ** ** Returns None ** *******************************************************************************/ static void epilog_wait_timeout(union sigval arg) { ALOGI("...epilog_wait_timeout..."); bthc_signal_event(HC_EVENT_EXIT); } /******************************************************************************* ** ** Function epilog_wait_timer ** ** Description Launch epilog watchdog timer ** ** Returns None ** *******************************************************************************/ static void epilog_wait_timer(void) { int status; struct itimerspec ts; struct sigevent se; uint32_t timeout_ms = EPILOG_TIMEOUT_MS; se.sigev_notify = SIGEV_THREAD; se.sigev_value.sival_ptr = &hc_cb.epilog_timer_id; se.sigev_notify_function = epilog_wait_timeout; se.sigev_notify_attributes = NULL; status = timer_create(CLOCK_MONOTONIC, &se, &hc_cb.epilog_timer_id); if (status == 0) { hc_cb.epilog_timer_created = 1; ts.it_value.tv_sec = timeout_ms/1000; ts.it_value.tv_nsec = 1000000*(timeout_ms%1000); ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; status = timer_settime(hc_cb.epilog_timer_id, 0, &ts, 0); if (status == -1) ALOGE("Failed to fire epilog watchdog timer"); } else { ALOGE("Failed to create epilog watchdog timer"); hc_cb.epilog_timer_created = 0; } } /***************************************************************************** ** ** BLUETOOTH HOST/CONTROLLER INTERFACE LIBRARY FUNCTIONS ** *****************************************************************************/ static int init(const bt_hc_callbacks_t* p_cb, unsigned char *local_bdaddr) { pthread_attr_t thread_attr; struct sched_param param; int policy, result; ALOGI("init"); if (p_cb == NULL) { ALOGE("init failed with no user callbacks!"); return BT_HC_STATUS_FAIL; } hc_cb.epilog_timer_created = 0; fwcfg_acked = FALSE; /* store reference to user callbacks */ bt_hc_cbacks = (bt_hc_callbacks_t *) p_cb; init_vnd_if(local_bdaddr); utils_init(); #ifdef HCI_USE_MCT extern tHCI_IF hci_mct_func_table; p_hci_if = &hci_mct_func_table; #else extern tHCI_IF hci_h4_func_table; p_hci_if = &hci_h4_func_table; #endif p_hci_if->init(); userial_init(); lpm_init(); utils_queue_init(&tx_q); if (lib_running) { ALOGW("init has been called repeatedly without calling cleanup ?"); } lib_running = 1; ready_events = 0; pthread_mutex_init(&hc_cb.mutex, NULL); pthread_cond_init(&hc_cb.cond, NULL); pthread_attr_init(&thread_attr); if (pthread_create(&hc_cb.worker_thread, &thread_attr, \ bt_hc_worker_thread, NULL) != 0) { ALOGE("pthread_create failed!"); lib_running = 0; return BT_HC_STATUS_FAIL; } if(pthread_getschedparam(hc_cb.worker_thread, &policy, ¶m)==0) { policy = BTHC_LINUX_BASE_POLICY; #if (BTHC_LINUX_BASE_POLICY!=SCHED_NORMAL) param.sched_priority = BTHC_MAIN_THREAD_PRIORITY; #endif result = pthread_setschedparam(hc_cb.worker_thread, policy, ¶m); if (result != 0) { ALOGW("libbt-hci init: pthread_setschedparam failed (%s)", \ strerror(result)); } } return BT_HC_STATUS_SUCCESS; } /** Chip power control */ static void set_power(bt_hc_chip_power_state_t state) { int pwr_state; BTHCDBG("set_power %d", state); /* Calling vendor-specific part */ pwr_state = (state == BT_HC_CHIP_PWR_ON) ? BT_VND_PWR_ON : BT_VND_PWR_OFF; if (bt_vnd_if) bt_vnd_if->op(BT_VND_OP_POWER_CTRL, &pwr_state); else ALOGE("vendor lib is missing!"); } /** Configure low power mode wake state */ static int lpm(bt_hc_low_power_event_t event) { uint8_t status = TRUE; switch (event) { case BT_HC_LPM_DISABLE: bthc_signal_event(HC_EVENT_LPM_DISABLE); break; case BT_HC_LPM_ENABLE: bthc_signal_event(HC_EVENT_LPM_ENABLE); break; case BT_HC_LPM_WAKE_ASSERT: bthc_signal_event(HC_EVENT_LPM_WAKE_DEVICE); break; case BT_HC_LPM_WAKE_DEASSERT: bthc_signal_event(HC_EVENT_LPM_ALLOW_SLEEP); break; } return(status == TRUE) ? BT_HC_STATUS_SUCCESS : BT_HC_STATUS_FAIL; } /** Called prio to stack initialization */ static void preload(TRANSAC transac) { BTHCDBG("preload"); bthc_signal_event(HC_EVENT_PRELOAD); } /** Called post stack initialization */ static void postload(TRANSAC transac) { BTHCDBG("postload"); bthc_signal_event(HC_EVENT_POSTLOAD); } /** Transmit frame */ static int transmit_buf(TRANSAC transac, char *p_buf, int len) { utils_enqueue(&tx_q, (void *) transac); bthc_signal_event(HC_EVENT_TX); return BT_HC_STATUS_SUCCESS; } /** Controls receive flow */ static int set_rxflow(bt_rx_flow_state_t state) { BTHCDBG("set_rxflow %d", state); userial_ioctl(\ ((state == BT_RXFLOW_ON) ? USERIAL_OP_RXFLOW_ON : USERIAL_OP_RXFLOW_OFF), \ NULL); return BT_HC_STATUS_SUCCESS; } /** Controls HCI logging on/off */ static int logging(bt_hc_logging_state_t state, char *p_path) { BTHCDBG("logging %d", state); if (state == BT_HC_LOGGING_ON) { if (p_path != NULL) btsnoop_open(p_path); } else { btsnoop_close(); } return BT_HC_STATUS_SUCCESS; } /** Closes the interface */ static void cleanup( void ) { BTHCDBG("cleanup"); if (lib_running) { if (fwcfg_acked == TRUE) { epilog_wait_timer(); bthc_signal_event(HC_EVENT_EPILOG); } else { bthc_signal_event(HC_EVENT_EXIT); } pthread_join(hc_cb.worker_thread, NULL); if (hc_cb.epilog_timer_created == 1) { timer_delete(hc_cb.epilog_timer_id); hc_cb.epilog_timer_created = 0; } } lib_running = 0; lpm_cleanup(); userial_close(); p_hci_if->cleanup(); utils_cleanup(); /* Calling vendor-specific part */ if (bt_vnd_if) bt_vnd_if->cleanup(); fwcfg_acked = FALSE; bt_hc_cbacks = NULL; } static const bt_hc_interface_t bluetoothHCLibInterface = { sizeof(bt_hc_interface_t), init, set_power, lpm, preload, postload, transmit_buf, set_rxflow, logging, cleanup }; /******************************************************************************* ** ** Function bt_hc_worker_thread ** ** Description Mian worker thread ** ** Returns void * ** *******************************************************************************/ static void *bt_hc_worker_thread(void *arg) { uint16_t events; HC_BT_HDR *p_msg, *p_next_msg; ALOGI("bt_hc_worker_thread started"); prctl(PR_SET_NAME, (unsigned long)"bt_hc_worker", 0, 0, 0); tx_cmd_pkts_pending = FALSE; raise_priority_a2dp(TASK_HIGH_HCI_WORKER); while (lib_running) { pthread_mutex_lock(&hc_cb.mutex); while (ready_events == 0) { pthread_cond_wait(&hc_cb.cond, &hc_cb.mutex); } events = ready_events; ready_events = 0; pthread_mutex_unlock(&hc_cb.mutex); #ifndef HCI_USE_MCT if (events & HC_EVENT_RX) { p_hci_if->rcv(); if ((tx_cmd_pkts_pending == TRUE) && (num_hci_cmd_pkts > 0)) { /* Got HCI Cmd Credits from Controller. * Prepare to send prior pending Cmd packets in the * following HC_EVENT_TX session. */ events |= HC_EVENT_TX; } } #endif if (events & HC_EVENT_PRELOAD) { userial_open(USERIAL_PORT_1); /* Calling vendor-specific part */ if (bt_vnd_if) { bt_vnd_if->op(BT_VND_OP_FW_CFG, NULL); } else { if (bt_hc_cbacks) bt_hc_cbacks->preload_cb(NULL, BT_HC_PRELOAD_FAIL); } } if (events & HC_EVENT_POSTLOAD) { /* Start from SCO related H/W configuration, if SCO configuration * is required. Then, follow with reading requests of getting * ACL data length for both BR/EDR and LE. */ int result = -1; /* Calling vendor-specific part */ if (bt_vnd_if) result = bt_vnd_if->op(BT_VND_OP_SCO_CFG, NULL); if (result == -1) p_hci_if->get_acl_max_len(); } if (events & HC_EVENT_TX) { /* * We will go through every packets in the tx queue. * Fine to clear tx_cmd_pkts_pending. */ tx_cmd_pkts_pending = FALSE; HC_BT_HDR * sending_msg_que[64]; int sending_msg_count = 0; int sending_hci_cmd_pkts_count = 0; utils_lock(); p_next_msg = tx_q.p_first; while (p_next_msg && sending_msg_count < (int)sizeof(sending_msg_que)/sizeof(sending_msg_que[0])) { if ((p_next_msg->event & MSG_EVT_MASK)==MSG_STACK_TO_HC_HCI_CMD) { /* * if we have used up controller's outstanding HCI command * credits (normally is 1), skip all HCI command packets in * the queue. * The pending command packets will be sent once controller * gives back us credits through CommandCompleteEvent or * CommandStatusEvent. */ if ((tx_cmd_pkts_pending == TRUE) || (sending_hci_cmd_pkts_count >= num_hci_cmd_pkts)) { tx_cmd_pkts_pending = TRUE; p_next_msg = utils_getnext(p_next_msg); continue; } sending_hci_cmd_pkts_count++; } p_msg = p_next_msg; p_next_msg = utils_getnext(p_msg); utils_remove_from_queue_unlocked(&tx_q, p_msg); sending_msg_que[sending_msg_count++] = p_msg; } utils_unlock(); int i; for(i = 0; i < sending_msg_count; i++) p_hci_if->send(sending_msg_que[i]); if (tx_cmd_pkts_pending == TRUE) BTHCDBG("Used up Tx Cmd credits"); } if (events & HC_EVENT_LPM_ENABLE) { lpm_enable(TRUE); } if (events & HC_EVENT_LPM_DISABLE) { lpm_enable(FALSE); } if (events & HC_EVENT_LPM_IDLE_TIMEOUT) { lpm_wake_deassert(); } if (events & HC_EVENT_LPM_ALLOW_SLEEP) { lpm_allow_bt_device_sleep(); } if (events & HC_EVENT_LPM_WAKE_DEVICE) { lpm_wake_assert(); } if (events & HC_EVENT_EPILOG) { /* Calling vendor-specific part */ if (bt_vnd_if) bt_vnd_if->op(BT_VND_OP_EPILOG, NULL); else break; // equivalent to HC_EVENT_EXIT } if (events & HC_EVENT_EXIT) break; } ALOGI("bt_hc_worker_thread exiting"); lib_running = 0; pthread_exit(NULL); return NULL; // compiler friendly } /******************************************************************************* ** ** Function bt_hc_get_interface ** ** Description Caller calls this function to get API instance ** ** Returns API table ** *******************************************************************************/ const bt_hc_interface_t *bt_hc_get_interface(void) { return &bluetoothHCLibInterface; }