blob: c38536a7f8e2b0915b120ee11c8553c7f98495a1 [file] [log] [blame]
/******************************************************************************
*
* 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 <assert.h>
#include <utils/Log.h>
#include "btsnoop.h"
#include "bt_hci_bdroid.h"
#include "bt_utils.h"
#include "bt_vendor_lib.h"
#include "hci.h"
#include "osi.h"
#include "thread.h"
#include "userial.h"
#include "utils.h"
#include "vendor.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 */
static const uint32_t EPILOG_TIMEOUT_MS = 3000;
/******************************************************************************
** Externs
******************************************************************************/
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);
/******************************************************************************
** Variables
******************************************************************************/
bt_hc_callbacks_t *bt_hc_cbacks = NULL;
tHCI_IF *p_hci_if;
volatile bool fwcfg_acked;
/******************************************************************************
** Local type definitions
******************************************************************************/
/* Host/Controller lib thread control block */
typedef struct
{
thread_t *worker_thread;
pthread_mutex_t worker_thread_lock;
bool epilog_timer_created;
timer_t epilog_timer_id;
} bt_hc_cb_t;
/******************************************************************************
** Static Variables
******************************************************************************/
static bt_hc_cb_t hc_cb;
static bool tx_cmd_pkts_pending = false;
static BUFFER_Q tx_q;
/******************************************************************************
** Functions
******************************************************************************/
static void event_preload(UNUSED_ATTR void *context) {
userial_open(USERIAL_PORT_1);
vendor_send_command(BT_VND_OP_FW_CFG, NULL);
}
static void event_postload(UNUSED_ATTR void *context) {
/* 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 = vendor_send_command(BT_VND_OP_SCO_CFG, NULL);
if (result == -1)
p_hci_if->get_acl_max_len();
}
static void event_tx(UNUSED_ATTR void *context) {
/*
* 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];
size_t sending_msg_count = 0;
int sending_hci_cmd_pkts_count = 0;
utils_lock();
HC_BT_HDR *p_next_msg = tx_q.p_first;
while (p_next_msg && sending_msg_count < ARRAY_SIZE(sending_msg_que))
{
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 ||
(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++;
}
HC_BT_HDR *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();
for(size_t i = 0; i < sending_msg_count; i++)
p_hci_if->send(sending_msg_que[i]);
if (tx_cmd_pkts_pending)
BTHCDBG("Used up Tx Cmd credits");
}
static void event_rx(UNUSED_ATTR void *context) {
#ifndef HCI_USE_MCT
p_hci_if->rcv();
if (tx_cmd_pkts_pending && num_hci_cmd_pkts > 0) {
// Got HCI Cmd credits from controller. Send whatever data
// we have in our tx queue. We can call |event_tx| directly
// here since we're already on the worker thread.
event_tx(NULL);
}
#endif
}
static void event_lpm_enable(UNUSED_ATTR void *context) {
lpm_enable(true);
}
static void event_lpm_disable(UNUSED_ATTR void *context) {
lpm_enable(false);
}
static void event_lpm_wake_device(UNUSED_ATTR void *context) {
lpm_wake_assert();
}
static void event_lpm_allow_sleep(UNUSED_ATTR void *context) {
lpm_allow_bt_device_sleep();
}
static void event_lpm_idle_timeout(UNUSED_ATTR void *context) {
lpm_wake_deassert();
}
static void event_epilog(UNUSED_ATTR void *context) {
vendor_send_command(BT_VND_OP_EPILOG, NULL);
}
static void event_tx_cmd(void *msg) {
HC_BT_HDR *p_msg = (HC_BT_HDR *)msg;
BTHCDBG("%s: p_msg: %p, event: 0x%x", __func__, p_msg, p_msg->event);
int event = p_msg->event & MSG_EVT_MASK;
int sub_event = p_msg->event & MSG_SUB_EVT_MASK;
if (event == MSG_CTRL_TO_HC_CMD && sub_event == BT_HC_AUDIO_STATE) {
vendor_send_command(BT_VND_OP_SET_AUDIO_STATE, p_msg->data);
} else {
ALOGW("%s (event: 0x%x, sub_event: 0x%x) not supported", __func__, event, sub_event);
}
bt_hc_cbacks->dealloc(msg);
}
void bthc_rx_ready(void) {
pthread_mutex_lock(&hc_cb.worker_thread_lock);
if (hc_cb.worker_thread)
thread_post(hc_cb.worker_thread, event_rx, NULL);
pthread_mutex_unlock(&hc_cb.worker_thread_lock);
}
void bthc_tx(HC_BT_HDR *buf) {
pthread_mutex_lock(&hc_cb.worker_thread_lock);
if (hc_cb.worker_thread) {
if (buf)
utils_enqueue(&tx_q, buf);
thread_post(hc_cb.worker_thread, event_tx, NULL);
}
pthread_mutex_unlock(&hc_cb.worker_thread_lock);
}
void bthc_idle_timeout(void) {
pthread_mutex_lock(&hc_cb.worker_thread_lock);
if (hc_cb.worker_thread)
thread_post(hc_cb.worker_thread, event_lpm_idle_timeout, NULL);
pthread_mutex_unlock(&hc_cb.worker_thread_lock);
}
/*******************************************************************************
**
** Function epilog_wait_timeout
**
** Description Timeout thread of epilog watchdog timer
**
** Returns None
**
*******************************************************************************/
static void epilog_wait_timeout(UNUSED_ATTR union sigval arg)
{
ALOGI("...epilog_wait_timeout...");
thread_free(hc_cb.worker_thread);
pthread_mutex_lock(&hc_cb.worker_thread_lock);
hc_cb.worker_thread = NULL;
pthread_mutex_unlock(&hc_cb.worker_thread_lock);
}
/*******************************************************************************
**
** 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 = true;
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 = false;
}
}
/*****************************************************************************
**
** BLUETOOTH HOST/CONTROLLER INTERFACE LIBRARY FUNCTIONS
**
*****************************************************************************/
static int init(const bt_hc_callbacks_t* p_cb, unsigned char *local_bdaddr)
{
int result;
ALOGI("init");
if (p_cb == NULL)
{
ALOGE("init failed with no user callbacks!");
return BT_HC_STATUS_FAIL;
}
hc_cb.epilog_timer_created = false;
fwcfg_acked = false;
pthread_mutex_init(&hc_cb.worker_thread_lock, NULL);
/* store reference to user callbacks */
bt_hc_cbacks = (bt_hc_callbacks_t *) p_cb;
vendor_open(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 (hc_cb.worker_thread)
{
ALOGW("init has been called repeatedly without calling cleanup ?");
}
hc_cb.worker_thread = thread_new("bt_hc_worker");
if (!hc_cb.worker_thread) {
ALOGE("%s unable to create worker thread.", __func__);
return BT_HC_STATUS_FAIL;
}
// TODO(sharvil): increase thread priority (raise_priority_a2dp)
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;
vendor_send_command(BT_VND_OP_POWER_CTRL, &pwr_state);
}
/** Configure low power mode wake state */
static int lpm(bt_hc_low_power_event_t event)
{
switch (event)
{
case BT_HC_LPM_DISABLE:
thread_post(hc_cb.worker_thread, event_lpm_disable, NULL);
break;
case BT_HC_LPM_ENABLE:
thread_post(hc_cb.worker_thread, event_lpm_enable, NULL);
break;
case BT_HC_LPM_WAKE_ASSERT:
thread_post(hc_cb.worker_thread, event_lpm_wake_device, NULL);
break;
case BT_HC_LPM_WAKE_DEASSERT:
thread_post(hc_cb.worker_thread, event_lpm_allow_sleep, NULL);
break;
}
return BT_HC_STATUS_SUCCESS;
}
/** Called prior to stack initialization */
static void preload(UNUSED_ATTR TRANSAC transac) {
BTHCDBG("preload");
thread_post(hc_cb.worker_thread, event_preload, NULL);
}
/** Called post stack initialization */
static void postload(UNUSED_ATTR TRANSAC transac) {
BTHCDBG("postload");
thread_post(hc_cb.worker_thread, event_postload, NULL);
}
/** Transmit frame */
static int transmit_buf(TRANSAC transac, UNUSED_ATTR char *p_buf, UNUSED_ATTR int len) {
bthc_tx((HC_BT_HDR *)transac);
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)
btsnoop_close();
else if (p_path != NULL)
btsnoop_open(p_path);
return BT_HC_STATUS_SUCCESS;
}
/** sends command HC controller to configure platform specific behaviour */
static int tx_hc_cmd(TRANSAC transac, char *p_buf, int len) {
BTHCDBG("tx_hc_cmd: transac %p", transac);
if (!transac)
return BT_HC_STATUS_FAIL;
thread_post(hc_cb.worker_thread, event_tx_cmd, transac);
return BT_HC_STATUS_SUCCESS;
}
/** Closes the interface */
static void cleanup( void )
{
BTHCDBG("cleanup");
if (hc_cb.worker_thread)
{
if (fwcfg_acked)
{
epilog_wait_timer();
thread_post(hc_cb.worker_thread, event_epilog, NULL);
}
thread_free(hc_cb.worker_thread);
pthread_mutex_lock(&hc_cb.worker_thread_lock);
hc_cb.worker_thread = NULL;
pthread_mutex_unlock(&hc_cb.worker_thread_lock);
if (hc_cb.epilog_timer_created)
{
timer_delete(hc_cb.epilog_timer_id);
hc_cb.epilog_timer_created = false;
}
}
lpm_cleanup();
userial_close();
p_hci_if->cleanup();
utils_cleanup();
set_power(BT_VND_PWR_OFF);
vendor_close();
pthread_mutex_destroy(&hc_cb.worker_thread_lock);
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,
logging,
cleanup,
tx_hc_cmd,
};
/*******************************************************************************
**
** 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;
}