From 08188f95f81aa4c716184eb40e08873e810ad089 Mon Sep 17 00:00:00 2001 From: Adam Pigg Date: Sat, 1 Jul 2017 09:04:43 +0100 Subject: [PATCH] BLUETOOTH: Add and port the BT HCI SMD driver Change-Id: I19e3cd2c7043d192590e4f81af6f8a18c9513d5e --- drivers/bluetooth/Kconfig | 10 + drivers/bluetooth/Makefile | 1 + drivers/bluetooth/hci_smd.c | 678 +++++++++++++++++++++++++++++++ include/net/bluetooth/hci.h | 1 + include/net/bluetooth/hci_core.h | 3 + 5 files changed, 693 insertions(+) create mode 100644 drivers/bluetooth/hci_smd.c diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index d3d007014679..95903015e45e 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -2,6 +2,16 @@ menu "Bluetooth device drivers" depends on BT +config BT_HCISMD + tristate "HCI SMD driver" + help + Bluetooth HCI SMD driver. + This driver is required if you want to use Bluetoth device with + SMD interface. + + Say Y here to compile support for Bluetooth USB devices into the + kernel or say M to compile is as a module (hci_smd). + config BT_HCIBTUSB tristate "HCI USB driver" depends on USB diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index d2e424bd273d..008ba6830e43 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -2,6 +2,7 @@ # Makefile for the Linux Bluetooth HCI device drivers. # +obj-$(CONFIG_BT_HCISMD) += hci_smd.o obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o obj-$(CONFIG_BT_HCIUART) += hci_uart.o obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o diff --git a/drivers/bluetooth/hci_smd.c b/drivers/bluetooth/hci_smd.c new file mode 100644 index 000000000000..8e9a7505ff1f --- /dev/null +++ b/drivers/bluetooth/hci_smd.c @@ -0,0 +1,678 @@ +/* + * HCI_SMD (HCI Shared Memory Driver) is Qualcomm's Shared memory driver + * for the BT HCI protocol. + * + * Copyright (c) 2000-2001, 2011-2012 The Linux Foundation. All rights reserved. + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2006 Marcel Holtmann + * + * This file is based on drivers/bluetooth/hci_vhci.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EVENT_CHANNEL "APPS_RIVA_BT_CMD" +#define DATA_CHANNEL "APPS_RIVA_BT_ACL" +/* release wakelock in 500ms, not immediately, because higher layers + * don't always take wakelocks when they should + * This is derived from the implementation for UART transport + */ + +#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); + +static int restart_in_progress; + +static int hcismd_set_enable(const char *val, struct kernel_param *kp); +module_param_call(hcismd_set, hcismd_set_enable, NULL, &hcismd_set, 0644); + +static void hci_dev_smd_open(struct work_struct *worker); +static void hci_dev_restart(struct work_struct *worker); + +struct hci_smd_data { + struct hci_dev *hdev; + unsigned long flags; + struct smd_channel *event_channel; + struct smd_channel *data_channel; + struct wake_lock wake_lock_tx; + struct wake_lock wake_lock_rx; + struct timer_list rx_q_timer; + struct tasklet_struct rx_task; +}; +static struct hci_smd_data hs; + +/* Rx queue monitor timer function */ +static int is_rx_q_empty(unsigned long arg) +{ + struct hci_dev *hdev = (struct hci_dev *) arg; + struct sk_buff_head *list_ = &hdev->rx_q; + struct sk_buff *list = ((struct sk_buff *)list_)->next; + BT_DBG("%s Rx timer triggered", hdev->name); + + if (list == (struct sk_buff *)list_) { + BT_DBG("%s RX queue empty", hdev->name); + return 1; + } else{ + BT_DBG("%s RX queue not empty", hdev->name); + return 0; + } +} + +static void release_lock(void) +{ + struct hci_smd_data *hsmd = &hs; + BT_DBG("Releasing Rx Lock"); + if (is_rx_q_empty((unsigned long)hsmd->hdev) && + wake_lock_active(&hs.wake_lock_rx)) + wake_unlock(&hs.wake_lock_rx); +} + +/* Rx timer callback function */ +static void schedule_timer(unsigned long arg) +{ + struct hci_dev *hdev = (struct hci_dev *) arg; + struct hci_smd_data *hsmd = &hs; + BT_DBG("%s Schedule Rx timer", hdev->name); + + if (is_rx_q_empty(arg) && wake_lock_active(&hs.wake_lock_rx)) { + BT_DBG("%s RX queue empty", hdev->name); + /* + * Since the queue is empty, its ideal + * to release the wake lock on Rx + */ + wake_unlock(&hs.wake_lock_rx); + } else{ + BT_DBG("%s RX queue not empty", hdev->name); + /* + * Restart the timer to monitor whether the Rx queue is + * empty for releasing the Rx wake lock + */ + mod_timer(&hsmd->rx_q_timer, + jiffies + msecs_to_jiffies(RX_Q_MONITOR)); + } +} + +static int hci_smd_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &hdev->flags); + return 0; +} + + +static int hci_smd_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + else + return -EPERM; +} + +#if 0 +static void hci_smd_destruct(struct hci_dev *hdev) +{ + if (NULL != hdev->driver_data) + kfree(hdev->driver_data); +} +#endif + +static void hci_smd_recv_data(void) +{ + int len = 0; + int rc = 0; + struct sk_buff *skb = NULL; + struct hci_smd_data *hsmd = &hs; + wake_lock(&hs.wake_lock_rx); + + len = smd_read_avail(hsmd->data_channel); + if (len > HCI_MAX_FRAME_SIZE) { + BT_ERR("Frame larger than the allowed size, flushing frame"); + smd_read(hsmd->data_channel, NULL, len); + goto out_data; + } + + if (len <= 0) + goto out_data; + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + BT_ERR("Error in allocating socket buffer"); + smd_read(hsmd->data_channel, NULL, len); + goto out_data; + } + + rc = smd_read(hsmd->data_channel, skb_put(skb, len), len); + if (rc < len) { + BT_ERR("Error in reading from the channel"); + goto out_data; + } + + skb->dev = (void *)hsmd->hdev; + bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; + skb_orphan(skb); + + rc = hci_recv_frame(hsmd->hdev, skb); + if (rc < 0) { + BT_ERR("Error in passing the packet to HCI Layer"); + /* + * skb is getting freed in hci_recv_frame, making it + * to null to avoid multiple access + */ + skb = NULL; + goto out_data; + } + + /* + * Start the timer to monitor whether the Rx queue is + * empty for releasing the Rx wake lock + */ + BT_DBG("Rx Timer is starting"); + mod_timer(&hsmd->rx_q_timer, + jiffies + msecs_to_jiffies(RX_Q_MONITOR)); + +out_data: + release_lock(); + if (rc) + kfree_skb(skb); +} + +static void hci_smd_recv_event(void) +{ + int len = 0; + int rc = 0; + struct sk_buff *skb = NULL; + struct hci_smd_data *hsmd = &hs; + wake_lock(&hs.wake_lock_rx); + + len = smd_read_avail(hsmd->event_channel); + if (len > HCI_MAX_FRAME_SIZE) { + BT_ERR("Frame larger than the allowed size, flushing frame"); + rc = smd_read(hsmd->event_channel, NULL, len); + goto out_event; + } + + while (len > 0) { + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + BT_ERR("Error in allocating socket buffer"); + smd_read(hsmd->event_channel, NULL, len); + goto out_event; + } + + rc = smd_read(hsmd->event_channel, skb_put(skb, len), len); + if (rc < len) { + BT_ERR("Error in reading from the event channel"); + goto out_event; + } + + skb->dev = (void *)hsmd->hdev; + bt_cb(skb)->pkt_type = HCI_EVENT_PKT; + + skb_orphan(skb); + + rc = hci_recv_frame(hsmd->hdev, skb); + if (rc < 0) { + BT_ERR("Error in passing the packet to HCI Layer"); + /* + * skb is getting freed in hci_recv_frame, making it + * to null to avoid multiple access + */ + skb = NULL; + goto out_event; + } + + len = smd_read_avail(hsmd->event_channel); + /* + * Start the timer to monitor whether the Rx queue is + * empty for releasing the Rx wake lock + */ + BT_DBG("Rx Timer is starting"); + mod_timer(&hsmd->rx_q_timer, + jiffies + msecs_to_jiffies(RX_Q_MONITOR)); + } +out_event: + release_lock(); + if (rc) + kfree_skb(skb); +} + +static int hci_smd_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + int len; + int avail; + int ret = 0; + wake_lock(&hs.wake_lock_tx); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + avail = smd_write_avail(hs.event_channel); + if (!avail) { + BT_ERR("No space available for smd frame"); + ret = -ENOSPC; + } + len = smd_write(hs.event_channel, skb->data, skb->len); + if (len < skb->len) { + BT_ERR("Failed to write Command %d", len); + ret = -ENODEV; + } + break; + case HCI_ACLDATA_PKT: + case HCI_SCODATA_PKT: + avail = smd_write_avail(hs.data_channel); + if (!avail) { + BT_ERR("No space available for smd frame"); + ret = -ENOSPC; + } + len = smd_write(hs.data_channel, skb->data, skb->len); + if (len < skb->len) { + BT_ERR("Failed to write Data %d", len); + ret = -ENODEV; + } + break; + default: + BT_ERR("Uknown packet type"); + ret = -ENODEV; + break; + } + + kfree_skb(skb); + wake_unlock(&hs.wake_lock_tx); + return ret; +} + +static void hci_smd_rx(unsigned long arg) +{ + struct hci_smd_data *hsmd = &hs; + + while ((smd_read_avail(hsmd->event_channel) > 0) || + (smd_read_avail(hsmd->data_channel) > 0)) { + hci_smd_recv_event(); + hci_smd_recv_data(); + } +} + +static void hci_smd_notify_event(void *data, unsigned int event) +{ + struct hci_dev *hdev = hs.hdev; + struct hci_smd_data *hsmd = &hs; + struct work_struct *reset_worker; + struct work_struct *open_worker; + + int len = 0; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + len = smd_read_avail(hsmd->event_channel); + if (len > 0) + tasklet_hi_schedule(&hs.rx_task); + else if (len < 0) + BT_ERR("Failed to read event from smd %d", len); + + break; + case SMD_EVENT_OPEN: + BT_INFO("opening HCI-SMD channel :%s", EVENT_CHANNEL); + 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); + + } + break; + case SMD_EVENT_CLOSE: + BT_INFO("Closing HCI-SMD channel :%s", EVENT_CHANNEL); + 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); + } + + break; + default: + break; + } +} + +static void hci_smd_notify_data(void *data, unsigned int event) +{ + struct hci_dev *hdev = hs.hdev; + struct hci_smd_data *hsmd = &hs; + int len = 0; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + len = smd_read_avail(hsmd->data_channel); + if (len > 0) + tasklet_hi_schedule(&hs.rx_task); + else if (len < 0) + BT_ERR("Failed to read data from smd %d", len); + break; + case SMD_EVENT_OPEN: + BT_INFO("opening HCI-SMD channel :%s", DATA_CHANNEL); + hci_smd_open(hdev); + break; + case SMD_EVENT_CLOSE: + BT_INFO("Closing HCI-SMD channel :%s", DATA_CHANNEL); + hci_smd_close(hdev); + break; + default: + break; + } + +} + +static int hci_smd_hci_register_dev(struct hci_smd_data *hsmd) +{ + struct hci_dev *hdev; + + if (hsmd->hdev) + hdev = hsmd->hdev; + else { + BT_ERR("hdev is NULL"); + return 0; + } + /* 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; +} + +static int hci_smd_register_smd(struct hci_smd_data *hsmd) +{ + struct hci_dev *hdev; + int rc; + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hsmd->hdev = hdev; + hdev->bus = HCI_SMD; + hdev->driver_data = NULL; + hdev->open = hci_smd_open; + hdev->close = hci_smd_close; + hdev->send = hci_smd_send_frame; + //hdev->destruct = hci_smd_destruct; + hdev->owner = THIS_MODULE; + + + tasklet_init(&hsmd->rx_task, + hci_smd_rx, (unsigned long) hsmd); + /* + * Setup the timer to monitor whether the Rx queue is empty, + * to control the wake lock release + */ + 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); + if (rc < 0) { + BT_ERR("Cannot open the command channel"); + hci_free_dev(hdev); + hsmd->hdev = NULL; + return -ENODEV; + } + + rc = smd_named_open_on_edge(DATA_CHANNEL, SMD_APPS_WCNSS, + &hsmd->data_channel, hdev, hci_smd_notify_data); + if (rc < 0) { + BT_ERR("Failed to open the Data channel"); + hci_free_dev(hdev); + hsmd->hdev = NULL; + return -ENODEV; + } + + /* Disable the read interrupts on the channel */ + smd_disable_read_intr(hsmd->event_channel); + smd_disable_read_intr(hsmd->data_channel); + return 0; +} + +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"); + } else { + BT_INFO("HCI device un-registration going on"); + + if (hsmd->hdev) { + hci_unregister_dev(hsmd->hdev) ; + hci_free_dev(hsmd->hdev); + hsmd->hdev = NULL; + } + } + smd_close(hs.event_channel); + smd_close(hs.data_channel); + + if (wake_lock_active(&hs.wake_lock_rx)) + wake_unlock(&hs.wake_lock_rx); + if (wake_lock_active(&hs.wake_lock_tx)) + wake_unlock(&hs.wake_lock_tx); + + /*Destroy the timer used to monitor the Rx queue for emptiness */ + if (hs.rx_q_timer.function) { + del_timer_sync(&hs.rx_q_timer); + hs.rx_q_timer.function = NULL; + hs.rx_q_timer.data = 0; + } +} + +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) +{ + int ret = 0; + + pr_err("hcismd_set_enable %d", hcismd_set); + + down(&hci_smd_enable); + + ret = param_set_int(val, 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) && (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: + 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; + } + +done: + up(&hci_smd_enable); + return ret; +} +static int __init hci_smd_init(void) +{ + wake_lock_init(&hs.wake_lock_rx, WAKE_LOCK_SUSPEND, + "msm_smd_Rx"); + 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; +} +module_init(hci_smd_init); + +static void __exit hci_smd_exit(void) +{ + wake_lock_destroy(&hs.wake_lock_rx); + wake_lock_destroy(&hs.wake_lock_tx); +} +module_exit(hci_smd_exit); + +MODULE_AUTHOR("Ankur Nandwani "); +MODULE_DESCRIPTION("Bluetooth SMD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index cb8d4b314bcf..84e041e99f78 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -59,6 +59,7 @@ #define HCI_RS232 4 #define HCI_PCI 5 #define HCI_SDIO 6 +#define HCI_SMD 7 /* HCI controller types */ #define HCI_BREDR 0x00 diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index b1980ccdd6ee..a2c759045bfc 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -349,6 +349,9 @@ struct hci_dev { struct delayed_work rpa_expired; bdaddr_t rpa; + void *driver_data; + struct module *owner; + int (*open)(struct hci_dev *hdev); int (*close)(struct hci_dev *hdev); int (*flush)(struct hci_dev *hdev);