diff --git a/drivers/bluetooth/hci_smd.c b/drivers/bluetooth/hci_smd.c index 6030520460c5..9b60bdabc2fb 100644 --- a/drivers/bluetooth/hci_smd.c +++ b/drivers/bluetooth/hci_smd.c @@ -44,6 +44,19 @@ #define RX_Q_MONITOR (500) /* 500 milli second */ #define HCI_REGISTER_SET 0 +/* SSR state machine to take care of back to back SSR requests + * and handling the incomming BT on/off,Airplane mode toggling and + * also spuriour SMD open notification while one SSr is in progress + */ +#define STATE_SSR_ON 0x1 +#define STATE_SSR_START 0x02 +#define STATE_SSR_CHANNEL_OPEN_PENDING 0x04 +#define STATE_SSR_PENDING_INIT 0x08 +#define STATE_SSR_COMPLETE 0x00 +#define STATE_SSR_OFF STATE_SSR_COMPLETE + +static int ssr_state = STATE_SSR_OFF; + static int hcismd_set; static DEFINE_SEMAPHORE(hci_smd_enable); @@ -341,25 +354,47 @@ static void hci_smd_notify_event(void *data, unsigned int event) break; case SMD_EVENT_OPEN: BT_INFO("opening HCI-SMD channel :%s", EVENT_CHANNEL); - hci_smd_open(hdev); - open_worker = kzalloc(sizeof(*open_worker), GFP_ATOMIC); - if (!open_worker) { - BT_ERR("Out of memory"); - break; + BT_DBG("SSR state is : %x", ssr_state); + if ((ssr_state == STATE_SSR_OFF) || + (ssr_state == STATE_SSR_CHANNEL_OPEN_PENDING)) { + + hci_smd_open(hdev); + open_worker = kzalloc(sizeof(*open_worker), GFP_ATOMIC); + if (!open_worker) { + BT_ERR("Out of memory"); + break; + } + if (ssr_state == STATE_SSR_CHANNEL_OPEN_PENDING) { + ssr_state = STATE_SSR_PENDING_INIT; + BT_INFO("SSR state is : %x", ssr_state); + } + INIT_WORK(open_worker, hci_dev_smd_open); + schedule_work(open_worker); + } - INIT_WORK(open_worker, hci_dev_smd_open); - schedule_work(open_worker); break; case SMD_EVENT_CLOSE: BT_INFO("Closing HCI-SMD channel :%s", EVENT_CHANNEL); - hci_smd_close(hdev); - reset_worker = kzalloc(sizeof(*reset_worker), GFP_ATOMIC); - if (!reset_worker) { - BT_ERR("Out of memory"); - break; + BT_DBG("SSR state is : %x", ssr_state); + if ((ssr_state == STATE_SSR_OFF) || + (ssr_state == (STATE_SSR_PENDING_INIT))) { + + hci_smd_close(hdev); + reset_worker = kzalloc(sizeof(*reset_worker), + GFP_ATOMIC); + if (!reset_worker) { + BT_ERR("Out of memory"); + break; + } + ssr_state = STATE_SSR_ON; + BT_INFO("SSR state is : %x", ssr_state); + INIT_WORK(reset_worker, hci_dev_restart); + schedule_work(reset_worker); + + } else if (ssr_state & STATE_SSR_ON) { + BT_ERR("SSR state is : %x", ssr_state); } - INIT_WORK(reset_worker, hci_dev_restart); - schedule_work(reset_worker); + break; default: break; @@ -403,19 +438,37 @@ static int hci_smd_hci_register_dev(struct hci_smd_data *hsmd) { struct hci_dev *hdev; - hdev = hsmd->hdev; - if (test_and_set_bit(HCI_REGISTER_SET, &hsmd->flags)) { - BT_ERR("HCI device registered already"); + if (hsmd->hdev) + hdev = hsmd->hdev; + else { + BT_ERR("hdev is NULL"); return 0; - } else - BT_INFO("HCI device registration is starting"); - if (hci_register_dev(hdev) < 0) { - BT_ERR("Can't register HCI device"); - hci_free_dev(hdev); - hsmd->hdev = NULL; - clear_bit(HCI_REGISTER_SET, &hsmd->flags); - return -ENODEV; } + /* Allow the incomming SSR even the prev one at PENDING INIT STATE + * since clenup need to be started again from the beging and ignore + * or bypass the prev one + */ + if ((ssr_state == STATE_SSR_OFF) || + (ssr_state == STATE_SSR_PENDING_INIT)) { + + if (test_and_set_bit(HCI_REGISTER_SET, &hsmd->flags)) { + BT_ERR("HCI device registered already"); + return 0; + } else + BT_INFO("HCI device registration is starting"); + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + hsmd->hdev = NULL; + clear_bit(HCI_REGISTER_SET, &hsmd->flags); + return -ENODEV; + } + if (ssr_state == STATE_SSR_PENDING_INIT) { + ssr_state = STATE_SSR_COMPLETE; + BT_INFO("SSR state is : %x", ssr_state); + } + } else if (ssr_state) + BT_ERR("Registration called in invalid context"); return 0; } @@ -449,7 +502,10 @@ static int hci_smd_register_smd(struct hci_smd_data *hsmd) */ setup_timer(&hsmd->rx_q_timer, schedule_timer, (unsigned long) hsmd->hdev); - + if (ssr_state == STATE_SSR_START) { + ssr_state = STATE_SSR_CHANNEL_OPEN_PENDING; + BT_INFO("SSR state is : %x", ssr_state); + } /* Open the SMD Channel and device and register the callback function */ rc = smd_named_open_on_edge(EVENT_CHANNEL, SMD_APPS_WCNSS, &hsmd->event_channel, hdev, hci_smd_notify_event); @@ -478,21 +534,25 @@ static int hci_smd_register_smd(struct hci_smd_data *hsmd) static void hci_smd_deregister_dev(struct hci_smd_data *hsmd) { tasklet_kill(&hs.rx_task); - + if (ssr_state) + BT_DBG("SSR state is : %x", ssr_state); + /* Though the hci_smd driver is not registered with the hci + * need to close the opened channels as a part of cleaup + */ if (!test_and_clear_bit(HCI_REGISTER_SET, &hsmd->flags)) { BT_ERR("HCI device un-registered already"); - return; - } else + } else { BT_INFO("HCI device un-registration going on"); - if (hsmd->hdev) { - if (hci_unregister_dev(hsmd->hdev) < 0) - BT_ERR("Can't unregister HCI device %s", - hsmd->hdev->name); - hci_free_dev(hsmd->hdev); - hsmd->hdev = NULL; + if (hsmd->hdev) { + if (hci_unregister_dev(hsmd->hdev) < 0) + BT_ERR("Can't unregister HCI device %s", + hsmd->hdev->name); + + hci_free_dev(hsmd->hdev); + hsmd->hdev = NULL; + } } - smd_close(hs.event_channel); smd_close(hs.data_channel); @@ -513,23 +573,47 @@ static void hci_dev_restart(struct work_struct *worker) { down(&hci_smd_enable); restart_in_progress = 1; + BT_DBG("SSR state is : %x", ssr_state); + + if (ssr_state == STATE_SSR_ON) { + ssr_state = STATE_SSR_START; + BT_INFO("SSR state is : %x", ssr_state); + } else { + BT_ERR("restart triggered in wrong context"); + up(&hci_smd_enable); + kfree(worker); + return; + } hci_smd_deregister_dev(&hs); hci_smd_register_smd(&hs); up(&hci_smd_enable); kfree(worker); + } static void hci_dev_smd_open(struct work_struct *worker) { down(&hci_smd_enable); + if (ssr_state) + BT_DBG("SSR state is : %x", ssr_state); + + if ((ssr_state != STATE_SSR_OFF) && + (ssr_state != (STATE_SSR_PENDING_INIT))) { + up(&hci_smd_enable); + kfree(worker); + return; + } + if (restart_in_progress == 1) { /* Allow wcnss to initialize */ restart_in_progress = 0; msleep(10000); } + hci_smd_hci_register_dev(&hs); up(&hci_smd_enable); kfree(worker); + } static int hcismd_set_enable(const char *val, struct kernel_param *kp) @@ -545,14 +629,23 @@ static int hcismd_set_enable(const char *val, struct kernel_param *kp) if (ret) goto done; + /* Ignore the all incomming register de-register requests in case of + * SSR is in-progress + */ switch (hcismd_set) { case 1: - if (hs.hdev == NULL) + if ((hs.hdev == NULL) && (ssr_state == STATE_SSR_OFF)) hci_smd_register_smd(&hs); + else if (ssr_state) + BT_ERR("SSR is in progress,state is : %x", ssr_state); + break; case 0: - hci_smd_deregister_dev(&hs); + if (ssr_state == STATE_SSR_OFF) + hci_smd_deregister_dev(&hs); + else if (ssr_state) + BT_ERR("SSR is in progress,state is : %x", ssr_state); break; default: ret = -EFAULT; @@ -569,6 +662,7 @@ static int __init hci_smd_init(void) wake_lock_init(&hs.wake_lock_tx, WAKE_LOCK_SUSPEND, "msm_smd_Tx"); restart_in_progress = 0; + ssr_state = STATE_SSR_OFF; hs.hdev = NULL; return 0; }