diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 4e73a531b..7821df5df 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -425,4 +425,6 @@ config BT_HCIRSI Say Y here to compile support for HCI over Redpine into the kernel or say M to compile as a module. +source "drivers/bluetooth/rtkbt/Kconfig" + endmenu diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 1a58a3ae1..cd067dcf8 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -46,3 +46,5 @@ hci_uart-$(CONFIG_BT_HCIUART_QCA) += hci_qca.o hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o hci_uart-$(CONFIG_BT_HCIUART_MRVL) += hci_mrvl.o hci_uart-objs := $(hci_uart-y) + +obj-$(CONFIG_BT_RTKBT) += rtkbt/ diff --git a/drivers/bluetooth/rtkbt/Kconfig b/drivers/bluetooth/rtkbt/Kconfig new file mode 100644 index 000000000..286ee90f5 --- /dev/null +++ b/drivers/bluetooth/rtkbt/Kconfig @@ -0,0 +1,5 @@ +config BT_RTKBT + tristate "BT_RTKBT" + default n + help + Help message of RTKBT diff --git a/drivers/bluetooth/rtkbt/Makefile b/drivers/bluetooth/rtkbt/Makefile new file mode 100644 index 000000000..d4802d654 --- /dev/null +++ b/drivers/bluetooth/rtkbt/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_BT_RTKBT) := rtkbt.o +rtkbt-objs := hci_ldisc.o hci_h4.o hci_rtk_h5.o rtk_coex.o diff --git a/drivers/bluetooth/rtkbt/hci_h4.c b/drivers/bluetooth/rtkbt/hci_h4.c new file mode 100644 index 000000000..580bdad53 --- /dev/null +++ b/drivers/bluetooth/rtkbt/hci_h4.c @@ -0,0 +1,327 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hci_uart.h" + +#ifdef BTCOEX +#include "rtk_coex.h" +#endif + +//#define VERSION "1.2" + +struct h4_struct { + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head txq; +}; + +/* H4 receiver States */ +#define H4_W4_PACKET_TYPE 0 +#define H4_W4_EVENT_HDR 1 +#define H4_W4_ACL_HDR 2 +#define H4_W4_SCO_HDR 3 +#define H4_W4_DATA 4 + +/* Initialize protocol */ +static int h4_open(struct hci_uart *hu) +{ + struct h4_struct *h4; + + BT_DBG("hu %p", hu); + + h4 = kzalloc(sizeof(*h4), GFP_ATOMIC); + if (!h4) + return -ENOMEM; + + skb_queue_head_init(&h4->txq); + + hu->priv = h4; + return 0; +} + +/* Flush protocol data */ +static int h4_flush(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&h4->txq); + + return 0; +} + +/* Close protocol */ +static int h4_close(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + + hu->priv = NULL; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&h4->txq); + + kfree_skb(h4->rx_skb); + + hu->priv = NULL; + kfree(h4); + + return 0; +} + +/* Enqueue frame for transmittion (padding, crc, etc) */ +static int h4_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + struct h4_struct *h4 = hu->priv; + + BT_DBG("hu %p skb %p", hu, skb); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + skb_queue_tail(&h4->txq, skb); + + return 0; +} + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) +static inline int h4_check_data_len(struct h4_struct *h4, int len) +#else +static inline int h4_check_data_len(struct hci_dev *hdev, struct h4_struct *h4, int len) +#endif +{ + register int room = skb_tailroom(h4->rx_skb); + + BT_DBG("len %d room %d", len, room); + + if (!len) { +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + hci_recv_frame(h4->rx_skb); +#else + hci_recv_frame(hdev, h4->rx_skb); +#endif + } else if (len > room) { + BT_ERR("Data length is too large"); + kfree_skb(h4->rx_skb); + } else { + h4->rx_state = H4_W4_DATA; + h4->rx_count = len; + return len; + } + + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_skb = NULL; + h4->rx_count = 0; + + return 0; +} + +/* Recv data */ +static int h4_recv(struct hci_uart *hu, void *data, int count) +{ + struct h4_struct *h4 = hu->priv; + register char *ptr; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + register int len, type, dlen; + + BT_DBG("hu %p count %d rx_state %ld rx_count %ld", + hu, count, h4->rx_state, h4->rx_count); + + ptr = data; + while (count) { + if (h4->rx_count) { + len = min_t(unsigned int, h4->rx_count, count); + memcpy(skb_put(h4->rx_skb, len), ptr, len); + h4->rx_count -= len; count -= len; ptr += len; + + if (h4->rx_count) + continue; + + switch (h4->rx_state) { + case H4_W4_DATA: + BT_DBG("Complete data"); +#ifdef BTCOEX + if(bt_cb(h4->rx_skb)->pkt_type == HCI_EVENT_PKT) + rtk_btcoex_parse_event( + h4->rx_skb->data, + h4->rx_skb->len); + + if(bt_cb(h4->rx_skb)->pkt_type == HCI_ACLDATA_PKT) + rtk_btcoex_parse_l2cap_data_rx( + h4->rx_skb->data, + h4->rx_skb->len); +#endif + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + hci_recv_frame(h4->rx_skb); +#else + hci_recv_frame(hu->hdev, h4->rx_skb); +#endif + + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_skb = NULL; + continue; + + case H4_W4_EVENT_HDR: + eh = hci_event_hdr(h4->rx_skb); + + BT_DBG("Event header: evt 0x%2.2x plen %d", eh->evt, eh->plen); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + h4_check_data_len(h4, eh->plen); +#else + h4_check_data_len(hu->hdev, h4, eh->plen); +#endif + continue; + + case H4_W4_ACL_HDR: + ah = hci_acl_hdr(h4->rx_skb); + dlen = __le16_to_cpu(ah->dlen); + + BT_DBG("ACL header: dlen %d", dlen); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + h4_check_data_len(h4, dlen); +#else + h4_check_data_len(hu->hdev, h4, dlen); +#endif + continue; + + case H4_W4_SCO_HDR: + sh = hci_sco_hdr(h4->rx_skb); + + BT_DBG("SCO header: dlen %d", sh->dlen); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + h4_check_data_len(h4, sh->dlen); +#else + h4_check_data_len(hu->hdev, h4, sh->dlen); +#endif + continue; + } + } + + /* H4_W4_PACKET_TYPE */ + switch (*ptr) { + case HCI_EVENT_PKT: + BT_DBG("Event packet"); + h4->rx_state = H4_W4_EVENT_HDR; + h4->rx_count = HCI_EVENT_HDR_SIZE; + type = HCI_EVENT_PKT; + break; + + case HCI_ACLDATA_PKT: + BT_DBG("ACL packet"); + h4->rx_state = H4_W4_ACL_HDR; + h4->rx_count = HCI_ACL_HDR_SIZE; + type = HCI_ACLDATA_PKT; + break; + + case HCI_SCODATA_PKT: + BT_DBG("SCO packet"); + h4->rx_state = H4_W4_SCO_HDR; + h4->rx_count = HCI_SCO_HDR_SIZE; + type = HCI_SCODATA_PKT; + break; + + default: + BT_ERR("Unknown HCI packet type %2.2x", (__u8)*ptr); + hu->hdev->stat.err_rx++; + ptr++; count--; + continue; + }; + + ptr++; count--; + + /* Allocate packet */ + h4->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!h4->rx_skb) { + BT_ERR("Can't allocate mem for new packet"); + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_count = 0; + return -ENOMEM; + } + + h4->rx_skb->dev = (void *) hu->hdev; + bt_cb(h4->rx_skb)->pkt_type = type; + } + + return count; +} + +static struct sk_buff *h4_dequeue(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + return skb_dequeue(&h4->txq); +} + +static struct hci_uart_proto h4p = { + .id = HCI_UART_H4, + .open = h4_open, + .close = h4_close, + .recv = h4_recv, + .enqueue = h4_enqueue, + .dequeue = h4_dequeue, + .flush = h4_flush, +}; + +int __init h4_init(void) +{ + int err = hci_uart_register_proto(&h4p); + + if (!err) + BT_INFO("HCI H4 protocol initialized"); + else + BT_ERR("HCI H4 protocol registration failed"); + + return err; +} + +int __exit h4_deinit(void) +{ + return hci_uart_unregister_proto(&h4p); +} diff --git a/drivers/bluetooth/rtkbt/hci_ldisc.c b/drivers/bluetooth/rtkbt/hci_ldisc.c new file mode 100644 index 000000000..3486c89ac --- /dev/null +++ b/drivers/bluetooth/rtkbt/hci_ldisc.c @@ -0,0 +1,1403 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hci_uart.h" + +#define NEW_TX_SCHED_POLICY + +#if WOBT_NOTIFY +#include +#endif + +#ifdef BTCOEX +#include "rtk_coex.h" +#endif + +#define VERSION "2.2.3634cd9.20220330-143427" + +#if HCI_VERSION_CODE > KERNEL_VERSION(3, 4, 0) +#define GET_DRV_DATA(x) hci_get_drvdata(x) +#else +#define GET_DRV_DATA(x) (struct hci_uart *)(x->driver_data) +#endif + +#define SEMWAIT_TIMEOUT 50 + +#if WOBT_NOTIFY +struct hci_rsp_read_local { + __u8 status; + __u8 hci_ver; + __le16 hci_rev; + __u8 lmp_ver; + __le16 manufacturer; + __le16 lmp_subver; +} __packed; +#endif + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 4, 0) +static int reset = 0; +#endif + +static struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; +static int hci_uart_flush(struct hci_dev *hdev); + +int hci_uart_register_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (hup[p->id]) + return -EEXIST; + + hup[p->id] = p; + + return 0; +} + +int hci_uart_unregister_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (!hup[p->id]) + return -EINVAL; + + hup[p->id] = NULL; + + return 0; +} + +static struct hci_uart_proto *hci_uart_get_proto(unsigned int id) +{ + if (id >= HCI_UART_MAX_PROTO) + return NULL; + + return hup[id]; +} + +static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) +{ + struct hci_dev *hdev = hu->hdev; + + /* Update HCI stat counters */ + switch (pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } +} + +static inline void hci_proto_read_lock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + percpu_down_read(&hu->proto_lock); +#else + down_read(&hu->proto_lock); +#endif +} + +static inline int hci_proto_read_trylock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + return percpu_down_read_trylock(&hu->proto_lock); +#else + return down_read_trylock(&hu->proto_lock); +#endif +} + +static inline void hci_proto_read_unlock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + percpu_up_read(&hu->proto_lock); +#else + up_read(&hu->proto_lock); +#endif +} + +static inline void hci_proto_write_lock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + percpu_down_write(&hu->proto_lock); +#else + down_write(&hu->proto_lock); +#endif +} + +static inline void hci_proto_write_unlock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + percpu_up_write(&hu->proto_lock); +#else + up_write(&hu->proto_lock); +#endif +} + +static inline int hci_proto_init_rwlock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + return percpu_init_rwsem(&hu->proto_lock); +#else + init_rwsem(&hu->proto_lock); + return 0; +#endif +} + +static inline void hci_proto_free_rwlock(struct hci_uart *hu) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + percpu_free_rwsem(&hu->proto_lock); +#endif +} + +static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb = hu->tx_skb; + + if (!skb) { + hci_proto_read_lock(hu); + + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) + skb = hu->proto->dequeue(hu); + + hci_proto_read_unlock(hu); + } else { + hu->tx_skb = NULL; + } + + return skb; +} + +/* This may be called in an IRQ context */ +int hci_uart_tx_wakeup(struct hci_uart *hu) +{ + /* If acquiring lock fails we assume the tty is being closed because + * that is the only time the write lock is acquired. If, however, + * at some point in the future the write lock is also acquired in + * other situations, then this must be revisited. + */ + if (!hci_proto_read_trylock(hu)) + return 0; + + /* proto_lock is locked */ + if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) + goto no_schedule; + +#ifdef NEW_TX_SCHED_POLICY + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) + goto no_schedule; +#else + if (in_interrupt() || in_atomic()) { + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + goto no_schedule; + } + } else { + /* NOTE: proto_lock can't be spin lock, because it may + * schedule here. Schedule is not allowed while atomic + */ + if (down_timeout(&hu->tx_sem, + msecs_to_jiffies(SEMWAIT_TIMEOUT)) == -ETIME) { + pr_warn("%s: Something went wrong with wait\n", + __func__); + goto no_schedule; + } + /* semaphore is locked */ + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + up(&hu->tx_sem); + goto no_schedule; + } + up(&hu->tx_sem); + } +#endif + + BT_DBG(""); + + schedule_work(&hu->write_work); + +no_schedule: + hci_proto_read_unlock(hu); + + return 0; +} + +static void hci_uart_write_work(struct work_struct *work) +{ + struct hci_uart *hu = container_of(work, struct hci_uart, write_work); + struct tty_struct *tty = hu->tty; + struct hci_dev *hdev = hu->hdev; + struct sk_buff *skb; + + /* REVISIT: should we cope with bad skbs or ->write() returning + * and error value ? + */ + + restart: + clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + + while ((skb = hci_uart_dequeue(hu))) { + int len; + + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + len = tty->ops->write(tty, skb->data, skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len) { + hu->tx_skb = skb; + break; + } + + hci_uart_tx_complete(hu, bt_cb(skb)->pkt_type); + kfree_skb(skb); + } + +#ifdef NEW_TX_SCHED_POLICY + clear_bit(HCI_UART_SENDING, &hu->tx_state); + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) + goto restart; +#else + if (down_timeout(&hu->tx_sem, msecs_to_jiffies(SEMWAIT_TIMEOUT))) { + pr_warn("%s: Something went wrong with wait\n", __func__); + goto restart; + } + /* semaphore is locked */ + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) { + up(&hu->tx_sem); + goto restart; + } + + clear_bit(HCI_UART_SENDING, &hu->tx_state); + up(&hu->tx_sem); +#endif + + return; +} + +/* ------- Interface to HCI layer ------ */ +/* Initialize device */ +static int hci_uart_open(struct hci_dev *hdev) +{ + BT_DBG("%s %p", hdev->name, hdev); + + /* Undo clearing this from hci_uart_close() */ + hdev->flush = hci_uart_flush; + +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + set_bit(HCI_RUNNING, &hdev->flags); +#endif + +#ifdef BTCOEX + rtk_btcoex_open(hdev); +#endif + + return 0; +} + +/* static void hci_flush_sync(struct hci_dev *hdev) + * { + * #if HCI_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + * u8 buf[2] = { 0, 0 }; + * struct sk_buff *skb; + * + * BT_INFO("hci flush sync"); + * + * set_bit(HCI_INIT, &hdev->flags); + * skb = __hci_cmd_sync(hdev, 0xfc19, 2, buf, msecs_to_jiffies(2000)); + * clear_bit(HCI_INIT, &hdev->flags); + * + * if (IS_ERR(skb)) { + * BT_ERR("command 0xfc19 tx failed (%ld)\n", PTR_ERR(skb)); + * return; + * } + * + * if (skb->len == 1) + * BT_INFO("hci flush sync status %u", skb->data[0]); + * + * kfree_skb(skb); + * #endif + * } + */ + +static int __hci_uart_flush(struct hci_dev *hdev, u8 sync) +{ + struct hci_uart *hu = GET_DRV_DATA(hdev); //(struct hci_uart *) hdev->driver_data; + struct tty_struct *tty = hu->tty; + + BT_INFO("%s: hdev %p tty %p", __func__, hdev, tty); + + /* Make sure all HCI packets has been transmitted */ + /* if (sync && test_bit(HCI_RUNNING, &hdev->flags)) + * hci_flush_sync(hdev); + */ + + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); + hu->tx_skb = NULL; + } + + /* Flush any pending characters in the driver and discipline. */ + /* tty_ldisc_flush(tty); + * tty_driver_flush_buffer(tty); + */ + /* Don't flush the tty. Sometime, the hdev is closed abnormally. + * There may be cmd complete event in rx buf or the sent ack in tx buf. + * tty flush will result in hciX: command 0xXXXX tx timeout + */ + tty_wait_until_sent(tty, msecs_to_jiffies(500)); + + hci_proto_read_lock(hu); + + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) + hu->proto->flush(hu); + + hci_proto_read_unlock(hu); + + return 0; +} + +/* Reset device */ +static int hci_uart_flush(struct hci_dev *hdev) +{ + return __hci_uart_flush(hdev, 1); +} + +/* Close device */ +static int hci_uart_close(struct hci_dev *hdev) +{ + BT_INFO("%s: hdev %p", __func__, hdev); + + /* When in kernel 4.4.0 and greater, the HCI_RUNNING bit is + * cleared in hci_dev_do_close(). */ +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; +#else + if (test_bit(HCI_RUNNING, &hdev->flags)) + BT_ERR("HCI_RUNNING is not cleared before."); +#endif + + if (test_bit(HCI_RUNNING, &hdev->flags)) + __hci_uart_flush(hdev, 0); + else + __hci_uart_flush(hdev, 1); + + hdev->flush = NULL; + +#ifdef BTCOEX + rtk_btcoex_close(); +#endif + + return 0; +} + +/* Send frames from HCI layer */ +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) +int hci_uart_send_frame(struct sk_buff *skb) +#else +int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +#endif +{ +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + struct hci_dev *hdev = (struct hci_dev *)skb->dev; +#endif + struct hci_uart *hu; + + if (!hdev) { + BT_ERR("Frame for unknown device (hdev=NULL)"); + return -ENODEV; + } + +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; +#endif + + hu = GET_DRV_DATA(hdev); //(struct hci_uart *) hdev->driver_data; + + BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, + skb->len); + +#ifdef BTCOEX + if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT) + rtk_btcoex_parse_cmd(skb->data, skb->len); + if (bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) + rtk_btcoex_parse_l2cap_data_tx(skb->data, skb->len); +#endif + + hci_proto_read_lock(hu); + + if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + hci_proto_read_unlock(hu); + return -EUNATCH; + } + + hu->proto->enqueue(hu, skb); + hci_proto_read_unlock(hu); + + hci_uart_tx_wakeup(hu); + + return 0; +} + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 4, 0) +static void hci_uart_destruct(struct hci_dev *hdev) +{ + if (!hdev) + return; + + BT_DBG("%s", hdev->name); + kfree(hdev->driver_data); +} +#endif + +#if WOBT_NOTIFY +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 13, 0) +static inline void *skb_put_data(struct sk_buff *skb, const void *data, + unsigned int len) +{ + void *tmp = skb_put(skb, len); + + memcpy(tmp, data, len); + + return tmp; +} +#endif + +static int hci_uart_async_send(struct hci_uart *hu, u16 opcode, + u32 plen, const void *param) +{ + int len = HCI_COMMAND_HDR_SIZE + plen; + struct hci_command_hdr *hdr; + struct sk_buff *skb; + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + hdr = (struct hci_command_hdr *)skb_put(skb, HCI_COMMAND_HDR_SIZE); + hdr->opcode = cpu_to_le16(opcode); + hdr->plen = plen; + + if (plen) + memcpy(skb_put(skb, plen), param, plen); + + BT_INFO("rtl: skb len %d", skb->len); + + bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + bt_cb(skb)->opcode = opcode; +#else + bt_cb(skb)->hci.opcode = opcode; +#endif +#endif + + /* Stand-alone HCI commands must be flagged as + * single-command requests. + */ +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + bt_cb(skb)->req.start = true; +#else + +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 5, 0) + bt_cb(skb)->hci.req_start = true; +#else + + bt_cb(skb)->hci.req_flags |= HCI_REQ_START; +#endif +#endif /* 4.4.0 */ +#endif /* 3.10.0 */ + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + hci_uart_send_frame(skb); +#else + hci_uart_send_frame(hu->hdev, skb); +#endif + + /* hci_proto_read_lock(hu); + + * if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + * hci_proto_read_unlock(hu); + * BT_ERR("rtl send: proto not ready"); + * return -EUNATCH; + * } + + * hu->proto->enqueue(hu, skb); + * hci_proto_read_unlock(hu); + + * hci_uart_tx_wakeup(hu); + */ + + return 0; +} + +static int rtl_read_local_version(struct hci_dev *hdev, u8 *hci_ver, + u16 *hci_rev, u16 *lmp_subver) +{ + struct hci_rsp_read_local *ver; + struct sk_buff *skb; + + skb = __hci_cmd_sync(hdev, 0x1001, 0, NULL, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("rtl: Could not read lmp subversion"); + return PTR_ERR(skb); + } + + if (skb->len != sizeof(struct hci_rsp_read_local)) { + BT_ERR("%s: rtl: Local version length mismatch", hdev->name); + kfree_skb(skb); + return -EIO; + } + + ver = (struct hci_rsp_read_local *)skb->data; + *hci_ver = ver->hci_ver; + *hci_rev = le16_to_cpu(ver->hci_rev); + *lmp_subver = le16_to_cpu(ver->lmp_subver); + + kfree_skb(skb); + + return 0; +} + +#if RTKBT_TV_POWERON_WHITELIST +static int rtkbt_lookup_le_device_poweron_whitelist(struct hci_uart *hu) +{ + struct hci_conn_params *p; + u8 *params; + int result = 0; + + hci_dev_lock(hu->hdev); + list_for_each_entry(p, &hu->hdev->le_conn_params, list) { +#if 0 // for debug message + BT_INFO("%s(): auto_connect = %d", __FUNCTION__, p->auto_connect); + BT_INFO("%s(): addr_type = 0x%02x", __FUNCTION__, p->addr_type); + BT_INFO("%s(): addr=%02x:%02x:%02x:%02x:%02x:%02x", __FUNCTION__, + p->addr.b[5], p->addr.b[4], p->addr.b[3], + p->addr.b[2], p->addr.b[1], p->addr.b[0]); +#endif + if ( p->auto_connect == HCI_AUTO_CONN_ALWAYS && + p->addr_type == ADDR_LE_DEV_PUBLIC ) { + + BT_INFO("%s(): Set RTKBT LE Power-on Whitelist for " + "%02x:%02x:%02x:%02x:%02x:%02x", __FUNCTION__, + p->addr.b[5], p->addr.b[4], p->addr.b[3], + p->addr.b[2], p->addr.b[1], p->addr.b[0]); + + params = kzalloc(8, GFP_ATOMIC); + if (!params) { + BT_ERR("Can't allocate memory for params"); + return -ENOMEM; + } + + params[0] = 0x00; + params[1] = p->addr.b[0]; + params[2] = p->addr.b[1]; + params[3] = p->addr.b[2]; + params[4] = p->addr.b[3]; + params[5] = p->addr.b[4]; + params[6] = p->addr.b[5]; + + result = hci_uart_async_send(hu, 0xfc7b, 7, params); + if (result) + BT_ERR("rtl: Command failed for power-on whitelist"); + + msleep(500); + + kfree(params); + } + } + hci_dev_unlock(hu->hdev); + + return result; +} +#endif + +#if RTKBT_TV_POWERON_DATA_FILTER +static int rtkbt_set_le_device_poweron_data_filter(struct hci_uart *hu) +{ + /* Set data filter on Manufacturer field of Advertising data */ + /* Manufacturer | ID | Additional data*/ + /* Technicolor | 0x02af | 0x57, 0x41, 0x4b, 0x45, 0x55, 0x50 */ + u8 params[8] = { 0xaf, 0x02, // Manufacturer ID + 0x57, 0x41, 0x4b, 0x45, 0x55, 0x50 }; // Additional data + int result = 0; + + result = hci_uart_async_send(hu, 0xfc7f, 8, params); + if (result) + BT_ERR("rtl: Command failed for set data filter"); + + return result; +} +#endif + +static int rtkbt_simulate_disconnect_event(struct hci_uart *hu) +{ + struct hci_conn *conn; + struct sk_buff *rx_skb; + u8 event_params[6] = { 0x05, 0x04, 0x00, 0x10, 0x00, 0x13 }; + int result = 0; + + hci_dev_lock(hu->hdev); + + conn = hci_conn_hash_lookup_state(hu->hdev, LE_LINK, BT_CONNECTED); + if (conn && (conn->state == BT_CONNECTED)){ + rx_skb = alloc_skb(6, GFP_ATOMIC); + if (!rx_skb) + return -1; + + event_params[3] = (u8)(conn->handle); + event_params[4] = (u8)(conn->handle >> 8); + hci_skb_pkt_type(rx_skb) = HCI_EVENT_PKT; + skb_put_data(rx_skb, event_params, 6); + + BT_INFO("Send Disconnect Complete EVENT to upper stack"); + hci_recv_frame(hu->hdev, rx_skb); + } + + hci_dev_unlock(hu->hdev); + + msleep(1000); + + return result; +} + +static int rtkbt_notify_suspend(struct hci_uart *hu) +{ + u8 params_suspend_notify[1] = { 0x01 }; + int result = 0; + + result = hci_uart_async_send(hu, 0xfc28, 1, params_suspend_notify); + if (result) + BT_ERR("Realtek suspend h5-bt failed"); + + msleep(500); + + return result; +} + +static void le_scan_disable(struct hci_uart *hu) +{ +#if HCI_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + if (use_ext_scan(hu->hdev)) { + u8 ext_enable_cp[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + hci_uart_async_send(hu, HCI_OP_LE_SET_EXT_SCAN_ENABLE, 6, ext_enable_cp); + } else { + u8 enable_cp[2] = {0x00, 0x00}; + + hci_uart_async_send(hu, HCI_OP_LE_SET_SCAN_ENABLE, 2, enable_cp); + } +#else + u8 enable_cp[2] = {0x00, 0x00}; + + hci_uart_async_send(hu, HCI_OP_LE_SET_SCAN_ENABLE, 2, enable_cp); +#endif + + return; +} + +static void le_scan_restart(struct hci_uart *hu) +{ + int result; +#if HCI_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + if (use_ext_scan(hu->hdev)) { + u8 ext_enable_cp[6] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}; + + BT_INFO("LE Extended Scan Restart..."); + le_scan_disable(hu); + result = hci_uart_async_send(hu, HCI_OP_LE_SET_EXT_SCAN_ENABLE, 6, ext_enable_cp); + if (result) + BT_ERR("LE Extended Scan Restart: Failed"); + } else { + u8 enable_cp[2] = {0x01, 0x01}; + + BT_INFO("LE Scan Restart..."); + le_scan_disable(hu); + result = hci_uart_async_send(hu, HCI_OP_LE_SET_SCAN_ENABLE, 2, enable_cp); + if (result) + BT_ERR("LE Scan Restart: Failed"); + } +#else + u8 enable_cp[2] = {0x01, 0x01}; + + BT_INFO("LE Scan Restart"); + le_scan_disable(hu); + result = hci_uart_async_send(hu, HCI_OP_LE_SET_SCAN_ENABLE, 2, enable_cp); + if (result) + BT_ERR("LE Scan Restart: Failed"); +#endif + return; +} + +static bool le_aoto_conn_always_exist(struct hci_uart *hu) +{ + struct hci_conn_params *p; + bool ret = false; + + hci_dev_lock(hu->hdev); + list_for_each_entry(p, &hu->hdev->le_conn_params, list) { + if ( p->auto_connect == HCI_AUTO_CONN_ALWAYS && + p->addr_type == ADDR_LE_DEV_PUBLIC ) { + + ret = true; + } + } + hci_dev_unlock(hu->hdev); + + return ret; +} + +static int hci_uart_pm_notifier(struct notifier_block *b, unsigned long v, void *d) +{ + int result; + struct hci_uart *hu = container_of(b, struct hci_uart, pm_notify_block); + u8 hci_ver = 0; + u16 hci_rev = 0; + u16 lmp_subver = 0; +#if WOBT_NOTIFY_BG_SCAN_LE_WHITELIST_ONLY + u8 params_bg_scan[5] = { 0x60, 0x01, 0x10, 0x00, 0x01 }; +#endif + + BT_INFO("%s: %lu", __func__, v); + switch (v) { + case PM_SUSPEND_PREPARE: + BT_INFO("rtl: bt suspending"); +#if WOBT_NOTIFY_BG_SCAN_LE_WHITELIST_ONLY + /* Send set back ground scan parameters to Controller for power-on mode */ + result = hci_uart_async_send(hu, 0xfc7a, 5, params_bg_scan); + if (result) + BT_ERR("Realtek bg-scan h5-bt failed"); + /* FIXME: Ensure the above vendor command is sent to Controller + * and we received the h5 ack from Controller + * */ + msleep(500); + +#endif + +#if RTKBT_TV_POWERON_WHITELIST + result = rtkbt_lookup_le_device_poweron_whitelist(hu); + if (result < 0) { + BT_ERR("rtkbt_lookup_le_device_poweron_whitelist error: %d", result); + } +#endif + +#if RTKBT_TV_POWERON_DATA_FILTER + result = rtkbt_set_le_device_poweron_data_filter(hu); + if (result < 0) { + BT_ERR("rtkbt_set_le_device_poweron_data_filter error: %d", result); + } +#endif + result = rtkbt_notify_suspend(hu); + if (result < 0) { + BT_ERR("rtkbt_notify_suspend error: %d", result); + } + + break; + case PM_POST_SUSPEND: + result = rtl_read_local_version(hu->hdev, &hci_ver, &hci_rev, + &lmp_subver); + if (result) + break; + BT_INFO("rtl resume: hci ver %u, hci rev %04x, lmp subver %04x", + hci_ver, hci_rev, lmp_subver); + + result = rtkbt_simulate_disconnect_event(hu); + if (result < 0) + BT_ERR("rtkbt_simulate_disconnect_event error: %d", result); + + if (le_aoto_conn_always_exist(hu)) + le_scan_restart(hu); + + break; + default: + BT_INFO("Caught msg %lu other than SUSPEND_PREPARE", v); + break; + } + + return 0; +} +#endif + +/* ------ LDISC part ------ */ +/* hci_uart_tty_open + * + * Called when line discipline changed to HCI_UART. + * + * Arguments: + * tty pointer to tty info structure + * Return Value: + * 0 if success, otherwise error code + */ +static int hci_uart_tty_open(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG("tty %p", tty); + + /* But nothing ensures disc_data to be NULL. And since ld->ops->open + * shall be called only once, we do not need the check at all. + * So remove it. + * + * Note that this is not an issue now, but n_tty will start using the + * disc_data pointer and this invalid 'if' would trigger then rendering + * TTYs over BT unusable. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0) + /* FIXME: This btw is bogus, nothing requires the old ldisc to clear + * the pointer + */ + if (hu) + return -EEXIST; +#endif + + /* Error if the tty has no write op instead of leaving an exploitable + * hole + */ + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + if (!(hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL))) { + BT_ERR("Can't allocate control structure"); + return -ENFILE; + } + + tty->disc_data = hu; + hu->tty = tty; + tty->receive_room = 65536; + + INIT_WORK(&hu->write_work, hci_uart_write_work); + + hci_proto_init_rwlock(hu); + sema_init(&hu->tx_sem, 1); + + /* Flush any pending characters in the driver and line discipline. */ + + /* FIXME: why is this needed. Note don't use ldisc_ref here as the + open path is before the ldisc is referencable */ + + if (tty->ldisc->ops->flush_buffer) + tty->ldisc->ops->flush_buffer(tty); + tty_driver_flush_buffer(tty); + +#if WOBT_NOTIFY + hu->pm_notify_block.notifier_call = hci_uart_pm_notifier; + register_pm_notifier(&hu->pm_notify_block); +#endif + + return 0; +} + +/* hci_uart_tty_close() + * + * Called when the line discipline is changed to something + * else, the tty is closed, or the tty detects a hangup. + */ +static void hci_uart_tty_close(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + struct hci_dev *hdev; + + BT_INFO("%s: tty %p", __func__, tty); + + /* Detach from the tty */ + tty->disc_data = NULL; + + if (!hu) + return; + + hdev = hu->hdev; + if (hdev) + hci_uart_close(hdev); + + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + hci_proto_write_lock(hu); + clear_bit(HCI_UART_PROTO_READY, &hu->flags); + hci_proto_write_unlock(hu); + + cancel_work_sync(&hu->write_work); + + if (hdev) { + if (test_bit(HCI_UART_REGISTERED, &hu->flags)) + hci_unregister_dev(hdev); + hci_free_dev(hdev); + } + hu->proto->close(hu); + } + clear_bit(HCI_UART_PROTO_SET, &hu->flags); + + hci_proto_free_rwlock(hu); +#if WOBT_NOTIFY + unregister_pm_notifier(&hu->pm_notify_block); +#endif + + kfree(hu); +} + +/* hci_uart_tty_wakeup() + * + * Callback for transmit wakeup. Called when low level + * device driver can accept more send data. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: None + */ +static void hci_uart_tty_wakeup(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG(""); + + if (!hu) + return; + + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + if (tty != hu->tty) + return; + + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) + hci_uart_tx_wakeup(hu); +} + +/* hci_uart_tty_receive() + * + * Called by tty low level driver when receive data is + * available. + * + * Arguments: tty pointer to tty isntance data + * data pointer to received data + * flags pointer to flags for data + * count count of received data in bytes + * + * Return Value: None + */ +static void hci_uart_tty_receive(struct tty_struct *tty, const u8 * data, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) + const char *flags, int count) +#else + char *flags, int count) +#endif +{ + struct hci_uart *hu = (void *)tty->disc_data; + int (*proto_receive)(struct hci_uart *hu, void *data, int len); + + if (!hu || tty != hu->tty) + return; + + hci_proto_read_lock(hu); + + if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + hci_proto_read_unlock(hu); + return; + } + + proto_receive = hu->proto->recv; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) + proto_receive(hu, (void *)data, count); + hci_proto_read_unlock(hu); +#else + hci_proto_read_unlock(hu); + /* It does not need a lock here as it is already protected by a mutex in + * tty caller + */ + proto_receive(hu, (void *)data, count); +#endif + + if (hu->hdev) + hu->hdev->stat.byte_rx += count; + + tty_unthrottle(tty); +} + +static int hci_uart_register_dev(struct hci_uart *hu) +{ + struct hci_dev *hdev; + + BT_INFO("hci_uart_register_dev"); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hu->hdev = hdev; + +#if HCI_VERSION_CODE > KERNEL_VERSION(2, 6, 33) + hdev->bus = HCI_UART; +#else + hdev->type = HCI_UART; +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + hci_set_drvdata(hdev, hu); +#else + hdev->driver_data = hu; +#endif + + hdev->open = hci_uart_open; + hdev->close = hci_uart_close; + hdev->flush = hci_uart_flush; + hdev->send = hci_uart_send_frame; + + /* NOTE: No hdev->setup setting for Realtek BTUART because + * the download procedure is done with rtk_hciattach in userspace + * before this function called in hci_uart_set_proto() + */ + + SET_HCIDEV_DEV(hdev, hu->tty->dev); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + hdev->destruct = hci_uart_destruct; + hdev->owner = THIS_MODULE; +#endif + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + if (!reset) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) + if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) + if (test_bit(HCI_UART_EXT_CONFIG, &hu->hdev_flags)) + set_bit(HCI_QUIRK_EXTERNAL_CONFIG, &hdev->quirks); +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + if (!test_bit(HCI_UART_RESET_ON_INIT, &hu->hdev_flags)) +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) + set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks); +#else + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); +#endif +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + if (test_bit(HCI_UART_CREATE_AMP, &hu->hdev_flags)) + hdev->dev_type = HCI_AMP; + else +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 8, 0) + hdev->dev_type = HCI_BREDR; +#else + hdev->dev_type = HCI_PRIMARY; +#endif +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks); +#endif + +#if HCI_VERSION_CODE >= KERNEL_VERSION(5, 10, 21) +#if WOBT_NOTIFY + set_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks); +#endif +#endif + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + return -ENODEV; + } + + set_bit(HCI_UART_REGISTERED, &hu->flags); + +#ifdef BTCOEX + rtk_btcoex_probe(hdev); +#endif + + return 0; +} + +static int hci_uart_set_proto(struct hci_uart *hu, int id) +{ + struct hci_uart_proto *p; + int err; + + p = hci_uart_get_proto(id); + if (!p) + return -EPROTONOSUPPORT; + + err = p->open(hu); + if (err) + return err; + + hu->proto = p; + set_bit(HCI_UART_PROTO_READY, &hu->flags); + + /* Initialize and register HCI dev */ + err = hci_uart_register_dev(hu); + if (err) { + clear_bit(HCI_UART_PROTO_READY, &hu->flags); + p->close(hu); + return err; + } + + return 0; +} + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) +static int hci_uart_set_flags(struct hci_uart *hu, unsigned long flags) +{ + /* TODO: Add HCI_UART_INIT_PENDING, HCI_UART_VND_DETECT check */ + unsigned long valid_flags = BIT(HCI_UART_RAW_DEVICE) | + BIT(HCI_UART_RESET_ON_INIT) | + BIT(HCI_UART_CREATE_AMP) | + BIT(HCI_UART_EXT_CONFIG); + + if (flags & ~valid_flags) + return -EINVAL; + + hu->hdev_flags = flags; + + return 0; +} +#endif + +/* hci_uart_tty_ioctl() + * + * Process IOCTL system call for the tty device. + * + * Arguments: + * + * tty pointer to tty instance data + * file pointer to open file object for device + * cmd IOCTL command code + * arg argument for IOCTL call (cmd dependent) + * + * Return Value: Command dependent + */ +static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct hci_uart *hu = (void *)tty->disc_data; + int err = 0; + + BT_DBG(""); + + /* Verify the status of the device */ + if (!hu) + return -EBADF; + + switch (cmd) { + case HCIUARTSETPROTO: + if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) { + err = hci_uart_set_proto(hu, arg); + if (err) { + clear_bit(HCI_UART_PROTO_SET, &hu->flags); + return err; + } + } else + return -EBUSY; + break; + + case HCIUARTGETPROTO: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return hu->proto->id; + return -EUNATCH; + + case HCIUARTGETDEVICE: + if (test_bit(HCI_UART_REGISTERED, &hu->flags)) + return hu->hdev->id; + return -EUNATCH; + + case HCIUARTSETFLAGS: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return -EBUSY; +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) + err = hci_uart_set_flags(hu, arg); + if (err) + return err; +#else + hu->hdev_flags = arg; +#endif + break; + + case HCIUARTGETFLAGS: + return hu->hdev_flags; + + default: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + err = n_tty_ioctl_helper(tty, cmd, arg); +#else + err = n_tty_ioctl_helper(tty, file, cmd, arg); +#endif + break; + }; + + return err; +} + +/* + * We don't provide read/write/poll interface for user space. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 20) && \ + ((LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0)) || \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 3))) +static ssize_t hci_uart_tty_read(struct tty_struct *tty, struct file *file, + unsigned char *buf, size_t nr, + void **cookie, unsigned long offset) +#else +static ssize_t hci_uart_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user * buf, size_t nr) +#endif +{ + return 0; +} + +static ssize_t hci_uart_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + return 0; +} + +static unsigned int hci_uart_tty_poll(struct tty_struct *tty, + struct file *filp, poll_table * wait) +{ + return 0; +} + +static struct tty_ldisc_ops hci_uart_ldisc = { + .owner = THIS_MODULE, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) + .num = N_HCI, +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) + .magic = TTY_LDISC_MAGIC, +#endif + .name = "n_hci", + .open = hci_uart_tty_open, + .close = hci_uart_tty_close, + .read = hci_uart_tty_read, + .write = hci_uart_tty_write, + .ioctl = hci_uart_tty_ioctl, +#if HCI_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) + .compat_ioctl = hci_uart_tty_ioctl, +#endif + .poll = hci_uart_tty_poll, + .receive_buf = hci_uart_tty_receive, + .write_wakeup = hci_uart_tty_wakeup, +}; + +static int __init hci_uart_init(void) +{ + int err; + + BT_INFO("HCI UART driver ver %s", VERSION); + + /* Register the tty discipline */ +#if HCI_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) + if ((err = tty_register_ldisc(&hci_uart_ldisc))) { +#else + if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) { +#endif + BT_ERR("HCI line discipline registration failed. (%d)", err); + return err; + } +#ifdef CONFIG_BT_HCIUART_H4 + h4_init(); +#endif + /* Add realtek h5 support */ + h5_init(); + +#ifdef BTCOEX + rtk_btcoex_init(); +#endif + + return 0; +} + +static void __exit hci_uart_exit(void) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) + int err; +#endif + +#ifdef CONFIG_BT_HCIUART_H4 + h4_deinit(); +#endif + h5_deinit(); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) + tty_unregister_ldisc(&hci_uart_ldisc); +#else + /* Release tty registration of line discipline */ + if ((err = tty_unregister_ldisc(N_HCI))) + BT_ERR("Can't unregister HCI line discipline (%d)", err); +#endif + +#ifdef BTCOEX + rtk_btcoex_exit(); +#endif +} + +module_init(hci_uart_init); +module_exit(hci_uart_exit); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 4, 0) +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); +#endif + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("Bluetooth HCI UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_HCI); diff --git a/drivers/bluetooth/rtkbt/hci_rtk_h5.c b/drivers/bluetooth/rtkbt/hci_rtk_h5.c new file mode 100644 index 000000000..7fbce6689 --- /dev/null +++ b/drivers/bluetooth/rtkbt/hci_rtk_h5.c @@ -0,0 +1,908 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2011-2014 wifi_fae + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hci_uart.h" + +#ifdef BTCOEX +#include "rtk_coex.h" +#endif + +//#define VERSION "1.0" + +static int txcrc = 1; +//static int hciextn = 1; + +#define H5_TXWINSIZE 4 +#define H5_ACK_PKT 0x00 +#define H5_LE_PKT 0x0F +#define H5_VDRSPEC_PKT 0x0E + +struct h5_struct { + struct sk_buff_head unack; /* Unack'ed packets queue */ + struct sk_buff_head rel; /* Reliable packets queue */ + struct sk_buff_head unrel; /* Unreliable packets queue */ + + unsigned long rx_count; + struct sk_buff *rx_skb; + struct delayed_work retrans_work; + struct hci_uart *hu; /* Parent HCI UART */ + + enum { + H5_W4_PKT_DELIMITER, + H5_W4_PKT_START, + H5_W4_HDR, + H5_W4_DATA, + H5_W4_CRC + } rx_state; + + enum { + H5_ESCSTATE_NOESC, + H5_ESCSTATE_ESC + } rx_esc_state; + + u16 message_crc; + u8 use_crc; + u8 rxack; /* Last packet sent by us that the peer ack'ed */ + + u8 rxseq_txack; /* rxseq == txack. */ + u8 txack_req; /* Do we need to send ack's to the peer? */ + /* Reliable packet sequence number - used to assign seq to each rel pkt. */ + u8 msgq_txseq; + + /* The spin lock protects seq, ack and ack req */ + spinlock_t lock; +}; + +/* ---- H5 CRC calculation ---- */ + +/* Table for calculating CRC for polynomial 0x1021, LSB processed first, +initial value 0xffff, bits shifted in reverse order. */ + +static const u16 crc_table[] = { + 0x0000, 0x1081, 0x2102, 0x3183, + 0x4204, 0x5285, 0x6306, 0x7387, + 0x8408, 0x9489, 0xa50a, 0xb58b, + 0xc60c, 0xd68d, 0xe70e, 0xf78f +}; + +/* Initialise the crc calculator */ +#define H5_CRC_INIT(x) x = 0xffff + +/* + Update crc with next data byte + + Implementation note + The data byte is treated as two nibbles. The crc is generated + in reverse, i.e., bits are fed into the register from the top. +*/ +static void h5_crc_update(u16 * crc, u8 d) +{ + u16 reg = *crc; + + reg = (reg >> 4) ^ crc_table[(reg ^ d) & 0x000f]; + reg = (reg >> 4) ^ crc_table[(reg ^ (d >> 4)) & 0x000f]; + + *crc = reg; +} + +/* ---- H5 core ---- */ + +static void h5_slip_msgdelim(struct sk_buff *skb) +{ + const char pkt_delim = 0xc0; + + memcpy(skb_put(skb, 1), &pkt_delim, 1); +} + +static void h5_slip_one_byte(struct sk_buff *skb, u8 c) +{ + const char esc_c0[2] = { 0xdb, 0xdc }; + const char esc_db[2] = { 0xdb, 0xdd }; + const char esc_11[2] = { 0xdb, 0xde }; + const char esc_13[2] = { 0xdb, 0xdf }; + + switch (c) { + case 0xc0: + memcpy(skb_put(skb, 2), &esc_c0, 2); + break; + case 0xdb: + memcpy(skb_put(skb, 2), &esc_db, 2); + break; + case 0x11: + memcpy(skb_put(skb, 2), &esc_11, 2); + break; + case 0x13: + memcpy(skb_put(skb, 2), &esc_13, 2); + break; + default: + memcpy(skb_put(skb, 1), &c, 1); + } +} + +static int h5_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + struct h5_struct *h5 = hu->priv; + + if (skb->len > 0xFFF) { //Pkt length must be less than 4095 bytes + BT_ERR("Packet too long"); + kfree_skb(skb); + return 0; + } + + switch (bt_cb(skb)->pkt_type) { + case HCI_ACLDATA_PKT: + case HCI_COMMAND_PKT: + skb_queue_tail(&h5->rel, skb); + break; + + case HCI_SCODATA_PKT: + skb_queue_tail(&h5->unrel, skb); + break; + case H5_LE_PKT: + case H5_ACK_PKT: + case H5_VDRSPEC_PKT: + skb_queue_tail(&h5->unrel, skb); /* 3-wire LinkEstablishment */ + break; + + default: + BT_ERR("Unknown packet type"); + kfree_skb(skb); + break; + } + + return 0; +} + +static struct sk_buff *h5_prepare_pkt(struct h5_struct *h5, u8 * data, + int len, int pkt_type) +{ + struct sk_buff *nskb; + u8 hdr[4], chan; + u16 H5_CRC_INIT(h5_txmsg_crc); + int rel, i; + u8 tmp; + unsigned long flags; + + switch (pkt_type) { + case HCI_ACLDATA_PKT: + chan = 2; /* 3-wire ACL channel */ + rel = 1; /* reliable channel */ + break; + case HCI_COMMAND_PKT: + chan = 1; /* 3-wire cmd channel */ + rel = 1; /* reliable channel */ + break; + case HCI_EVENT_PKT: + chan = 4; /* 3-wire cmd channel */ + rel = 1; /* reliable channel */ + break; + case HCI_SCODATA_PKT: + chan = 3; /* 3-wire SCO channel */ + rel = 0; /* unreliable channel */ + break; + case H5_LE_PKT: + chan = 15; /* 3-wire LinkEstablishment channel */ + rel = 0; /* unreliable channel */ + break; + case H5_ACK_PKT: + chan = 0; /* 3-wire ACK channel */ + rel = 0; /* unreliable channel */ + break; + case H5_VDRSPEC_PKT: + chan = 14; /* 3-wire Vendor Specific channel */ + rel = 0; /* unreliable channel */ + break; + default: + BT_ERR("Unknown packet type"); + return NULL; + } + + /* Max len of packet: (original len +4(h5 hdr) +2(crc))*2 + (because bytes 0xc0 and 0xdb are escaped, worst case is + when the packet is all made of 0xc0 and 0xdb :) ) + + 2 (0xc0 delimiters at start and end). */ + + nskb = alloc_skb((len + 6) * 2 + 2, GFP_ATOMIC); + if (!nskb) + return NULL; + + bt_cb(nskb)->pkt_type = pkt_type; + + h5_slip_msgdelim(nskb); + + spin_lock_irqsave(&h5->lock, flags); + tmp = h5->rxseq_txack; + hdr[0] = h5->rxseq_txack << 3; + h5->txack_req = 0; + spin_unlock_irqrestore(&h5->lock, flags); + BT_DBG("We request packet no %u to card", tmp); + + if (rel) { + spin_lock_irqsave(&h5->lock, flags); + tmp = h5->msgq_txseq; + hdr[0] |= 0x80 + h5->msgq_txseq; + h5->msgq_txseq = (h5->msgq_txseq + 1) & 0x07; + spin_unlock_irqrestore(&h5->lock, flags); + BT_DBG("Sending packet with seqno %u", tmp); + } + + if (h5->use_crc) + hdr[0] |= 0x40; + + hdr[1] = ((len << 4) & 0xff) | chan; + hdr[2] = len >> 4; + hdr[3] = ~(hdr[0] + hdr[1] + hdr[2]); + + /* Put H5 header */ + for (i = 0; i < 4; i++) { + h5_slip_one_byte(nskb, hdr[i]); + + if (h5->use_crc) + h5_crc_update(&h5_txmsg_crc, hdr[i]); + } + + /* Put payload */ + for (i = 0; i < len; i++) { + h5_slip_one_byte(nskb, data[i]); + + if (h5->use_crc) + h5_crc_update(&h5_txmsg_crc, data[i]); + } + + /* Put CRC */ + if (h5->use_crc) { + h5_txmsg_crc = bitrev16(h5_txmsg_crc); + h5_slip_one_byte(nskb, (u8) ((h5_txmsg_crc >> 8) & 0x00ff)); + h5_slip_one_byte(nskb, (u8) (h5_txmsg_crc & 0x00ff)); + } + + h5_slip_msgdelim(nskb); + return nskb; +} + +/* This is a rewrite of pkt_avail in AH5 */ +static struct sk_buff *h5_dequeue(struct hci_uart *hu) +{ + struct h5_struct *h5 = hu->priv; + unsigned long flags; + struct sk_buff *skb; + + /* First of all, check for unreliable messages in the queue, + since they have priority */ + + if ((skb = skb_dequeue(&h5->unrel)) != NULL) { + struct sk_buff *nskb = + h5_prepare_pkt(h5, skb->data, skb->len, + bt_cb(skb)->pkt_type); + if (nskb) { + kfree_skb(skb); + return nskb; + } else { + skb_queue_head(&h5->unrel, skb); + BT_ERR + ("Could not dequeue pkt because alloc_skb failed"); + } + } + + /* Now, try to send a reliable pkt. We can only send a + reliable packet if the number of packets sent but not yet ack'ed + is < than the winsize */ + + spin_lock_irqsave_nested(&h5->unack.lock, flags, SINGLE_DEPTH_NESTING); + + if (h5->unack.qlen < H5_TXWINSIZE + && (skb = skb_dequeue(&h5->rel)) != NULL) { + struct sk_buff *nskb = + h5_prepare_pkt(h5, skb->data, skb->len, + bt_cb(skb)->pkt_type); + if (nskb) { + __skb_queue_tail(&h5->unack, skb); + schedule_delayed_work(&h5->retrans_work, HZ / 4); + spin_unlock_irqrestore(&h5->unack.lock, flags); + return nskb; + } else { + skb_queue_head(&h5->rel, skb); + BT_ERR + ("Could not dequeue pkt because alloc_skb failed"); + } + } + + spin_unlock_irqrestore(&h5->unack.lock, flags); + + /* We could not send a reliable packet, either because there are + none or because there are too many unack'ed pkts. Did we receive + any packets we have not acknowledged yet ? */ + + if (h5->txack_req) { + /* if so, craft an empty ACK pkt and send it on H5 unreliable + channel 0 */ + struct sk_buff *nskb = h5_prepare_pkt(h5, NULL, 0, H5_ACK_PKT); + return nskb; + } + + /* We have nothing to send */ + return NULL; +} + +static int h5_flush(struct hci_uart *hu) +{ + BT_DBG("hu %p", hu); + return 0; +} + +/* Remove ack'ed packets */ +static void h5_pkt_cull(struct h5_struct *h5) +{ + struct sk_buff *skb, *tmp; + unsigned long flags; + int i, pkts_to_be_removed; + u8 seqno; + + spin_lock_irqsave(&h5->unack.lock, flags); + + pkts_to_be_removed = skb_queue_len(&h5->unack); + seqno = h5->msgq_txseq; + + while (pkts_to_be_removed) { + if (h5->rxack == seqno) + break; + pkts_to_be_removed--; + seqno = (seqno - 1) & 0x07; + } + + if (h5->rxack != seqno) + BT_ERR("Peer acked invalid packet"); + + BT_DBG("Removing %u pkts out of %u, up to seqno %u", + pkts_to_be_removed, skb_queue_len(&h5->unack), + (seqno - 1) & 0x07); + + i = 0; + skb_queue_walk_safe(&h5->unack, skb, tmp) { + if (i >= pkts_to_be_removed) + break; + i++; + + __skb_unlink(skb, &h5->unack); + kfree_skb(skb); + } + + if (skb_queue_empty(&h5->unack)) + cancel_delayed_work(&h5->retrans_work); + + spin_unlock_irqrestore(&h5->unack.lock, flags); + + if (i != pkts_to_be_removed) + BT_ERR("Removed only %u out of %u pkts", i, pkts_to_be_removed); +} + +/* Handle H5 link-establishment packets. When we + detect a "sync" packet, symptom that the BT module has reset, + we do nothing :) (yet) */ +#if 0 +static void h5_handle_le_pkt(struct hci_uart *hu) +{ + struct h5_struct *h5 = hu->priv; + u8 conf_pkt[2] = { 0x03, 0xfc }; + u8 conf_rsp_pkt[3] = { 0x04, 0x7b, 0x00 }; + u8 sync_pkt[2] = { 0x01, 0x7e }; + u8 sync_rsp_pkt[2] = { 0x02, 0x7d }; + + u8 wakeup_pkt[2] = { 0x05, 0xfa }; + u8 woken_pkt[2] = { 0x06, 0xf9 }; + u8 sleep_pkt[2] = { 0x07, 0x78 }; + + /* spot "conf" pkts and reply with a "conf rsp" pkt */ + if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_skb->data[4], conf_pkt, 2)) { + struct sk_buff *nskb = alloc_skb(3, GFP_ATOMIC); + + BT_DBG("Found a LE conf pkt"); + if (!nskb) + return; + + conf_rsp_pkt[2] |= txcrc << 0x4; //crc check enable, version no = 0. needed to be as avariable. + memcpy(skb_put(nskb, 3), conf_rsp_pkt, 3); + bt_cb(nskb)->pkt_type = H5_LE_PKT; + + skb_queue_head(&h5->unrel, nskb); + hci_uart_tx_wakeup(hu); + } + /* spot "conf resp" pkts */ + else if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_skb->data[4], conf_rsp_pkt, 2)) { + BT_DBG("Found a LE conf resp pkt, device go into active state"); + txcrc = (h5->rx_skb->data[6] >> 0x4) & 0x1; + } + + /* Spot "sync" pkts. If we find one...disaster! */ + else if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_skb->data[4], sync_pkt, 2)) { + BT_ERR("Found a LE sync pkt, card has reset"); + //DO Something here + } + /* Spot "sync resp" pkts. If we find one...disaster! */ + else if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_skb->data[4], sync_rsp_pkt, 2)) { + BT_ERR + ("Found a LE sync resp pkt, device go into initialized state"); + // DO Something here + } + /* Spot "wakeup" pkts. reply woken message when in active mode */ + else if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_skb->data[4], wakeup_pkt, 2)) { + struct sk_buff *nskb = alloc_skb(2, GFP_ATOMIC); + + BT_ERR("Found a LE Wakeup pkt, and reply woken message"); + // DO Something here + + memcpy(skb_put(nskb, 2), woken_pkt, 2); + bt_cb(nskb)->pkt_type = H5_LE_PKT; + + skb_queue_head(&h5->unrel, nskb); + hci_uart_tx_wakeup(hu); + } + /* Spot "woken" pkts. receive woken message from device */ + else if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_skb->data[4], woken_pkt, 2)) { + BT_ERR("Found a LE woken pkt from device"); + // DO Something here + } + /* Spot "Sleep" pkts */ + else if (h5->rx_skb->data[1] >> 4 == 2 && h5->rx_skb->data[2] == 0 && + !memcmp(&h5->rx_indent: Standard input:620: Error:Unmatched 'else' +skb->data[4], sleep_pkt, 2)) { + BT_ERR("Found a LE Sleep pkt"); + // DO Something here + } +} +#endif + +static inline void h5_unslip_one_byte(struct h5_struct *h5, unsigned char byte) +{ + const u8 c0 = 0xc0, db = 0xdb; + const u8 oof1 = 0x11, oof2 = 0x13; + + switch (h5->rx_esc_state) { + case H5_ESCSTATE_NOESC: + switch (byte) { + case 0xdb: + h5->rx_esc_state = H5_ESCSTATE_ESC; + break; + default: + memcpy(skb_put(h5->rx_skb, 1), &byte, 1); + if ((h5->rx_skb->data[0] & 0x40) != 0 && + h5->rx_state != H5_W4_CRC) + h5_crc_update(&h5->message_crc, byte); + h5->rx_count--; + } + break; + + case H5_ESCSTATE_ESC: + switch (byte) { + case 0xdc: + memcpy(skb_put(h5->rx_skb, 1), &c0, 1); + if ((h5->rx_skb->data[0] & 0x40) != 0 && + h5->rx_state != H5_W4_CRC) + h5_crc_update(&h5->message_crc, 0xc0); + h5->rx_esc_state = H5_ESCSTATE_NOESC; + h5->rx_count--; + break; + + case 0xdd: + memcpy(skb_put(h5->rx_skb, 1), &db, 1); + if ((h5->rx_skb->data[0] & 0x40) != 0 && + h5->rx_state != H5_W4_CRC) + h5_crc_update(&h5->message_crc, 0xdb); + h5->rx_esc_state = H5_ESCSTATE_NOESC; + h5->rx_count--; + break; + + case 0xde: + memcpy(skb_put(h5->rx_skb, 1), &oof1, 1); + if ((h5->rx_skb->data[0] & 0x40) != 0 + && h5->rx_state != H5_W4_CRC) + h5_crc_update(&h5->message_crc, oof1); + h5->rx_esc_state = H5_ESCSTATE_NOESC; + h5->rx_count--; + break; + + case 0xdf: + memcpy(skb_put(h5->rx_skb, 1), &oof2, 1); + if ((h5->rx_skb->data[0] & 0x40) != 0 + && h5->rx_state != H5_W4_CRC) + h5_crc_update(&h5->message_crc, oof2); + h5->rx_esc_state = H5_ESCSTATE_NOESC; + h5->rx_count--; + break; + + default: + BT_ERR("Invalid byte %02x after esc byte", byte); + kfree_skb(h5->rx_skb); + h5->rx_skb = NULL; + h5->rx_state = H5_W4_PKT_DELIMITER; + h5->rx_count = 0; + } + } +} + +static void h5_complete_rx_pkt(struct hci_uart *hu) +{ + struct h5_struct *h5 = hu->priv; + int pass_up; + + if (h5->rx_skb->data[0] & 0x80) { /* reliable pkt */ + unsigned long flags; + u8 rxseq; + + spin_lock_irqsave(&h5->lock, flags); + rxseq = h5->rxseq_txack; + h5->rxseq_txack++; + h5->rxseq_txack %= 0x8; + h5->txack_req = 1; + spin_unlock_irqrestore(&h5->lock, flags); + + BT_DBG("Received seqno %u from card", rxseq); + } + + h5->rxack = (h5->rx_skb->data[0] >> 3) & 0x07; + BT_DBG("Request for pkt %u from card", h5->rxack); + + h5_pkt_cull(h5); + + hci_uart_tx_wakeup(hu); + + if ((h5->rx_skb->data[1] & 0x0f) == 2 && h5->rx_skb->data[0] & 0x80) { + bt_cb(h5->rx_skb)->pkt_type = HCI_ACLDATA_PKT; + pass_up = 1; + } else if ((h5->rx_skb->data[1] & 0x0f) == 4 && + h5->rx_skb->data[0] & 0x80) { + bt_cb(h5->rx_skb)->pkt_type = HCI_EVENT_PKT; + pass_up = 1; + } else if ((h5->rx_skb->data[1] & 0x0f) == 3) { + bt_cb(h5->rx_skb)->pkt_type = HCI_SCODATA_PKT; + pass_up = 1; + } else if ((h5->rx_skb->data[1] & 0x0f) == 15 && + !(h5->rx_skb->data[0] & 0x80)) { + //h5_handle_le_pkt(hu);//Link Establishment Pkt + pass_up = 0; + } else if ((h5->rx_skb->data[1] & 0x0f) == 1 && + h5->rx_skb->data[0] & 0x80) { + bt_cb(h5->rx_skb)->pkt_type = HCI_COMMAND_PKT; + pass_up = 1; + } else if ((h5->rx_skb->data[1] & 0x0f) == 14) { + bt_cb(h5->rx_skb)->pkt_type = H5_VDRSPEC_PKT; + pass_up = 1; + } else + pass_up = 0; + + if (!pass_up) { + /* struct hci_event_hdr hdr; */ + u8 desc = (h5->rx_skb->data[1] & 0x0f); + + if (desc != H5_ACK_PKT && desc != H5_LE_PKT) { + /* if (hciextn) { + * desc |= 0xc0; + * skb_pull(h5->rx_skb, 4); + * memcpy(skb_push(h5->rx_skb, 1), &desc, 1); + + * hdr.evt = 0xff; + * hdr.plen = h5->rx_skb->len; + * memcpy(skb_push(h5->rx_skb, HCI_EVENT_HDR_SIZE), + * &hdr, HCI_EVENT_HDR_SIZE); + * bt_cb(h5->rx_skb)->pkt_type = HCI_EVENT_PKT; + + * hci_recv_frame(h5->rx_skb); + * } else { */ + BT_ERR("Packet for unknown channel (%u %s)", + h5->rx_skb->data[1] & 0x0f, + h5->rx_skb->data[0] & 0x80 ? + "reliable" : "unreliable"); + kfree_skb(h5->rx_skb); + /* } */ + } else + kfree_skb(h5->rx_skb); + } else { + /* Pull out H5 hdr */ + skb_pull(h5->rx_skb, 4); + +#ifdef BTCOEX + if (bt_cb(h5->rx_skb)->pkt_type == HCI_EVENT_PKT) + rtk_btcoex_parse_event(h5->rx_skb->data, + h5->rx_skb->len); + + if (bt_cb(h5->rx_skb)->pkt_type == HCI_ACLDATA_PKT) + rtk_btcoex_parse_l2cap_data_rx(h5->rx_skb->data, + h5->rx_skb->len); +#endif + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + hci_recv_frame(h5->rx_skb); +#else + hci_recv_frame(hu->hdev, h5->rx_skb); +#endif + } + + h5->rx_state = H5_W4_PKT_DELIMITER; + h5->rx_skb = NULL; +} + +static u16 bscp_get_crc(struct h5_struct *h5) { + return get_unaligned_be16(&h5->rx_skb-> + data[h5->rx_skb->len - 2]); +} + +/* Recv data */ +static int h5_recv(struct hci_uart *hu, void *data, int count) +{ + struct h5_struct *h5 = hu->priv; + register unsigned char *ptr; + u8 rxseq; + unsigned long flags; + + BT_DBG("hu %p count %d rx_state %d rx_count %ld", + hu, count, h5->rx_state, h5->rx_count); + + ptr = data; + while (count) { + if (h5->rx_count) { + if (*ptr == 0xc0) { + BT_ERR("Short H5 packet"); + kfree_skb(h5->rx_skb); + h5->rx_state = H5_W4_PKT_START; + h5->rx_count = 0; + } else + h5_unslip_one_byte(h5, *ptr); + + ptr++; + count--; + continue; + } + + switch (h5->rx_state) { + case H5_W4_HDR: + if ((0xff & (u8) ~ + (h5->rx_skb->data[0] + + h5->rx_skb->data[1] + + h5->rx_skb->data[2])) != h5->rx_skb->data[3]) { + BT_ERR("Error in H5 hdr checksum"); + kfree_skb(h5->rx_skb); + h5->rx_state = H5_W4_PKT_DELIMITER; + h5->rx_count = 0; + continue; + } + rxseq = h5->rxseq_txack; + if (h5->rx_skb->data[0] & 0x80 /* reliable pkt */ + && (h5->rx_skb->data[0] & 0x07) != rxseq) { + BT_ERR("Out-of-order packet arrived, got %u expected %u", + h5->rx_skb->data[0] & 0x07, rxseq); + + spin_lock_irqsave(&h5->lock, flags); + h5->txack_req = 1; + spin_unlock_irqrestore(&h5->lock, flags); + hci_uart_tx_wakeup(hu); + kfree_skb(h5->rx_skb); + h5->rx_state = H5_W4_PKT_DELIMITER; + h5->rx_count = 0; + continue; + } + h5->rx_state = H5_W4_DATA; + h5->rx_count = (h5->rx_skb->data[1] >> 4) + (h5->rx_skb->data[2] << 4); /* May be 0 */ + continue; + + case H5_W4_DATA: + if (h5->rx_skb->data[0] & 0x40) { /* pkt with crc */ + h5->rx_state = H5_W4_CRC; + h5->rx_count = 2; + } else + h5_complete_rx_pkt(hu); + continue; + + case H5_W4_CRC: + if (bitrev16(h5->message_crc) != bscp_get_crc(h5)) { + BT_ERR + ("Checksum failed: computed %04x received %04x", + bitrev16(h5->message_crc), + bscp_get_crc(h5)); + + kfree_skb(h5->rx_skb); + h5->rx_state = H5_W4_PKT_DELIMITER; + h5->rx_count = 0; + continue; + } + skb_trim(h5->rx_skb, h5->rx_skb->len - 2); + h5_complete_rx_pkt(hu); + continue; + + case H5_W4_PKT_DELIMITER: + switch (*ptr) { + case 0xc0: + h5->rx_state = H5_W4_PKT_START; + break; + default: + /*BT_ERR("Ignoring byte %02x", *ptr); */ + break; + } + ptr++; + count--; + break; + + case H5_W4_PKT_START: + switch (*ptr) { + case 0xc0: + ptr++; + count--; + break; + + default: + h5->rx_state = H5_W4_HDR; + h5->rx_count = 4; + h5->rx_esc_state = H5_ESCSTATE_NOESC; + H5_CRC_INIT(h5->message_crc); + + /* Do not increment ptr or decrement count + * Allocate packet. Max len of a H5 pkt= + * 0xFFF (payload) +4 (header) +2 (crc) */ + + h5->rx_skb = bt_skb_alloc(0x1005, GFP_ATOMIC); + if (!h5->rx_skb) { + BT_ERR + ("Can't allocate mem for new packet"); + h5->rx_state = H5_W4_PKT_DELIMITER; + h5->rx_count = 0; + return 0; + } + h5->rx_skb->dev = (void *)hu->hdev; + break; + } + break; + } + } + return count; +} + +/* Arrange to retransmit all messages in the relq. */ +static void h5_timed_event(struct work_struct *work) +{ + struct h5_struct *h5; + struct hci_uart *hu; + unsigned long flags; + unsigned long flags2; + struct sk_buff *skb; + + h5 = container_of(work, struct h5_struct, retrans_work.work); + hu = h5->hu; + + BT_INFO("hu %p retransmitting %u pkts", hu, h5->unack.qlen); + + spin_lock_irqsave_nested(&h5->unack.lock, flags, SINGLE_DEPTH_NESTING); + + /* Move the pkt from unack queue to the head of reliable tx queue and + * roll back the tx seq number + */ + while ((skb = __skb_dequeue_tail(&h5->unack)) != NULL) { + spin_lock_irqsave(&h5->lock, flags2); + h5->msgq_txseq = (h5->msgq_txseq - 1) & 0x07; + spin_unlock_irqrestore(&h5->lock, flags2); + skb_queue_head(&h5->rel, skb); + } + + spin_unlock_irqrestore(&h5->unack.lock, flags); + + hci_uart_tx_wakeup(hu); +} + +static int h5_open(struct hci_uart *hu) +{ + struct h5_struct *h5; + + BT_DBG("hu %p", hu); + + BT_INFO("h5_open"); + h5 = kzalloc(sizeof(*h5), GFP_ATOMIC); + if (!h5) + return -ENOMEM; + + hu->priv = h5; + skb_queue_head_init(&h5->unack); + skb_queue_head_init(&h5->rel); + skb_queue_head_init(&h5->unrel); + spin_lock_init(&h5->lock); + + h5->hu = hu; + INIT_DELAYED_WORK(&h5->retrans_work, (void *)h5_timed_event); + + h5->rx_state = H5_W4_PKT_DELIMITER; + + if (txcrc) + h5->use_crc = 1; + + return 0; +} + +static int h5_close(struct hci_uart *hu) +{ + struct h5_struct *h5 = hu->priv; + + BT_INFO("h5_close"); + + cancel_delayed_work_sync(&h5->retrans_work); + + hu->priv = NULL; + + skb_queue_purge(&h5->unack); + skb_queue_purge(&h5->rel); + skb_queue_purge(&h5->unrel); + + kfree(h5); + + return 0; +} + +static struct hci_uart_proto h5 = { + .id = HCI_UART_3WIRE, + .open = h5_open, + .close = h5_close, + .enqueue = h5_enqueue, + .dequeue = h5_dequeue, + .recv = h5_recv, + .flush = h5_flush +}; + +int h5_init(void) +{ + int err = hci_uart_register_proto(&h5); + + if (!err) + BT_INFO("HCI Realtek H5 protocol initialized"); + else + BT_ERR("HCI Realtek H5 protocol registration failed"); + + return err; +} + +int h5_deinit(void) +{ + return hci_uart_unregister_proto(&h5); +} diff --git a/drivers/bluetooth/rtkbt/hci_uart.h b/drivers/bluetooth/rtkbt/hci_uart.h new file mode 100644 index 000000000..5f12dc2dd --- /dev/null +++ b/drivers/bluetooth/rtkbt/hci_uart.h @@ -0,0 +1,145 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include + +/* #define HCI_VERSION_CODE KERNEL_VERSION(3, 14, 41) */ +#define HCI_VERSION_CODE LINUX_VERSION_CODE + +#ifndef N_HCI +#define N_HCI 15 +#endif + +#ifndef CONFIG_BT_HCIUART_H4 +#define CONFIG_BT_HCIUART_H4 +#endif + +#define BTCOEX + +/* Send host sleep notification to Controller */ +#define WOBT_NOTIFY 0 /* 1 enable; 0 disable */ + +/* Send LE whitelist only for Background scan parameters */ +#define WOBT_NOTIFY_BG_SCAN_LE_WHITELIST_ONLY (0 * WOBT_NOTIFY) /* 1 enable; 0 disable */ + +/* RTKBT Power-on Whitelist for sideband wake-up by LE Advertising from Remote. +* Note that it's necessary to apply TV FW Patch. */ +#define RTKBT_TV_POWERON_WHITELIST (0 * WOBT_NOTIFY) /* 1 enable; 0 disable */ + +/* RTKBT Power-on Data Filter for Manufacturer field */ +/* Note that please edit the datafilter in + * rtkbt_set_le_device_poweron_data_filter() of hci_ldisc.c */ +#define RTKBT_TV_POWERON_DATA_FILTER (0 * WOBT_NOTIFY) /* 1 enable; 0 disable */ + +/* Ioctls */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) +#define HCIUARTSETFLAGS _IOW('U', 203, int) +#define HCIUARTGETFLAGS _IOR('U', 204, int) + +/* UART protocols */ +#define HCI_UART_MAX_PROTO 6 + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 +#define HCI_UART_LL 4 +#define HCI_UART_ATH3K 5 + +#define HCI_UART_RAW_DEVICE 0 +#define HCI_UART_RESET_ON_INIT 1 +#define HCI_UART_CREATE_AMP 2 +#define HCI_UART_INIT_PENDING 3 +#define HCI_UART_EXT_CONFIG 4 +#define HCI_UART_VND_DETECT 5 + +struct hci_uart; + +struct hci_uart_proto { + unsigned int id; + int (*open)(struct hci_uart *hu); + int (*close)(struct hci_uart *hu); + int (*flush)(struct hci_uart *hu); + int (*recv)(struct hci_uart *hu, void *data, int len); + int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb); + struct sk_buff *(*dequeue)(struct hci_uart *hu); +}; + +struct hci_uart { + struct tty_struct *tty; + struct hci_dev *hdev; + unsigned long flags; + unsigned long hdev_flags; + + struct work_struct write_work; + struct workqueue_struct *hci_uart_wq; + + struct hci_uart_proto *proto; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + struct percpu_rw_semaphore proto_lock; /* Stop work for proto close */ +#else + struct rw_semaphore proto_lock; +#endif + void *priv; + + struct semaphore tx_sem; /* semaphore for tx */ + + struct sk_buff *tx_skb; + unsigned long tx_state; + +#if WOBT_NOTIFY + struct notifier_block pm_notify_block; +#endif +}; + +/* HCI_UART proto flag bits */ +#define HCI_UART_PROTO_SET 0 +#define HCI_UART_REGISTERED 1 +#define HCI_UART_PROTO_READY 2 + +/* TX states */ +#define HCI_UART_SENDING 1 +#define HCI_UART_TX_WAKEUP 2 + +extern int hci_uart_register_proto(struct hci_uart_proto *p); +extern int hci_uart_unregister_proto(struct hci_uart_proto *p); +extern int hci_uart_tx_wakeup(struct hci_uart *hu); + +#ifdef CONFIG_BT_HCIUART_H4 +extern int h4_init(void); +extern int h4_deinit(void); +#endif + +extern int h5_init(void); +extern int h5_deinit(void); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) +extern int hci_uart_send_frame(struct sk_buff *skb); +#else +extern int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb); +#endif diff --git a/drivers/bluetooth/rtkbt/rtk_coex.c b/drivers/bluetooth/rtkbt/rtk_coex.c new file mode 100644 index 000000000..a46b722b5 --- /dev/null +++ b/drivers/bluetooth/rtkbt/rtk_coex.c @@ -0,0 +1,3068 @@ +/* +* +* Realtek Bluetooth USB driver +* +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* 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. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtk_coex.h" + +/* Software coex message can be sent to and receive from WiFi driver by + * UDP socket or exported symbol */ +/* #define RTK_COEX_OVER_SYMBOL */ + +#if BTRTL_HCI_IF == BTRTL_HCIUSB +#include +#include "rtk_bt.h" +#undef RTKBT_DBG +#undef RTKBT_INFO +#undef RTKBT_WARN +#undef RTKBT_ERR + +#elif BTRTL_HCI_IF == BTRTL_HCIUART +/* #define HCI_VERSION_CODE KERNEL_VERSION(3, 14, 41) */ +#define HCI_VERSION_CODE LINUX_VERSION_CODE + +#else +#error "Please set type of HCI interface" +#endif + +#define RTK_VERSION "1.2" + +#define RTKBT_DBG(fmt, arg...) printk(KERN_INFO "rtk_btcoex: " fmt "\n" , ## arg) +#define RTKBT_INFO(fmt, arg...) printk(KERN_INFO "rtk_btcoex: " fmt "\n" , ## arg) +#define RTKBT_WARN(fmt, arg...) printk(KERN_WARNING "rtk_btcoex: " fmt "\n", ## arg) +#define RTKBT_ERR(fmt, arg...) printk(KERN_WARNING "rtk_btcoex: " fmt "\n", ## arg) + +static struct rtl_coex_struct btrtl_coex; + +#ifdef RTB_SOFTWARE_MAILBOX +#ifdef RTK_COEX_OVER_SYMBOL +static struct sk_buff_head rtw_q; +static struct workqueue_struct *rtw_wq; +static struct work_struct rtw_work; +static u8 rtw_coex_on; +#endif +#endif + +#define is_profile_connected(profile) ((btrtl_coex.profile_bitmap & BIT(profile)) > 0) +#define is_profile_busy(profile) ((btrtl_coex.profile_status & BIT(profile)) > 0) + +#ifdef RTB_SOFTWARE_MAILBOX +static void rtk_handle_event_from_wifi(uint8_t * msg); +#endif + +static int rtl_alloc_buff(struct rtl_coex_struct *coex) +{ + struct rtl_hci_ev *ev; + struct rtl_l2_buff *l2; + int i; + int order; + unsigned long addr; + unsigned long addr2; + int ev_size; + int l2_size; + int n; + + spin_lock_init(&coex->buff_lock); + + INIT_LIST_HEAD(&coex->ev_used_list); + INIT_LIST_HEAD(&coex->ev_free_list); + + INIT_LIST_HEAD(&coex->l2_used_list); + INIT_LIST_HEAD(&coex->l2_free_list); + + n = NUM_RTL_HCI_EV * sizeof(struct rtl_hci_ev); + ev_size = ALIGN(n, sizeof(unsigned long)); + + n = L2_MAX_PKTS * sizeof(struct rtl_l2_buff); + l2_size = ALIGN(n, sizeof(unsigned long)); + + RTKBT_DBG("alloc buffers %d, %d for ev and l2", ev_size, l2_size); + + order = get_order(ev_size + l2_size); + addr = __get_free_pages(GFP_KERNEL, order); + if (!addr) { + RTKBT_ERR("failed to alloc buffers for ev and l2."); + return -ENOMEM; + } + memset((void *)addr, 0, ev_size + l2_size); + + coex->pages_addr = addr; + coex->buff_size = ev_size + l2_size; + + ev = (struct rtl_hci_ev *)addr; + for (i = 0; i < NUM_RTL_HCI_EV; i++) { + list_add_tail(&ev->list, &coex->ev_free_list); + ev++; + } + + addr2 = addr + ev_size; + l2 = (struct rtl_l2_buff *)addr2; + for (i = 0; i < L2_MAX_PKTS; i++) { + list_add_tail(&l2->list, &coex->l2_free_list); + l2++; + } + + return 0; +} + +static void rtl_free_buff(struct rtl_coex_struct *coex) +{ + struct rtl_hci_ev *ev; + struct rtl_l2_buff *l2; + unsigned long flags; + + spin_lock_irqsave(&coex->buff_lock, flags); + + while (!list_empty(&coex->ev_used_list)) { + ev = list_entry(coex->ev_used_list.next, struct rtl_hci_ev, + list); + list_del(&ev->list); + } + + while (!list_empty(&coex->ev_free_list)) { + ev = list_entry(coex->ev_free_list.next, struct rtl_hci_ev, + list); + list_del(&ev->list); + } + + while (!list_empty(&coex->l2_used_list)) { + l2 = list_entry(coex->l2_used_list.next, struct rtl_l2_buff, + list); + list_del(&l2->list); + } + + while (!list_empty(&coex->l2_free_list)) { + l2 = list_entry(coex->l2_free_list.next, struct rtl_l2_buff, + list); + list_del(&l2->list); + } + + spin_unlock_irqrestore(&coex->buff_lock, flags); + + if (coex->buff_size > 0) { + free_pages(coex->pages_addr, get_order(coex->buff_size)); + coex->pages_addr = 0; + coex->buff_size = 0; + } +} + +static struct rtl_hci_ev *rtl_ev_node_get(struct rtl_coex_struct *coex) +{ + struct rtl_hci_ev *ev; + unsigned long flags; + + if (!coex->buff_size) + return NULL; + + spin_lock_irqsave(&coex->buff_lock, flags); + if (!list_empty(&coex->ev_free_list)) { + ev = list_entry(coex->ev_free_list.next, struct rtl_hci_ev, + list); + list_del(&ev->list); + } else + ev = NULL; + spin_unlock_irqrestore(&coex->buff_lock, flags); + return ev; +} + +static int rtl_ev_node_to_used(struct rtl_coex_struct *coex, + struct rtl_hci_ev *ev) +{ + unsigned long flags; + + spin_lock_irqsave(&coex->buff_lock, flags); + list_add_tail(&ev->list, &coex->ev_used_list); + spin_unlock_irqrestore(&coex->buff_lock, flags); + + return 0; +} + +static struct rtl_l2_buff *rtl_l2_node_get(struct rtl_coex_struct *coex) +{ + struct rtl_l2_buff *l2; + unsigned long flags; + + if (!coex->buff_size) + return NULL; + + spin_lock_irqsave(&coex->buff_lock, flags); + + if(!list_empty(&coex->l2_free_list)) { + l2 = list_entry(coex->l2_free_list.next, struct rtl_l2_buff, + list); + list_del(&l2->list); + } else + l2 = NULL; + + spin_unlock_irqrestore(&coex->buff_lock, flags); + return l2; +} + +static int rtl_l2_node_to_used(struct rtl_coex_struct *coex, + struct rtl_l2_buff *l2) +{ + unsigned long flags; + + spin_lock_irqsave(&coex->buff_lock, flags); + list_add_tail(&l2->list, &coex->l2_used_list); + spin_unlock_irqrestore(&coex->buff_lock, flags); + + return 0; +} + +static int8_t psm_to_profile_index(uint16_t psm) +{ + switch (psm) { + case PSM_AVCTP: + case PSM_SDP: + return -1; //ignore + + case PSM_HID: + case PSM_HID_INT: + return profile_hid; + + case PSM_AVDTP: + return profile_a2dp; + + case PSM_PAN: + case PSM_OPP: + case PSM_FTP: + case PSM_BIP: + case PSM_RFCOMM: + return profile_pan; + + default: + return profile_pan; + } +} + +static rtk_prof_info *find_by_psm(u16 psm) +{ + struct list_head *head = &btrtl_coex.profile_list; + struct list_head *iter = NULL; + struct list_head *temp = NULL; + rtk_prof_info *desc = NULL; + + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_prof_info, list); + if (desc->psm == psm) + return desc; + } + + return NULL; +} + +static void rtk_check_setup_timer(int8_t profile_index) +{ + if (profile_index == profile_a2dp) { + btrtl_coex.a2dp_packet_count = 0; + btrtl_coex.a2dp_count_timer.expires = + jiffies + msecs_to_jiffies(1000); + mod_timer(&btrtl_coex.a2dp_count_timer, + btrtl_coex.a2dp_count_timer.expires); + } + + if (profile_index == profile_pan) { + btrtl_coex.pan_packet_count = 0; + btrtl_coex.pan_count_timer.expires = + jiffies + msecs_to_jiffies(1000); + mod_timer(&btrtl_coex.pan_count_timer, + btrtl_coex.pan_count_timer.expires); + } + + /* hogp & voice share one timer now */ + if ((profile_index == profile_hogp) || (profile_index == profile_voice)) { + if ((0 == btrtl_coex.profile_refcount[profile_hogp]) + && (0 == btrtl_coex.profile_refcount[profile_voice])) { + btrtl_coex.hogp_packet_count = 0; + btrtl_coex.voice_packet_count = 0; + btrtl_coex.hogp_count_timer.expires = + jiffies + msecs_to_jiffies(1000); + mod_timer(&btrtl_coex.hogp_count_timer, + btrtl_coex.hogp_count_timer.expires); + } + } +} + +static void rtk_check_del_timer(int8_t profile_index) +{ + if (profile_a2dp == profile_index) { + btrtl_coex.a2dp_packet_count = 0; + del_timer_sync(&btrtl_coex.a2dp_count_timer); + } + if (profile_pan == profile_index) { + btrtl_coex.pan_packet_count = 0; + del_timer_sync(&btrtl_coex.pan_count_timer); + } + if (profile_hogp == profile_index) { + btrtl_coex.hogp_packet_count = 0; + if (btrtl_coex.profile_refcount[profile_voice] == 0) { + del_timer_sync(&btrtl_coex.hogp_count_timer); + } + } + if (profile_voice == profile_index) { + btrtl_coex.voice_packet_count = 0; + if (btrtl_coex.profile_refcount[profile_hogp] == 0) { + del_timer_sync(&btrtl_coex.hogp_count_timer); + } + } +} + + + +static rtk_conn_prof *find_connection_by_handle(struct rtl_coex_struct * coex, + uint16_t handle) +{ + struct list_head *head = &coex->conn_hash; + struct list_head *iter = NULL, *temp = NULL; + rtk_conn_prof *desc = NULL; + + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_conn_prof, list); + if ((handle & 0xEFF) == desc->handle) { + return desc; + } + } + return NULL; +} + +static rtk_conn_prof *allocate_connection_by_handle(uint16_t handle) +{ + rtk_conn_prof *phci_conn = NULL; + phci_conn = kmalloc(sizeof(rtk_conn_prof), GFP_ATOMIC); + if (phci_conn) + phci_conn->handle = handle; + + return phci_conn; +} + +static void init_connection_hash(struct rtl_coex_struct * coex) +{ + struct list_head *head = &coex->conn_hash; + INIT_LIST_HEAD(head); +} + +static void add_connection_to_hash(struct rtl_coex_struct * coex, + rtk_conn_prof * desc) +{ + struct list_head *head = &coex->conn_hash; + list_add_tail(&desc->list, head); +} + +static void delete_connection_from_hash(rtk_conn_prof * desc) +{ + if (desc) { + list_del(&desc->list); + kfree(desc); + } +} + +static void flush_connection_hash(struct rtl_coex_struct * coex) +{ + struct list_head *head = &coex->conn_hash; + struct list_head *iter = NULL, *temp = NULL; + rtk_conn_prof *desc = NULL; + + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_conn_prof, list); + if (desc) { + list_del(&desc->list); + kfree(desc); + } + } + //INIT_LIST_HEAD(head); +} + +static void init_profile_hash(struct rtl_coex_struct * coex) +{ + struct list_head *head = &coex->profile_list; + INIT_LIST_HEAD(head); +} + +static uint8_t list_allocate_add(uint16_t handle, uint16_t psm, + int8_t profile_index, uint16_t dcid, + uint16_t scid) +{ + rtk_prof_info *pprof_info = NULL; + + if (profile_index < 0) { + RTKBT_ERR("PSM 0x%x do not need parse", psm); + return FALSE; + } + + pprof_info = kmalloc(sizeof(rtk_prof_info), GFP_ATOMIC); + + if (NULL == pprof_info) { + RTKBT_ERR("list_allocate_add: allocate error"); + return FALSE; + } + + /* Check if it is the second l2cap connection for a2dp + * a2dp signal channel will be created first than media channel. + */ + if (psm == PSM_AVDTP) { + rtk_prof_info *pinfo = find_by_psm(psm); + if (!pinfo) { + pprof_info->flags = A2DP_SIGNAL; + RTKBT_INFO("%s: Add a2dp signal channel", __func__); + } else { + pprof_info->flags = A2DP_MEDIA; + RTKBT_INFO("%s: Add a2dp media channel", __func__); + } + } + + pprof_info->handle = handle; + pprof_info->psm = psm; + pprof_info->scid = scid; + pprof_info->dcid = dcid; + pprof_info->profile_index = profile_index; + list_add_tail(&(pprof_info->list), &(btrtl_coex.profile_list)); + + return TRUE; +} + +static void delete_profile_from_hash(rtk_prof_info * desc) +{ + RTKBT_DBG("Delete profile: hndl 0x%04x, psm 0x%04x, dcid 0x%04x, " + "scid 0x%04x", desc->handle, desc->psm, desc->dcid, + desc->scid); + if (desc) { + list_del(&desc->list); + kfree(desc); + desc = NULL; + } +} + +static void flush_profile_hash(struct rtl_coex_struct * coex) +{ + struct list_head *head = &coex->profile_list; + struct list_head *iter = NULL, *temp = NULL; + rtk_prof_info *desc = NULL; + + spin_lock(&btrtl_coex.spin_lock_profile); + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_prof_info, list); + delete_profile_from_hash(desc); + } + //INIT_LIST_HEAD(head); + spin_unlock(&btrtl_coex.spin_lock_profile); +} + +static rtk_prof_info *find_profile_by_handle_scid(struct rtl_coex_struct * + coex, uint16_t handle, + uint16_t scid) +{ + struct list_head *head = &coex->profile_list; + struct list_head *iter = NULL, *temp = NULL; + rtk_prof_info *desc = NULL; + + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_prof_info, list); + if (((handle & 0xFFF) == desc->handle) && (scid == desc->scid)) { + return desc; + } + } + return NULL; +} + +static rtk_prof_info *find_profile_by_handle_dcid(struct rtl_coex_struct * + coex, uint16_t handle, + uint16_t dcid) +{ + struct list_head *head = &coex->profile_list; + struct list_head *iter = NULL, *temp = NULL; + rtk_prof_info *desc = NULL; + + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_prof_info, list); + if (((handle & 0xFFF) == desc->handle) && (dcid == desc->dcid)) { + return desc; + } + } + return NULL; +} + +static rtk_prof_info *find_profile_by_handle_dcid_scid(struct rtl_coex_struct + * coex, uint16_t handle, + uint16_t dcid, + uint16_t scid) +{ + struct list_head *head = &coex->profile_list; + struct list_head *iter = NULL, *temp = NULL; + rtk_prof_info *desc = NULL; + + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_prof_info, list); + if (((handle & 0xFFF) == desc->handle) && (dcid == desc->dcid) + && (scid == desc->scid)) { + return desc; + } + } + return NULL; +} + +static void rtk_vendor_cmd_to_fw(uint16_t opcode, uint8_t parameter_len, + uint8_t * parameter) +{ + int len = HCI_CMD_PREAMBLE_SIZE + parameter_len; + uint8_t *p; + struct sk_buff *skb; + struct hci_dev *hdev = btrtl_coex.hdev; + + if (!hdev) { + RTKBT_ERR("No HCI device"); + return; + } else if (!test_bit(HCI_UP, &hdev->flags)) { + RTKBT_WARN("HCI device is down"); + return; + } + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + RTKBT_DBG("there is no room for cmd 0x%x", opcode); + return; + } + + p = (uint8_t *) skb_put(skb, HCI_CMD_PREAMBLE_SIZE); + UINT16_TO_STREAM(p, opcode); + *p++ = parameter_len; + + if (parameter_len) + memcpy(skb_put(skb, parameter_len), parameter, parameter_len); + + bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; + +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + bt_cb(skb)->opcode = opcode; +#else + bt_cb(skb)->hci.opcode = opcode; +#endif +#endif + + /* Stand-alone HCI commands must be flagged as + * single-command requests. + */ +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + bt_cb(skb)->req.start = true; +#else + +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 5, 0) + bt_cb(skb)->hci.req_start = true; +#else + + bt_cb(skb)->hci.req_flags |= HCI_REQ_START; +#endif + +#endif /* 4.4.0 */ +#endif /* 3.10.0 */ + RTKBT_DBG("%s: opcode 0x%x", __func__, opcode); + + /* It is harmless if set skb->dev twice. The dev will be used in + * btusb_send_frame() after or equal to kernel/hci 3.13.0, + * the hdev will not come from skb->dev. */ +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + skb->dev = (void *)btrtl_coex.hdev; +#endif + /* Put the skb to the global hdev->cmd_q */ + skb_queue_tail(&hdev->cmd_q, skb); + +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + tasklet_schedule(&hdev->cmd_task); +#else + queue_work(hdev->workqueue, &hdev->cmd_work); +#endif + + return; +} + +static void rtk_notify_profileinfo_to_fw(void) +{ + struct list_head *head = NULL; + struct list_head *iter = NULL; + struct list_head *temp = NULL; + rtk_conn_prof *hci_conn = NULL; + uint8_t handle_number = 0; + uint32_t buffer_size = 0; + uint8_t *p_buf = NULL; + uint8_t *p = NULL; + + head = &btrtl_coex.conn_hash; + list_for_each_safe(iter, temp, head) { + hci_conn = list_entry(iter, rtk_conn_prof, list); + if (hci_conn && hci_conn->profile_bitmap) + handle_number++; + } + + buffer_size = 1 + handle_number * 3 + 1; + + p_buf = kmalloc(buffer_size, GFP_ATOMIC); + + if (NULL == p_buf) { + RTKBT_ERR("%s: alloc error", __func__); + return; + } + p = p_buf; + + RTKBT_DBG("%s: BufferSize %u", __func__, buffer_size); + *p++ = handle_number; + RTKBT_DBG("%s: NumberOfHandles %u", __func__, handle_number); + head = &btrtl_coex.conn_hash; + list_for_each(iter, head) { + hci_conn = list_entry(iter, rtk_conn_prof, list); + if (hci_conn && hci_conn->profile_bitmap) { + UINT16_TO_STREAM(p, hci_conn->handle); + RTKBT_DBG("%s: handle 0x%04x", __func__, + hci_conn->handle); + *p++ = hci_conn->profile_bitmap; + RTKBT_DBG("%s: profile_bitmap 0x%02x", __func__, + hci_conn->profile_bitmap); + handle_number--; + } + if (0 == handle_number) + break; + } + + *p++ = btrtl_coex.profile_status; + RTKBT_DBG("%s: profile_status 0x%02x", __func__, + btrtl_coex.profile_status); + + rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_PROFILE_REPORT_COMMAND, buffer_size, + p_buf); + + kfree(p_buf); + return; +} + +static void update_profile_state(uint8_t profile_index, uint8_t is_busy) +{ + uint8_t need_update = FALSE; + + if ((btrtl_coex.profile_bitmap & BIT(profile_index)) == 0) { + RTKBT_ERR("%s: : ERROR!!! profile(Index: %x) does not exist", + __func__, profile_index); + return; + } + + if (is_busy) { + if ((btrtl_coex.profile_status & BIT(profile_index)) == 0) { + need_update = TRUE; + btrtl_coex.profile_status |= BIT(profile_index); + } + } else { + if ((btrtl_coex.profile_status & BIT(profile_index)) > 0) { + need_update = TRUE; + btrtl_coex.profile_status &= ~(BIT(profile_index)); + } + } + + if (need_update) { + RTKBT_DBG("%s: btrtl_coex.profie_bitmap = %x", + __func__, btrtl_coex.profile_bitmap); + RTKBT_DBG("%s: btrtl_coex.profile_status = %x", + __func__, btrtl_coex.profile_status); + rtk_notify_profileinfo_to_fw(); + } +} + +static void update_profile_connection(rtk_conn_prof * phci_conn, + int8_t profile_index, uint8_t is_add) +{ + uint8_t need_update = FALSE; + uint8_t kk; + + RTKBT_DBG("%s: is_add %d, profile_index %x", __func__, + is_add, profile_index); + if (profile_index < 0) + return; + + if (is_add) { + if (btrtl_coex.profile_refcount[profile_index] == 0) { + need_update = TRUE; + btrtl_coex.profile_bitmap |= BIT(profile_index); + + /* SCO is always busy */ + if (profile_index == profile_sco) + btrtl_coex.profile_status |= + BIT(profile_index); + + rtk_check_setup_timer(profile_index); + } + btrtl_coex.profile_refcount[profile_index]++; + + if (0 == phci_conn->profile_refcount[profile_index]) { + need_update = TRUE; + phci_conn->profile_bitmap |= BIT(profile_index); + } + phci_conn->profile_refcount[profile_index]++; + } else { + if (!btrtl_coex.profile_refcount[profile_index]) { + RTKBT_WARN("profile %u refcount is already zero", + profile_index); + return; + } + btrtl_coex.profile_refcount[profile_index]--; + RTKBT_DBG("%s: btrtl_coex.profile_refcount[%x] = %x", + __func__, profile_index, + btrtl_coex.profile_refcount[profile_index]); + if (btrtl_coex.profile_refcount[profile_index] == 0) { + need_update = TRUE; + btrtl_coex.profile_bitmap &= ~(BIT(profile_index)); + + /* if profile does not exist, status is meaningless */ + btrtl_coex.profile_status &= ~(BIT(profile_index)); + rtk_check_del_timer(profile_index); + } + + phci_conn->profile_refcount[profile_index]--; + if (0 == phci_conn->profile_refcount[profile_index]) { + need_update = TRUE; + phci_conn->profile_bitmap &= ~(BIT(profile_index)); + + /* clear profile_hid_interval if need */ + if ((profile_hid == profile_index) + && (phci_conn-> + profile_bitmap & (BIT(profile_hid_interval)))) { + phci_conn->profile_bitmap &= + ~(BIT(profile_hid_interval)); + btrtl_coex. + profile_refcount[profile_hid_interval]--; + } + } + } + + RTKBT_DBG("%s: btrtl_coex.profile_bitmap 0x%02x", __func__, + btrtl_coex.profile_bitmap); + for (kk = 0; kk < 8; kk++) + RTKBT_DBG("%s: btrtl_coex.profile_refcount[%d] = %d", + __func__, kk, + btrtl_coex.profile_refcount[kk]); + + if (need_update) + rtk_notify_profileinfo_to_fw(); +} + +static void update_hid_active_state(uint16_t handle, uint16_t interval) +{ + uint8_t need_update = 0; + rtk_conn_prof *phci_conn = + find_connection_by_handle(&btrtl_coex, handle); + + if (phci_conn == NULL) + return; + + RTKBT_DBG("%s: handle 0x%04x, interval %u", __func__, handle, interval); + if (((phci_conn->profile_bitmap) & (BIT(profile_hid))) == 0) { + RTKBT_DBG("HID not connected, nothing to be down"); + return; + } + + if (interval < 60) { + if ((phci_conn->profile_bitmap & (BIT(profile_hid_interval))) == + 0) { + need_update = 1; + phci_conn->profile_bitmap |= BIT(profile_hid_interval); + + btrtl_coex.profile_refcount[profile_hid_interval]++; + if (btrtl_coex. + profile_refcount[profile_hid_interval] == 1) + btrtl_coex.profile_status |= + BIT(profile_hid); + } + } else { + if ((phci_conn->profile_bitmap & (BIT(profile_hid_interval)))) { + need_update = 1; + phci_conn->profile_bitmap &= + ~(BIT(profile_hid_interval)); + + btrtl_coex.profile_refcount[profile_hid_interval]--; + if (btrtl_coex. + profile_refcount[profile_hid_interval] == 0) + btrtl_coex.profile_status &= + ~(BIT(profile_hid)); + } + } + + if (need_update) + rtk_notify_profileinfo_to_fw(); +} + +static uint8_t handle_l2cap_con_req(uint16_t handle, uint16_t psm, + uint16_t scid, uint8_t direction) +{ + uint8_t status = FALSE; + rtk_prof_info *prof_info = NULL; + int8_t profile_index = psm_to_profile_index(psm); + + if (profile_index < 0) { + RTKBT_DBG("PSM(0x%04x) do not need parse", psm); + return status; + } + + spin_lock(&btrtl_coex.spin_lock_profile); + if (direction) //1: out + prof_info = + find_profile_by_handle_scid(&btrtl_coex, handle, scid); + else // 0:in + prof_info = + find_profile_by_handle_dcid(&btrtl_coex, handle, scid); + + if (prof_info) { + RTKBT_DBG("%s: this profile is already exist!", __func__); + spin_unlock(&btrtl_coex.spin_lock_profile); + return status; + } + + if (direction) //1: out + status = list_allocate_add(handle, psm, profile_index, 0, scid); + else // 0:in + status = list_allocate_add(handle, psm, profile_index, scid, 0); + + spin_unlock(&btrtl_coex.spin_lock_profile); + + if (!status) + RTKBT_ERR("%s: list_allocate_add failed!", __func__); + + return status; +} + +static uint8_t handle_l2cap_con_rsp(uint16_t handle, uint16_t dcid, + uint16_t scid, uint8_t direction, + uint8_t result) +{ + rtk_prof_info *prof_info = NULL; + rtk_conn_prof *phci_conn = NULL; + + spin_lock(&btrtl_coex.spin_lock_profile); + if (!direction) //0, in + prof_info = + find_profile_by_handle_scid(&btrtl_coex, handle, scid); + else //1, out + prof_info = + find_profile_by_handle_dcid(&btrtl_coex, handle, scid); + + if (!prof_info) { + //RTKBT_DBG("handle_l2cap_con_rsp: prof_info Not Find!!"); + spin_unlock(&btrtl_coex.spin_lock_profile); + return FALSE; + } + + if (!result) { //success + RTKBT_DBG("l2cap connection success, update connection"); + if (!direction) //0, in + prof_info->dcid = dcid; + else //1, out + prof_info->scid = dcid; + + phci_conn = find_connection_by_handle(&btrtl_coex, handle); + if (phci_conn) + update_profile_connection(phci_conn, + prof_info->profile_index, + TRUE); + } + + spin_unlock(&btrtl_coex.spin_lock_profile); + return TRUE; +} + +static uint8_t handle_l2cap_discon_req(uint16_t handle, uint16_t dcid, + uint16_t scid, uint8_t direction) +{ + rtk_prof_info *prof_info = NULL; + rtk_conn_prof *phci_conn = NULL; + RTKBT_DBG("%s: handle 0x%04x, dcid 0x%04x, scid 0x%04x, dir %u", + __func__, handle, dcid, scid, direction); + + spin_lock(&btrtl_coex.spin_lock_profile); + if (!direction) //0: in + prof_info = + find_profile_by_handle_dcid_scid(&btrtl_coex, handle, + scid, dcid); + else //1: out + prof_info = + find_profile_by_handle_dcid_scid(&btrtl_coex, handle, + dcid, scid); + + if (!prof_info) { + //LogMsg("handle_l2cap_discon_req: prof_info Not Find!"); + spin_unlock(&btrtl_coex.spin_lock_profile); + return 0; + } + + phci_conn = find_connection_by_handle(&btrtl_coex, handle); + if (!phci_conn) { + spin_unlock(&btrtl_coex.spin_lock_profile); + return 0; + } + + update_profile_connection(phci_conn, prof_info->profile_index, FALSE); + if (prof_info->profile_index == profile_a2dp && + (phci_conn->profile_bitmap & BIT(profile_sink))) + update_profile_connection(phci_conn, profile_sink, FALSE); + + delete_profile_from_hash(prof_info); + spin_unlock(&btrtl_coex.spin_lock_profile); + + return 1; +} + +static const char sample_freqs[4][8] = { + "16", "32", "44.1", "48" +}; + +static const uint8_t sbc_blocks[4] = { 4, 8, 12, 16 }; + +static const char chan_modes[4][16] = { + "MONO", "DUAL_CHANNEL", "STEREO", "JOINT_STEREO" +}; + +static const char alloc_methods[2][12] = { + "LOUDNESS", "SNR" +}; + +static const uint8_t subbands[2] = { 4, 8 }; + +void print_sbc_header(struct sbc_frame_hdr *hdr) +{ + RTKBT_DBG("syncword: %02x", hdr->syncword); + RTKBT_DBG("freq %skHz", sample_freqs[hdr->sampling_frequency]); + RTKBT_DBG("blocks %u", sbc_blocks[hdr->blocks]); + RTKBT_DBG("channel mode %s", chan_modes[hdr->channel_mode]); + RTKBT_DBG("allocation method %s", + alloc_methods[hdr->allocation_method]); + RTKBT_DBG("subbands %u", subbands[hdr->subbands]); +} + +static void packets_count(uint16_t handle, uint16_t scid, uint16_t length, + uint8_t direction, u8 *user_data) +{ + rtk_prof_info *prof_info = NULL; + + rtk_conn_prof *hci_conn = + find_connection_by_handle(&btrtl_coex, handle); + if (NULL == hci_conn) + return; + + if (0 == hci_conn->type) { + if (!direction) //0: in + prof_info = + find_profile_by_handle_scid(&btrtl_coex, handle, + scid); + else //1: out + prof_info = + find_profile_by_handle_dcid(&btrtl_coex, handle, + scid); + + if (!prof_info) { + //RTKBT_DBG("packets_count: prof_info Not Find!"); + return; + } + + /* avdtp media data */ + if (prof_info->profile_index == profile_a2dp && + prof_info->flags == A2DP_MEDIA) { + if (!is_profile_busy(profile_a2dp)) { + struct sbc_frame_hdr *sbc_header; + struct rtp_header *rtph; + u8 bitpool; + + update_profile_state(profile_a2dp, TRUE); + if (!direction) { + if (!(hci_conn->profile_bitmap & BIT(profile_sink))) { + btrtl_coex.profile_bitmap |= BIT(profile_sink); + hci_conn->profile_bitmap |= BIT(profile_sink); + update_profile_connection(hci_conn, profile_sink, 1); + } + update_profile_state(profile_sink, TRUE); + } + + /* We assume it is SBC if the packet length + * is bigger than 100 bytes + */ + if (length > 100) { + RTKBT_INFO("Length %u", length); + rtph = (struct rtp_header *)user_data; + + RTKBT_DBG("rtp: v %u, cc %u, pt %u", + rtph->v, rtph->cc, rtph->pt); + /* move forward */ + user_data += sizeof(struct rtp_header) + + rtph->cc * 4 + 1; + + /* point to the sbc frame header */ + sbc_header = (struct sbc_frame_hdr *)user_data; + bitpool = sbc_header->bitpool; + + print_sbc_header(sbc_header); + + RTKBT_DBG("bitpool %u", bitpool); + + rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_BITPOOL, + 1, &bitpool); + } + } + btrtl_coex.a2dp_packet_count++; + } + + if (prof_info->profile_index == profile_pan) + btrtl_coex.pan_packet_count++; + } +} + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 14, 0) +static void count_a2dp_packet_timeout(struct timer_list *unused) +#else +static void count_a2dp_packet_timeout(unsigned long data) +#endif +{ + if (btrtl_coex.a2dp_packet_count) + RTKBT_DBG("%s: a2dp_packet_count %d", __func__, + btrtl_coex.a2dp_packet_count); + if (btrtl_coex.a2dp_packet_count == 0) { + if (is_profile_busy(profile_a2dp)) { + RTKBT_DBG("%s: a2dp busy->idle!", __func__); + update_profile_state(profile_a2dp, FALSE); + if (btrtl_coex.profile_bitmap & BIT(profile_sink)) + update_profile_state(profile_sink, FALSE); + } + } + btrtl_coex.a2dp_packet_count = 0; + mod_timer(&btrtl_coex.a2dp_count_timer, + jiffies + msecs_to_jiffies(1000)); +} + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 14, 0) +static void count_pan_packet_timeout(struct timer_list *unused) +#else +static void count_pan_packet_timeout(unsigned long data) +#endif +{ + if (btrtl_coex.pan_packet_count) + RTKBT_DBG("%s: pan_packet_count %d", __func__, + btrtl_coex.pan_packet_count); + if (btrtl_coex.pan_packet_count < PAN_PACKET_COUNT) { + if (is_profile_busy(profile_pan)) { + RTKBT_DBG("%s: pan busy->idle!", __func__); + update_profile_state(profile_pan, FALSE); + } + } else { + if (!is_profile_busy(profile_pan)) { + RTKBT_DBG("timeout_handler: pan idle->busy!"); + update_profile_state(profile_pan, TRUE); + } + } + btrtl_coex.pan_packet_count = 0; + mod_timer(&btrtl_coex.pan_count_timer, + jiffies + msecs_to_jiffies(1000)); +} + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 14, 0) +static void count_hogp_packet_timeout(struct timer_list *unused) +#else +static void count_hogp_packet_timeout(unsigned long data) +#endif +{ + if (btrtl_coex.hogp_packet_count) + RTKBT_DBG("%s: hogp_packet_count %d", __func__, + btrtl_coex.hogp_packet_count); + if (btrtl_coex.hogp_packet_count == 0) { + if (is_profile_busy(profile_hogp)) { + RTKBT_DBG("%s: hogp busy->idle!", __func__); + update_profile_state(profile_hogp, FALSE); + } + } + btrtl_coex.hogp_packet_count = 0; + + if (btrtl_coex.voice_packet_count) + RTKBT_DBG("%s: voice_packet_count %d", __func__, + btrtl_coex.voice_packet_count); + if (btrtl_coex.voice_packet_count == 0) { + if (is_profile_busy(profile_voice)) { + RTKBT_DBG("%s: voice busy->idle!", __func__); + update_profile_state(profile_voice, FALSE); + } + } + btrtl_coex.voice_packet_count = 0; + mod_timer(&btrtl_coex.hogp_count_timer, + jiffies + msecs_to_jiffies(1000)); +} + +#ifdef RTB_SOFTWARE_MAILBOX + +#ifndef RTK_COEX_OVER_SYMBOL +static int udpsocket_send(char *tx_msg, int msg_size) +{ + u8 error = 0; + struct msghdr udpmsg; + mm_segment_t oldfs; + struct iovec iov; + + RTKBT_DBG("send msg %s with len:%d", tx_msg, msg_size); + + if (btrtl_coex.sock_open) { + iov.iov_base = (void *)tx_msg; + iov.iov_len = msg_size; + udpmsg.msg_name = &btrtl_coex.wifi_addr; + udpmsg.msg_namelen = sizeof(struct sockaddr_in); +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) + udpmsg.msg_iov = &iov; + udpmsg.msg_iovlen = 1; +#else + iov_iter_init(&udpmsg.msg_iter, WRITE, &iov, 1, msg_size); +#endif + udpmsg.msg_control = NULL; + udpmsg.msg_controllen = 0; + udpmsg.msg_flags = MSG_DONTWAIT | MSG_NOSIGNAL; + oldfs = get_fs(); + set_fs(KERNEL_DS); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) + error = sock_sendmsg(btrtl_coex.udpsock, &udpmsg, msg_size); +#else + error = sock_sendmsg(btrtl_coex.udpsock, &udpmsg); +#endif + set_fs(oldfs); + + if (error < 0) + RTKBT_DBG("Error when sendimg msg, error:%d", error); + } + + return error; +} +#endif + +#ifdef RTK_COEX_OVER_SYMBOL +/* Receive message from WiFi */ +u8 rtw_btcoex_wifi_to_bt(u8 *msg, u8 msg_size) +{ + struct sk_buff *nskb; + + if (!rtw_coex_on) { + RTKBT_WARN("Bluetooth is closed"); + return 0; + } + + nskb = alloc_skb(msg_size, GFP_ATOMIC); + if (!nskb) { + RTKBT_ERR("Couldnt alloc skb for WiFi coex message"); + return 0; + } + + memcpy(skb_put(nskb, msg_size), msg, msg_size); + skb_queue_tail(&rtw_q, nskb); + + queue_work(rtw_wq, &rtw_work); + + return 1; +} +EXPORT_SYMBOL(rtw_btcoex_wifi_to_bt); + +static int rtk_send_coexmsg2wifi(u8 *msg, u8 size) +{ + u8 result; + u8 (*btmsg_to_wifi)(u8 *, u8); + + btmsg_to_wifi = __symbol_get(VMLINUX_SYMBOL_STR(rtw_btcoex_bt_to_wifi)); + + if (!btmsg_to_wifi) { + /* RTKBT_ERR("Couldnt get symbol"); */ + return -1; + } + + result = btmsg_to_wifi(msg, size); + __symbol_put(VMLINUX_SYMBOL_STR(rtw_btcoex_bt_to_wifi)); + if (!result) { + RTKBT_ERR("Couldnt send coex msg to WiFi"); + return -1; + } else if (result == 1){ + /* successful to send message */ + return 0; + } else { + RTKBT_ERR("Unknown result %d", result); + return -1; + } +} + +static int rtkbt_process_coexskb(struct sk_buff *skb) +{ + rtk_handle_event_from_wifi(skb->data); + return 0; +} + +static void rtw_work_func(struct work_struct *work) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(&rtw_q))) { + rtkbt_process_coexskb(skb); + kfree_skb(skb); + } +} + +#endif + +static int rtkbt_coexmsg_send(char *tx_msg, int msg_size) +{ +#ifdef RTK_COEX_OVER_SYMBOL + return rtk_send_coexmsg2wifi((uint8_t *)tx_msg, (u8)msg_size); +#else + return udpsocket_send(tx_msg, msg_size); +#endif +} + +#ifndef RTK_COEX_OVER_SYMBOL +static void udpsocket_recv_data(void) +{ + u8 recv_data[512]; + u32 len = 0; + u16 recv_length; + struct sk_buff *skb; + + RTKBT_DBG("-"); + + spin_lock(&btrtl_coex.spin_lock_sock); + len = skb_queue_len(&btrtl_coex.sk->sk_receive_queue); + + while (len > 0) { + skb = skb_dequeue(&btrtl_coex.sk->sk_receive_queue); + + /*important: cut the udp header from skb->data! header length is 8 byte */ + recv_length = skb->len - 8; + memset(recv_data, 0, sizeof(recv_data)); + memcpy(recv_data, skb->data + 8, recv_length); + //RTKBT_DBG("received data: %s :with len %u", recv_data, recv_length); + + rtk_handle_event_from_wifi(recv_data); + + len--; + kfree_skb(skb); + } + + spin_unlock(&btrtl_coex.spin_lock_sock); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0) +static void udpsocket_recv(struct sock *sk, int bytes) +#else +static void udpsocket_recv(struct sock *sk) +#endif +{ + spin_lock(&btrtl_coex.spin_lock_sock); + btrtl_coex.sk = sk; + spin_unlock(&btrtl_coex.spin_lock_sock); + queue_delayed_work(btrtl_coex.sock_wq, &btrtl_coex.sock_work, 0); +} + +static void create_udpsocket(void) +{ + int err; + RTKBT_DBG("%s: connect_port: %d", __func__, CONNECT_PORT); + btrtl_coex.sock_open = 0; + + err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, + &btrtl_coex.udpsock); + if (err < 0) { + RTKBT_ERR("%s: sock create error, err = %d", __func__, err); + return; + } + + memset(&btrtl_coex.addr, 0, sizeof(struct sockaddr_in)); + btrtl_coex.addr.sin_family = AF_INET; + btrtl_coex.addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + btrtl_coex.addr.sin_port = htons(CONNECT_PORT); + + memset(&btrtl_coex.wifi_addr, 0, sizeof(struct sockaddr_in)); + btrtl_coex.wifi_addr.sin_family = AF_INET; + btrtl_coex.wifi_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + btrtl_coex.wifi_addr.sin_port = htons(CONNECT_PORT_WIFI); + + err = + btrtl_coex.udpsock->ops->bind(btrtl_coex.udpsock, + (struct sockaddr *)&btrtl_coex. + addr, sizeof(struct sockaddr)); + if (err < 0) { + sock_release(btrtl_coex.udpsock); + RTKBT_ERR("%s: sock bind error, err = %d",__func__, err); + return; + } + + btrtl_coex.sock_open = 1; + btrtl_coex.udpsock->sk->sk_data_ready = udpsocket_recv; +} +#endif /* !RTK_COEX_OVER_SYMBOL */ + +static void rtk_notify_extension_version_to_wifi(void) +{ + uint8_t para_length = 2; + char p_buf[2 + HCI_CMD_PREAMBLE_SIZE]; + char *p = p_buf; + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_HCI_EXTENSION_VERSION_NOTIFY); + *p++ = para_length; + UINT16_TO_STREAM(p, HCI_EXTENSION_VERSION); + RTKBT_DBG("extension version is 0x%x", HCI_EXTENSION_VERSION); + if (rtkbt_coexmsg_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +static void rtk_notify_btpatch_version_to_wifi(void) +{ + uint8_t para_length = 4; + char p_buf[para_length + HCI_CMD_PREAMBLE_SIZE]; + char *p = p_buf; + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_HCI_BT_PATCH_VER_NOTIFY); + *p++ = para_length; + UINT16_TO_STREAM(p, btrtl_coex.hci_reversion); + UINT16_TO_STREAM(p, btrtl_coex.lmp_subversion); + RTKBT_DBG("btpatch ver: len %u, hci_rev 0x%04x, lmp_subver 0x%04x", + para_length, btrtl_coex.hci_reversion, + btrtl_coex.lmp_subversion); + + if (rtkbt_coexmsg_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +static void rtk_notify_afhmap_to_wifi(void) +{ + uint8_t para_length = 13; + char p_buf[para_length + HCI_CMD_PREAMBLE_SIZE]; + char *p = p_buf; + uint8_t kk = 0; + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_HCI_BT_AFH_MAP_NOTIFY); + *p++ = para_length; + *p++ = btrtl_coex.piconet_id; + *p++ = btrtl_coex.mode; + *p++ = 10; + memcpy(p, btrtl_coex.afh_map, 10); + + RTKBT_DBG("afhmap, piconet_id is 0x%x, map type is 0x%x", + btrtl_coex.piconet_id, btrtl_coex.mode); + for (kk = 0; kk < 10; kk++) + RTKBT_DBG("afhmap data[%d] is 0x%x", kk, + btrtl_coex.afh_map[kk]); + + if (rtkbt_coexmsg_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +static void rtk_notify_btcoex_to_wifi(uint8_t opcode, uint8_t status) +{ + uint8_t para_length = 2; + char p_buf[para_length + HCI_CMD_PREAMBLE_SIZE]; + char *p = p_buf; + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_HCI_BT_COEX_NOTIFY); + *p++ = para_length; + *p++ = opcode; + if (!status) + *p++ = 0; + else + *p++ = 1; + + RTKBT_DBG("btcoex, opcode is 0x%x, status is 0x%x", opcode, status); + + if (rtkbt_coexmsg_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +static void rtk_notify_btoperation_to_wifi(uint8_t operation, + uint8_t append_data_length, + uint8_t * append_data) +{ + uint8_t para_length = 3 + append_data_length; + char p_buf[para_length + HCI_CMD_PREAMBLE_SIZE]; + char *p = p_buf; + uint8_t kk = 0; + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_BT_OPERATION_NOTIFY); + *p++ = para_length; + *p++ = operation; + *p++ = append_data_length; + if (append_data_length) + memcpy(p, append_data, append_data_length); + + RTKBT_DBG("btoperation: op 0x%02x, append_data_length %u", + operation, append_data_length); + if (append_data_length) { + for (kk = 0; kk < append_data_length; kk++) + RTKBT_DBG("append data is 0x%x", *(append_data + kk)); + } + + if (rtkbt_coexmsg_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +static void rtk_notify_info_to_wifi(uint8_t reason, uint8_t length, + uint8_t *report_info) +{ + uint8_t para_length = 4 + length; + char buf[para_length + HCI_CMD_PREAMBLE_SIZE]; + char *p = buf; + struct rtl_btinfo *report = (struct rtl_btinfo *)report_info; + + if (length) { + RTKBT_DBG("bt info: cmd %2.2X", report->cmd); + RTKBT_DBG("bt info: len %2.2X", report->len); + RTKBT_DBG("bt info: data %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X", + report->data[0], report->data[1], report->data[2], + report->data[3], report->data[4], report->data[5]); + } + RTKBT_DBG("bt info: reason 0x%2x, length 0x%2x", reason, length); + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_HCI_BT_INFO_NOTIFY); + *p++ = para_length; + *p++ = btrtl_coex.polling_enable; + *p++ = btrtl_coex.polling_interval; + *p++ = reason; + *p++ = length; + + if (length) + memcpy(p, report_info, length); + + RTKBT_DBG("para length %2x, polling_enable %u, poiiling_interval %u", + para_length, btrtl_coex.polling_enable, + btrtl_coex.polling_interval); + /* send BT INFO to Wi-Fi driver */ + if (rtkbt_coexmsg_send(buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +static void rtk_notify_regester_to_wifi(uint8_t * reg_value) +{ + uint8_t para_length = 9; + char p_buf[para_length + HCI_CMD_PREAMBLE_SIZE]; + char *p = p_buf; + hci_mailbox_register *reg = (hci_mailbox_register *) reg_value; + + if (!btrtl_coex.wifi_on) + return; + + UINT16_TO_STREAM(p, HCI_OP_HCI_BT_REGISTER_VALUE_NOTIFY); + *p++ = para_length; + memcpy(p, reg_value, para_length); + + RTKBT_DBG("bt register, register type is %x", reg->type); + RTKBT_DBG("bt register, register offset is %x", reg->offset); + RTKBT_DBG("bt register, register value is %x", reg->value); + + if (rtkbt_coexmsg_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0) + RTKBT_ERR("%s: sock send error", __func__); +} + +#endif + +void rtk_btcoex_parse_cmd(uint8_t *buffer, int count) +{ + u16 opcode = (buffer[0]) + (buffer[1] << 8); + + if (!test_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) { + RTKBT_INFO("%s: Coex is closed, ignore", __func__); + return; + } + + switch (opcode) { + case HCI_OP_INQUIRY: + case HCI_OP_PERIODIC_INQ: + if (!btrtl_coex.isinquirying) { + btrtl_coex.isinquirying = 1; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci (periodic)inq, notify wifi " + "inquiry start"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_START, + 0, NULL); +#else + RTKBT_INFO("hci (periodic)inq start"); +#endif + } + break; + case HCI_OP_INQUIRY_CANCEL: + case HCI_OP_EXIT_PERIODIC_INQ: + if (btrtl_coex.isinquirying) { + btrtl_coex.isinquirying = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci (periodic)inq cancel/exit, notify wifi " + "inquiry stop"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0, + NULL); +#else + RTKBT_INFO("hci (periodic)inq cancel/exit"); +#endif + } + break; + case HCI_OP_ACCEPT_CONN_REQ: + if (!btrtl_coex.ispaging) { + btrtl_coex.ispaging = 1; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci accept connreq, notify wifi page start"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_START, 0, + NULL); +#else + RTKBT_INFO("hci accept conn req"); +#endif + } + break; + case HCI_OP_DISCONNECT: + RTKBT_INFO("HCI Disconnect, handle %04x, reason 0x%02x", + ((u16)buffer[4] << 8 | buffer[3]), buffer[5]); + break; + default: + break; + } +} + +static void rtk_handle_inquiry_complete(void) +{ + if (btrtl_coex.isinquirying) { + btrtl_coex.isinquirying = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("inq complete, notify wifi inquiry end"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0, NULL); +#else + RTKBT_INFO("inquiry complete"); +#endif + } +} + +static void rtk_handle_pin_code_req(void) +{ + if (!btrtl_coex.ispairing) { + btrtl_coex.ispairing = 1; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("pin code req, notify wifi pair start"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_START, 0, NULL); +#else + RTKBT_INFO("pin code request"); +#endif + } +} + +static void rtk_handle_io_capa_req(void) +{ + if (!btrtl_coex.ispairing) { + btrtl_coex.ispairing = 1; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("io cap req, notify wifi pair start"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_START, 0, NULL); +#else + RTKBT_INFO("io capability request"); +#endif + } +} + +static void rtk_handle_auth_request(void) +{ + if (btrtl_coex.ispairing) { + btrtl_coex.ispairing = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("auth req, notify wifi pair end"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_END, 0, NULL); +#else + RTKBT_INFO("authentication request"); +#endif + } +} + +static void rtk_handle_link_key_notify(void) +{ + if (btrtl_coex.ispairing) { + btrtl_coex.ispairing = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("link key notify, notify wifi pair end"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_END, 0, NULL); +#else + RTKBT_INFO("link key notify"); +#endif + } +} + +static void rtk_handle_mode_change_evt(u8 * p) +{ + u16 mode_change_handle, mode_interval; + + p++; + STREAM_TO_UINT16(mode_change_handle, p); + p++; + STREAM_TO_UINT16(mode_interval, p); + update_hid_active_state(mode_change_handle, mode_interval); +} + +#ifdef RTB_SOFTWARE_MAILBOX +static void rtk_parse_vendor_mailbox_cmd_evt(u8 * p, u8 total_len) +{ + u8 status, subcmd; + u8 temp_cmd[10]; + + status = *p++; + if (total_len <= 4) { + RTKBT_DBG("receive mailbox cmd from fw, total length <= 4"); + return; + } + subcmd = *p++; + RTKBT_DBG("receive mailbox cmd from fw, subcmd is 0x%x, status is 0x%x", + subcmd, status); + + switch (subcmd) { + case HCI_VENDOR_SUB_CMD_BT_REPORT_CONN_SCO_INQ_INFO: + if (status == 0) //success + rtk_notify_info_to_wifi(POLLING_RESPONSE, + RTL_BTINFO_LEN, (uint8_t *)p); + break; + + case HCI_VENDOR_SUB_CMD_WIFI_CHANNEL_AND_BANDWIDTH_CMD: + rtk_notify_btcoex_to_wifi(WIFI_BW_CHNL_NOTIFY, status); + break; + + case HCI_VENDOR_SUB_CMD_WIFI_FORCE_TX_POWER_CMD: + rtk_notify_btcoex_to_wifi(BT_POWER_DECREASE_CONTROL, status); + break; + + case HCI_VENDOR_SUB_CMD_BT_ENABLE_IGNORE_WLAN_ACT_CMD: + rtk_notify_btcoex_to_wifi(IGNORE_WLAN_ACTIVE_CONTROL, status); + break; + + case HCI_VENDOR_SUB_CMD_SET_BT_PSD_MODE: + rtk_notify_btcoex_to_wifi(BT_PSD_MODE_CONTROL, status); + break; + + case HCI_VENDOR_SUB_CMD_SET_BT_LNA_CONSTRAINT: + rtk_notify_btcoex_to_wifi(LNA_CONSTRAIN_CONTROL, status); + break; + + case HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_ENABLE: + break; + + case HCI_VENDOR_SUB_CMD_BT_SET_TXRETRY_REPORT_PARAM: + break; + + case HCI_VENDOR_SUB_CMD_BT_SET_PTATABLE: + break; + + case HCI_VENDOR_SUB_CMD_GET_AFH_MAP_L: + if (status == 0) { + memcpy(btrtl_coex.afh_map, p + 4, 4); /* cmd_idx, length, piconet_id, mode */ + temp_cmd[0] = HCI_VENDOR_SUB_CMD_GET_AFH_MAP_M; + temp_cmd[1] = 2; + temp_cmd[2] = btrtl_coex.piconet_id; + temp_cmd[3] = btrtl_coex.mode; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 4, + temp_cmd); + } else { + memset(btrtl_coex.afh_map, 0, 10); + rtk_notify_afhmap_to_wifi(); + } + break; + + case HCI_VENDOR_SUB_CMD_GET_AFH_MAP_M: + if (status == 0) { + memcpy(btrtl_coex.afh_map + 4, p + 4, 4); + temp_cmd[0] = HCI_VENDOR_SUB_CMD_GET_AFH_MAP_H; + temp_cmd[1] = 2; + temp_cmd[2] = btrtl_coex.piconet_id; + temp_cmd[3] = btrtl_coex.mode; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 4, + temp_cmd); + } else { + memset(btrtl_coex.afh_map, 0, 10); + rtk_notify_afhmap_to_wifi(); + } + break; + + case HCI_VENDOR_SUB_CMD_GET_AFH_MAP_H: + if (status == 0) + memcpy(btrtl_coex.afh_map + 8, p + 4, 2); + else + memset(btrtl_coex.afh_map, 0, 10); + + rtk_notify_afhmap_to_wifi(); + break; + + case HCI_VENDOR_SUB_CMD_RD_REG_REQ: + if (status == 0) + rtk_notify_regester_to_wifi(p + 3); /* cmd_idx,length,regist type */ + break; + + case HCI_VENDOR_SUB_CMD_WR_REG_REQ: + rtk_notify_btcoex_to_wifi(BT_REGISTER_ACCESS, status); + break; + + default: + break; + } +} +#endif /* RTB_SOFTWARE_MAILBOX */ + +static void rtk_handle_cmd_complete_evt(u8 total_len, u8 * p) +{ + u16 opcode; + + p++; + STREAM_TO_UINT16(opcode, p); + //RTKBT_DBG("cmd_complete, opcode is 0x%x", opcode); + + if (opcode == HCI_OP_PERIODIC_INQ) { + if (*p++ && btrtl_coex.isinquirying) { + btrtl_coex.isinquirying = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci period inq, start error, notify wifi " + "inquiry stop"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0, + NULL); +#else + RTKBT_INFO("hci period inquiry start error"); +#endif + } + } + + if (opcode == HCI_OP_READ_LOCAL_VERSION) { + if (!(*p++)) { + p++; + STREAM_TO_UINT16(btrtl_coex.hci_reversion, p); + p += 3; + STREAM_TO_UINT16(btrtl_coex.lmp_subversion, p); + RTKBT_DBG("BTCOEX hci_rev 0x%04x", + btrtl_coex.hci_reversion); + RTKBT_DBG("BTCOEX lmp_subver 0x%04x", + btrtl_coex.lmp_subversion); + } + } + +#ifdef RTB_SOFTWARE_MAILBOX + if (opcode == HCI_VENDOR_MAILBOX_CMD) { + rtk_parse_vendor_mailbox_cmd_evt(p, total_len); + } +#endif +} + +static void rtk_handle_cmd_status_evt(u8 * p) +{ + u16 opcode; + u8 status; + + status = *p++; + p++; + STREAM_TO_UINT16(opcode, p); + //RTKBT_DBG("cmd_status, opcode is 0x%x", opcode); + if ((opcode == HCI_OP_INQUIRY) && (status)) { + if (btrtl_coex.isinquirying) { + btrtl_coex.isinquirying = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci inq, start error, notify wifi inq stop"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0, + NULL); +#else + RTKBT_INFO("hci inquiry start error"); +#endif + } + } + + if (opcode == HCI_OP_CREATE_CONN) { + if (!status && !btrtl_coex.ispaging) { + btrtl_coex.ispaging = 1; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci create conn, notify wifi start page"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_START, 0, + NULL); +#else + RTKBT_INFO("hci create connection, start paging"); +#endif + } + } +} + +static void rtk_handle_connection_complete_evt(u8 * p) +{ + u16 handle; + u8 status, link_type; + rtk_conn_prof *hci_conn = NULL; + + status = *p++; + STREAM_TO_UINT16(handle, p); + p += 6; + link_type = *p++; + + RTKBT_INFO("connected, handle %04x, status 0x%02x", handle, status); + + if (status == 0) { + if (btrtl_coex.ispaging) { + btrtl_coex.ispaging = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("notify wifi page success end"); + rtk_notify_btoperation_to_wifi + (BT_OPCODE_PAGE_SUCCESS_END, 0, NULL); +#else + RTKBT_INFO("Page success"); +#endif + } + + hci_conn = find_connection_by_handle(&btrtl_coex, handle); + if (hci_conn == NULL) { + hci_conn = allocate_connection_by_handle(handle); + if (hci_conn) { + add_connection_to_hash(&btrtl_coex, + hci_conn); + hci_conn->profile_bitmap = 0; + memset(hci_conn->profile_refcount, 0, 8); + if ((0 == link_type) || (2 == link_type)) { //sco or esco + hci_conn->type = 1; + update_profile_connection(hci_conn, + profile_sco, + TRUE); + } else + hci_conn->type = 0; + } else { + RTKBT_ERR("hci connection allocate fail"); + } + } else { + RTKBT_DBG("hci conn handle 0x%04x already existed!", + handle); + hci_conn->profile_bitmap = 0; + memset(hci_conn->profile_refcount, 0, 8); + if ((0 == link_type) || (2 == link_type)) { //sco or esco + hci_conn->type = 1; + update_profile_connection(hci_conn, profile_sco, + TRUE); + } else + hci_conn->type = 0; + } + } else if (btrtl_coex.ispaging) { + btrtl_coex.ispaging = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("notify wifi page unsuccess end"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_UNSUCCESS_END, 0, + NULL); +#else + RTKBT_INFO("Page failed"); +#endif + } +} + +static void rtk_handle_le_connection_complete_evt(u8 enhanced, u8 * p) +{ + u16 handle, interval; + u8 status; + rtk_conn_prof *hci_conn = NULL; + + status = *p++; + STREAM_TO_UINT16(handle, p); + if (!enhanced) + p += 8; /* role, address type, address */ + else + p += (8 + 12); /* plus two bluetooth addresses */ + STREAM_TO_UINT16(interval, p); + + RTKBT_INFO("LE connected, handle %04x, status 0x%02x, interval %u", + handle, status, interval); + + if (status == 0) { + if (btrtl_coex.ispaging) { + btrtl_coex.ispaging = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("notify wifi page success end"); + rtk_notify_btoperation_to_wifi + (BT_OPCODE_PAGE_SUCCESS_END, 0, NULL); +#else + RTKBT_INFO("Page success end"); +#endif + } + + hci_conn = find_connection_by_handle(&btrtl_coex, handle); + if (hci_conn == NULL) { + hci_conn = allocate_connection_by_handle(handle); + if (hci_conn) { + add_connection_to_hash(&btrtl_coex, + hci_conn); + hci_conn->profile_bitmap = 0; + memset(hci_conn->profile_refcount, 0, 8); + hci_conn->type = 2; + update_profile_connection(hci_conn, profile_hid, TRUE); //for coex, le is the same as hid + update_hid_active_state(handle, interval); + } else { + RTKBT_ERR("hci connection allocate fail"); + } + } else { + RTKBT_DBG("hci conn handle 0x%04x already existed!", + handle); + hci_conn->profile_bitmap = 0; + memset(hci_conn->profile_refcount, 0, 8); + hci_conn->type = 2; + update_profile_connection(hci_conn, profile_hid, TRUE); + update_hid_active_state(handle, interval); + } + } else if (btrtl_coex.ispaging) { + btrtl_coex.ispaging = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("notify wifi page unsuccess end"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_UNSUCCESS_END, 0, + NULL); +#else + RTKBT_INFO("Page failed"); +#endif + } +} + +static void rtk_handle_le_connection_update_complete_evt(u8 * p) +{ + u16 handle, interval; + /* u8 status; */ + + /* status = *p++; */ + p++; + + STREAM_TO_UINT16(handle, p); + STREAM_TO_UINT16(interval, p); + update_hid_active_state(handle, interval); +} + +static void rtk_handle_le_meta_evt(u8 * p) +{ + u8 sub_event = *p++; + switch (sub_event) { + case HCI_EV_LE_CONN_COMPLETE: + rtk_handle_le_connection_complete_evt(0, p); + break; + case HCI_EV_LE_ENHANCED_CONN_COMPLETE: + rtk_handle_le_connection_complete_evt(1, p); + break; + + case HCI_EV_LE_CONN_UPDATE_COMPLETE: + rtk_handle_le_connection_update_complete_evt(p); + break; + + default: + break; + } +} + +static u8 disconn_profile(struct rtl_hci_conn *conn, u8 pfe_index) +{ + u8 need_update = 0; + + if (!btrtl_coex.profile_refcount[pfe_index]) { + RTKBT_WARN("profile %u ref is 0", pfe_index); + return 0; + } + + btrtl_coex.profile_refcount[pfe_index]--; + RTKBT_INFO("%s: profile_ref[%u] %u", __func__, pfe_index, + btrtl_coex.profile_refcount[pfe_index]); + + if (!btrtl_coex.profile_refcount[pfe_index]) { + need_update = 1; + btrtl_coex.profile_bitmap &= ~(BIT(pfe_index)); + + /* if profile does not exist, status is meaningless */ + btrtl_coex.profile_status &= ~(BIT(pfe_index)); + rtk_check_del_timer(pfe_index); + } + + if (conn->profile_refcount[pfe_index]) + conn->profile_refcount[pfe_index]--; + else + RTKBT_INFO("%s: conn pfe ref[%u] is 0", __func__, + conn->profile_refcount[pfe_index]); + if (!conn->profile_refcount[pfe_index]) { + need_update = 1; + conn->profile_bitmap &= ~(BIT(pfe_index)); + + /* clear profile_hid_interval if need */ + if ((profile_hid == pfe_index) && + (conn->profile_bitmap & (BIT(profile_hid_interval)))) { + conn->profile_bitmap &= ~(BIT(profile_hid_interval)); + if (btrtl_coex.profile_refcount[profile_hid_interval]) + btrtl_coex.profile_refcount[profile_hid_interval]--; + } + } + + return need_update; +} + +static void disconn_acl(u16 handle, struct rtl_hci_conn *conn) +{ + struct rtl_coex_struct *coex = &btrtl_coex; + rtk_prof_info *prof_info = NULL; + struct list_head *iter = NULL, *temp = NULL; + u8 need_update = 0; + + spin_lock(&coex->spin_lock_profile); + + list_for_each_safe(iter, temp, &coex->profile_list) { + prof_info = list_entry(iter, rtk_prof_info, list); + if (handle == prof_info->handle) { + RTKBT_DBG("hci disconn, hndl %x, psm %x, dcid %x, " + "scid %x, profile %u", prof_info->handle, + prof_info->psm, prof_info->dcid, + prof_info->scid, prof_info->profile_index); + //If both scid and dcid > 0, L2cap connection is exist. + need_update |= disconn_profile(conn, + prof_info->profile_index); + if ((prof_info->flags & A2DP_MEDIA) && + (conn->profile_bitmap & BIT(profile_sink))) + need_update |= disconn_profile(conn, + profile_sink); + delete_profile_from_hash(prof_info); + } + } + if (need_update) + rtk_notify_profileinfo_to_fw(); + spin_unlock(&coex->spin_lock_profile); +} + +static void rtk_handle_disconnect_complete_evt(u8 * p) +{ + u16 handle; + u8 status; + u8 reason; + rtk_conn_prof *hci_conn = NULL; + + if (btrtl_coex.ispairing) { //for slave: connection will be disconnected if authentication fail + btrtl_coex.ispairing = 0; +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("hci disc complete, notify wifi pair end"); + rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_END, 0, NULL); +#else + RTKBT_INFO("hci disconnection complete"); +#endif + } + + status = *p++; + STREAM_TO_UINT16(handle, p); + reason = *p; + + RTKBT_INFO("disconn cmpl evt: status 0x%02x, handle %04x, reason 0x%02x", + status, handle, reason); + + if (status == 0) { + RTKBT_DBG("process disconn complete event."); + hci_conn = find_connection_by_handle(&btrtl_coex, handle); + if (hci_conn) { + switch (hci_conn->type) { + case 0: + /* FIXME: If this is interrupted by l2cap rx, + * there may be deadlock on spin_lock_profile */ + disconn_acl(handle, hci_conn); + break; + + case 1: + update_profile_connection(hci_conn, profile_sco, + FALSE); + break; + + case 2: + update_profile_connection(hci_conn, profile_hid, + FALSE); + break; + + default: + break; + } + delete_connection_from_hash(hci_conn); + } else + RTKBT_ERR("hci conn handle 0x%04x not found", handle); + } +} + +static void rtk_handle_specific_evt(u8 * p) +{ + u16 subcode; + + STREAM_TO_UINT16(subcode, p); + if (subcode == HCI_VENDOR_PTA_AUTO_REPORT_EVENT) { +#ifdef RTB_SOFTWARE_MAILBOX + RTKBT_DBG("notify wifi driver with autoreport data"); + rtk_notify_info_to_wifi(AUTO_REPORT, RTL_BTINFO_LEN, + (uint8_t *)p); +#else + RTKBT_INFO("auto report data"); +#endif + } +} + +static void rtk_parse_event_data(struct rtl_coex_struct *coex, + u8 *data, u16 len) +{ + u8 *p = data; + u8 event_code = *p++; + u8 total_len = *p++; + + (void)coex; + (void)&len; + + switch (event_code) { + case HCI_EV_INQUIRY_COMPLETE: + rtk_handle_inquiry_complete(); + break; + + case HCI_EV_PIN_CODE_REQ: + rtk_handle_pin_code_req(); + break; + + case HCI_EV_IO_CAPA_REQUEST: + rtk_handle_io_capa_req(); + break; + + case HCI_EV_AUTH_COMPLETE: + rtk_handle_auth_request(); + break; + + case HCI_EV_LINK_KEY_NOTIFY: + rtk_handle_link_key_notify(); + break; + + case HCI_EV_MODE_CHANGE: + rtk_handle_mode_change_evt(p); + break; + + case HCI_EV_CMD_COMPLETE: + rtk_handle_cmd_complete_evt(total_len, p); + break; + + case HCI_EV_CMD_STATUS: + rtk_handle_cmd_status_evt(p); + break; + + case HCI_EV_CONN_COMPLETE: + case HCI_EV_SYNC_CONN_COMPLETE: + rtk_handle_connection_complete_evt(p); + break; + + case HCI_EV_DISCONN_COMPLETE: + rtk_handle_disconnect_complete_evt(p); + break; + + case HCI_EV_LE_META: + rtk_handle_le_meta_evt(p); + break; + + case HCI_EV_VENDOR_SPECIFIC: + rtk_handle_specific_evt(p); + break; + + default: + break; + } +} + +const char l2_dir_str[][4] = { + "RX", "TX", +}; + +void rtl_process_l2_sig(struct rtl_l2_buff *l2) +{ + /* u8 flag; */ + u8 code; + /* u8 identifier; */ + u16 handle; + /* u16 total_len; */ + /* u16 pdu_len, channel_id; */ + /* u16 command_len; */ + u16 psm, scid, dcid, result; + /* u16 status; */ + u8 *pp = l2->data; + + STREAM_TO_UINT16(handle, pp); + /* flag = handle >> 12; */ + handle = handle & 0x0FFF; + /* STREAM_TO_UINT16(total_len, pp); */ + pp += 2; /* data total length */ + + /* STREAM_TO_UINT16(pdu_len, pp); + * STREAM_TO_UINT16(channel_id, pp); */ + pp += 4; /* l2 len and channel id */ + + code = *pp++; + switch (code) { + case L2CAP_CONN_REQ: + /* identifier = *pp++; */ + pp++; + /* STREAM_TO_UINT16(command_len, pp); */ + pp += 2; + STREAM_TO_UINT16(psm, pp); + STREAM_TO_UINT16(scid, pp); + RTKBT_DBG("%s l2cap conn req, hndl 0x%04x, PSM 0x%04x, " + "scid 0x%04x", l2_dir_str[l2->out], handle, psm, + scid); + handle_l2cap_con_req(handle, psm, scid, l2->out); + break; + + case L2CAP_CONN_RSP: + /* identifier = *pp++; */ + pp++; + /* STREAM_TO_UINT16(command_len, pp); */ + pp += 2; + STREAM_TO_UINT16(dcid, pp); + STREAM_TO_UINT16(scid, pp); + STREAM_TO_UINT16(result, pp); + /* STREAM_TO_UINT16(status, pp); */ + pp += 2; + RTKBT_DBG("%s l2cap conn rsp, hndl 0x%04x, dcid 0x%04x, " + "scid 0x%04x, result 0x%04x", l2_dir_str[l2->out], + handle, dcid, scid, result); + handle_l2cap_con_rsp(handle, dcid, scid, l2->out, result); + break; + + case L2CAP_DISCONN_REQ: + /* identifier = *pp++; */ + pp++; + /* STREAM_TO_UINT16(command_len, pp); */ + pp += 2; + STREAM_TO_UINT16(dcid, pp); + STREAM_TO_UINT16(scid, pp); + RTKBT_DBG("%s l2cap disconn req, hndl 0x%04x, dcid 0x%04x, " + "scid 0x%04x", l2_dir_str[l2->out], handle, dcid, scid); + handle_l2cap_discon_req(handle, dcid, scid, l2->out); + break; + default: + RTKBT_DBG("undesired l2 command %u", code); + break; + } +} + +static void rtl_l2_data_process(u8 *pp, u16 len, int dir) +{ + u8 code; + u8 flag; + u16 handle, pdu_len, channel_id; + /* u16 total_len; */ + struct rtl_l2_buff *l2 = NULL; + u8 *hd = pp; + + /* RTKBT_DBG("l2 sig data %p, len %u, dir %d", pp, len, dir); */ + + STREAM_TO_UINT16(handle, pp); + flag = handle >> 12; + handle = handle & 0x0FFF; + /* STREAM_TO_UINT16(total_len, pp); */ + pp += 2; /* data total length */ + + STREAM_TO_UINT16(pdu_len, pp); + STREAM_TO_UINT16(channel_id, pp); + + if (channel_id == 0x0001) { + code = *pp++; + switch (code) { + case L2CAP_CONN_REQ: + case L2CAP_CONN_RSP: + case L2CAP_DISCONN_REQ: + RTKBT_DBG("l2cap op %u, len %u, out %d", code, len, + dir); + l2 = rtl_l2_node_get(&btrtl_coex); + if (l2) { + u16 n; + n = min_t(uint, len, L2_MAX_SUBSEC_LEN); + memcpy(l2->data, hd, n); + l2->out = dir; + rtl_l2_node_to_used(&btrtl_coex, l2); + queue_delayed_work(btrtl_coex.fw_wq, + &btrtl_coex.l2_work, 0); + } else + RTKBT_ERR("%s: failed to get l2 node", + __func__); + break; + case L2CAP_DISCONN_RSP: + break; + default: + break; + } + } else { + if ((flag != 0x01) && (is_profile_connected(profile_a2dp) || + is_profile_connected(profile_pan))) + /* Do not count the continuous packets */ + packets_count(handle, channel_id, pdu_len, dir, pp); + } + return; +} + + +static void rtl_l2_work(struct work_struct *work) +{ + struct rtl_coex_struct *coex; + struct rtl_l2_buff *l2; + unsigned long flags; + + coex = container_of(work, struct rtl_coex_struct, l2_work.work); + + spin_lock_irqsave(&coex->buff_lock, flags); + while (!list_empty(&coex->l2_used_list)) { + l2 = list_entry(coex->l2_used_list.next, struct rtl_l2_buff, + list); + list_del(&l2->list); + + spin_unlock_irqrestore(&coex->buff_lock, flags); + + rtl_process_l2_sig(l2); + + spin_lock_irqsave(&coex->buff_lock, flags); + + list_add_tail(&l2->list, &coex->l2_free_list); + } + spin_unlock_irqrestore(&coex->buff_lock, flags); + + return; +} + +static void rtl_ev_work(struct work_struct *work) +{ + struct rtl_coex_struct *coex; + struct rtl_hci_ev *ev; + unsigned long flags; + + coex = container_of(work, struct rtl_coex_struct, fw_work.work); + + spin_lock_irqsave(&coex->buff_lock, flags); + while (!list_empty(&coex->ev_used_list)) { + ev = list_entry(coex->ev_used_list.next, struct rtl_hci_ev, + list); + list_del(&ev->list); + spin_unlock_irqrestore(&coex->buff_lock, flags); + + rtk_parse_event_data(coex, ev->data, ev->len); + + spin_lock_irqsave(&coex->buff_lock, flags); + list_add_tail(&ev->list, &coex->ev_free_list); + } + spin_unlock_irqrestore(&coex->buff_lock, flags); +} + +static inline int cmd_cmplt_filter_out(u8 *buf) +{ + u16 opcode; + + opcode = buf[3] | (buf[4] << 8); + switch (opcode) { + case HCI_OP_PERIODIC_INQ: + case HCI_OP_READ_LOCAL_VERSION: +#ifdef RTB_SOFTWARE_MAILBOX + case HCI_VENDOR_MAILBOX_CMD: +#endif + return 0; + default: + return 1; + } +} + +static inline int cmd_status_filter_out(u8 *buf) +{ + u16 opcode; + + opcode = buf[4] | (buf[5] << 8); + switch (opcode) { + case HCI_OP_INQUIRY: + case HCI_OP_CREATE_CONN: + return 0; + default: + return 1; + } +} + +int ev_filter_out(u8 *buf) +{ + switch (buf[0]) { + case HCI_EV_INQUIRY_COMPLETE: + case HCI_EV_PIN_CODE_REQ: + case HCI_EV_IO_CAPA_REQUEST: + case HCI_EV_AUTH_COMPLETE: + case HCI_EV_LINK_KEY_NOTIFY: + case HCI_EV_MODE_CHANGE: + case HCI_EV_CONN_COMPLETE: + case HCI_EV_SYNC_CONN_COMPLETE: + case HCI_EV_DISCONN_COMPLETE: + case HCI_EV_VENDOR_SPECIFIC: + return 0; + case HCI_EV_LE_META: + /* Ignore frequent but not useful events that result in + * costing too much space. + */ + switch (buf[2]) { + case HCI_EV_LE_CONN_COMPLETE: + case HCI_EV_LE_ENHANCED_CONN_COMPLETE: + case HCI_EV_LE_CONN_UPDATE_COMPLETE: + return 0; + } + return 1; + case HCI_EV_CMD_COMPLETE: + return cmd_cmplt_filter_out(buf); + case HCI_EV_CMD_STATUS: + return cmd_status_filter_out(buf); + default: + return 1; + } +} + +static void rtk_btcoex_evt_enqueue(__u8 *s, __u16 count) +{ + struct rtl_hci_ev *ev; + + if (ev_filter_out(s)) + return; + + ev = rtl_ev_node_get(&btrtl_coex); + if (!ev) { + RTKBT_ERR("%s: no free ev node.", __func__); + return; + } + + if (count > MAX_LEN_OF_HCI_EV) { + memcpy(ev->data, s, MAX_LEN_OF_HCI_EV); + ev->len = MAX_LEN_OF_HCI_EV; + } else { + memcpy(ev->data, s, count); + ev->len = count; + } + + rtl_ev_node_to_used(&btrtl_coex, ev); + + queue_delayed_work(btrtl_coex.fw_wq, &btrtl_coex.fw_work, 0); +} + +/* Context: in_interrupt() */ +void rtk_btcoex_parse_event(uint8_t *buffer, int count) +{ + struct rtl_coex_struct *coex = &btrtl_coex; + __u8 *tbuff; + __u16 elen = 0; + + /* RTKBT_DBG("%s: parse ev.", __func__); */ + if (!test_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) { + /* RTKBT_INFO("%s: Coex is closed, ignore", __func__); */ + RTKBT_INFO("%s: Coex is closed, ignore %x, %x", + __func__, buffer[0], buffer[1]); + return; + } + + spin_lock(&coex->rxlock); + + /* coex->tbuff will be set to NULL when initializing or + * there is a complete frame or there is start of a frame */ + tbuff = coex->tbuff; + + while (count) { + int len; + + /* Start of a frame */ + if (!tbuff) { + tbuff = coex->back_buff; + coex->tbuff = NULL; + coex->elen = 0; + + coex->pkt_type = HCI_EVENT_PKT; + coex->expect = HCI_EVENT_HDR_SIZE; + } + + len = min_t(uint, coex->expect, count); + memcpy(tbuff, buffer, len); + tbuff += len; + coex->elen += len; + + count -= len; + buffer += len; + coex->expect -= len; + + if (coex->elen == HCI_EVENT_HDR_SIZE) { + /* Complete event header */ + coex->expect = + ((struct hci_event_hdr *)coex->back_buff)->plen; + if (coex->expect > HCI_MAX_EVENT_SIZE - coex->elen) { + tbuff = NULL; + coex->elen = 0; + RTKBT_ERR("tbuff room is not enough"); + break; + } + } + + if (coex->expect == 0) { + /* Complete frame */ + elen = coex->elen; + spin_unlock(&coex->rxlock); + rtk_btcoex_evt_enqueue(coex->back_buff, elen); + spin_lock(&coex->rxlock); + + tbuff = NULL; + coex->elen = 0; + } + } + + /* coex->tbuff would be non-NULL if there isn't a complete frame + * And it will be updated next time */ + coex->tbuff = tbuff; + spin_unlock(&coex->rxlock); +} + + +void rtk_btcoex_parse_l2cap_data_tx(uint8_t *buffer, int count) +{ + if (!test_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) { + RTKBT_INFO("%s: Coex is closed, ignore", __func__); + return; + } + + rtl_l2_data_process(buffer, count, 1); + //u16 handle, total_len, pdu_len, channel_ID, command_len, psm, scid, + // dcid, result, status; + //u8 flag, code, identifier; + //u8 *pp = (u8 *) (skb->data); + //STREAM_TO_UINT16(handle, pp); + //flag = handle >> 12; + //handle = handle & 0x0FFF; + //STREAM_TO_UINT16(total_len, pp); + //STREAM_TO_UINT16(pdu_len, pp); + //STREAM_TO_UINT16(channel_ID, pp); + + //if (channel_ID == 0x0001) { + // code = *pp++; + // switch (code) { + // case L2CAP_CONN_REQ: + // identifier = *pp++; + // STREAM_TO_UINT16(command_len, pp); + // STREAM_TO_UINT16(psm, pp); + // STREAM_TO_UINT16(scid, pp); + // RTKBT_DBG("TX l2cap conn req, hndl %x, PSM %x, scid=%x", + // handle, psm, scid); + // handle_l2cap_con_req(handle, psm, scid, 1); + // break; + + // case L2CAP_CONN_RSP: + // identifier = *pp++; + // STREAM_TO_UINT16(command_len, pp); + // STREAM_TO_UINT16(dcid, pp); + // STREAM_TO_UINT16(scid, pp); + // STREAM_TO_UINT16(result, pp); + // STREAM_TO_UINT16(status, pp); + // RTKBT_DBG("TX l2cap conn rsp, hndl %x, dcid %x, " + // "scid %x, result %x", + // handle, dcid, scid, result); + // handle_l2cap_con_rsp(handle, dcid, scid, 1, result); + // break; + + // case L2CAP_DISCONN_REQ: + // identifier = *pp++; + // STREAM_TO_UINT16(command_len, pp); + // STREAM_TO_UINT16(dcid, pp); + // STREAM_TO_UINT16(scid, pp); + // RTKBT_DBG("TX l2cap disconn req, hndl %x, dcid %x, " + // "scid %x", handle, dcid, scid); + // handle_l2cap_discon_req(handle, dcid, scid, 1); + // break; + + // case L2CAP_DISCONN_RSP: + // break; + + // default: + // break; + // } + //} else { + // if ((flag != 0x01) && (is_profile_connected(profile_a2dp) || is_profile_connected(profile_pan))) //Do not count the continuous packets + // packets_count(handle, channel_ID, pdu_len, 1, pp); + //} +} + +void rtk_btcoex_parse_l2cap_data_rx(uint8_t *buffer, int count) +{ + if (!test_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) { + RTKBT_INFO("%s: Coex is closed, ignore", __func__); + return; + } + + rtl_l2_data_process(buffer, count, 0); + //u16 handle, total_len, pdu_len, channel_ID, command_len, psm, scid, + // dcid, result, status; + //u8 flag, code, identifier; + //u8 *pp = urb->transfer_buffer; + //STREAM_TO_UINT16(handle, pp); + //flag = handle >> 12; + //handle = handle & 0x0FFF; + //STREAM_TO_UINT16(total_len, pp); + //STREAM_TO_UINT16(pdu_len, pp); + //STREAM_TO_UINT16(channel_ID, pp); + + //if (channel_ID == 0x0001) { + // code = *pp++; + // switch (code) { + // case L2CAP_CONN_REQ: + // identifier = *pp++; + // STREAM_TO_UINT16(command_len, pp); + // STREAM_TO_UINT16(psm, pp); + // STREAM_TO_UINT16(scid, pp); + // RTKBT_DBG("RX l2cap conn req, hndl %x, PSM %x, scid %x", + // handle, psm, scid); + // handle_l2cap_con_req(handle, psm, scid, 0); + // break; + + // case L2CAP_CONN_RSP: + // identifier = *pp++; + // STREAM_TO_UINT16(command_len, pp); + // STREAM_TO_UINT16(dcid, pp); + // STREAM_TO_UINT16(scid, pp); + // STREAM_TO_UINT16(result, pp); + // STREAM_TO_UINT16(status, pp); + // RTKBT_DBG("RX l2cap conn rsp, hndl %x, dcid %x, " + // "scid %x, result %x", + // handle, dcid, scid, result); + // handle_l2cap_con_rsp(handle, dcid, scid, 0, result); + // break; + + // case L2CAP_DISCONN_REQ: + // identifier = *pp++; + // STREAM_TO_UINT16(command_len, pp); + // STREAM_TO_UINT16(dcid, pp); + // STREAM_TO_UINT16(scid, pp); + // RTKBT_DBG("RX l2cap disconn req, hndl %x, dcid %x, " + // "scid %x", handle, dcid, scid); + // handle_l2cap_discon_req(handle, dcid, scid, 0); + // break; + + // case L2CAP_DISCONN_RSP: + // break; + + // default: + // break; + // } + //} else { + // if ((flag != 0x01) && (is_profile_connected(profile_a2dp) || is_profile_connected(profile_pan))) //Do not count the continuous packets + // packets_count(handle, channel_ID, pdu_len, 0, pp); + //} +} + +#ifdef RTB_SOFTWARE_MAILBOX + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 14, 0) +static void polling_bt_info(struct timer_list *unused) +#else +static void polling_bt_info(unsigned long data) +#endif +{ + uint8_t temp_cmd[1]; + RTKBT_DBG("polling timer"); + if (btrtl_coex.polling_enable) { + //temp_cmd[0] = HCI_VENDOR_SUB_CMD_BT_REPORT_CONN_SCO_INQ_INFO; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_STATUS_INFO; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 1, temp_cmd); + } + mod_timer(&btrtl_coex.polling_timer, + jiffies + msecs_to_jiffies(1000 * btrtl_coex.polling_interval)); +} + +static void rtk_handle_bt_info_control(uint8_t *p) +{ + uint8_t temp_cmd[20]; + struct rtl_btinfo_ctl *ctl = (struct rtl_btinfo_ctl*)p; + RTKBT_DBG("Received polling_enable %u, polling_time %u, " + "autoreport_enable %u", ctl->polling_enable, + ctl->polling_time, ctl->autoreport_enable); + RTKBT_DBG("coex: original polling_enable %u", + btrtl_coex.polling_enable); + + if (ctl->polling_enable && !btrtl_coex.polling_enable) { + /* setup polling timer for getting bt info from firmware */ + btrtl_coex.polling_timer.expires = + jiffies + msecs_to_jiffies(ctl->polling_time * 1000); + mod_timer(&btrtl_coex.polling_timer, + btrtl_coex.polling_timer.expires); + } + + /* Close bt info polling timer */ + if (!ctl->polling_enable && btrtl_coex.polling_enable) + del_timer(&btrtl_coex.polling_timer); + + if (btrtl_coex.autoreport != ctl->autoreport_enable) { + temp_cmd[0] = HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_ENABLE; + temp_cmd[1] = 1; + temp_cmd[2] = ctl->autoreport_enable; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 3, temp_cmd); + } + + btrtl_coex.polling_enable = ctl->polling_enable; + btrtl_coex.polling_interval = ctl->polling_time; + btrtl_coex.autoreport = ctl->autoreport_enable; + + rtk_notify_info_to_wifi(HOST_RESPONSE, 0, NULL); +} + +static void rtk_handle_bt_coex_control(uint8_t * p) +{ + uint8_t temp_cmd[20]; + uint8_t opcode, opcode_len, value, power_decrease, psd_mode, + access_type; + + opcode = *p++; + RTKBT_DBG("receive bt coex control event from wifi, op 0x%02x", opcode); + + switch (opcode) { + case BT_PATCH_VERSION_QUERY: + rtk_notify_btpatch_version_to_wifi(); + break; + + case IGNORE_WLAN_ACTIVE_CONTROL: + opcode_len = *p++; + value = *p++; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_BT_ENABLE_IGNORE_WLAN_ACT_CMD; + temp_cmd[1] = 1; + temp_cmd[2] = value; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 3, temp_cmd); + break; + + case LNA_CONSTRAIN_CONTROL: + opcode_len = *p++; + value = *p++; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_SET_BT_LNA_CONSTRAINT; + temp_cmd[1] = 1; + temp_cmd[2] = value; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 3, temp_cmd); + break; + + case BT_POWER_DECREASE_CONTROL: + opcode_len = *p++; + power_decrease = *p++; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_WIFI_FORCE_TX_POWER_CMD; + temp_cmd[1] = 1; + temp_cmd[2] = power_decrease; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 3, temp_cmd); + break; + + case BT_PSD_MODE_CONTROL: + opcode_len = *p++; + psd_mode = *p++; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_SET_BT_PSD_MODE; + temp_cmd[1] = 1; + temp_cmd[2] = psd_mode; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 3, temp_cmd); + break; + + case WIFI_BW_CHNL_NOTIFY: + opcode_len = *p++; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_WIFI_CHANNEL_AND_BANDWIDTH_CMD; + temp_cmd[1] = 3; + memcpy(temp_cmd + 2, p, 3); //wifi_state, wifi_centralchannel, chnnels_btnotuse + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 5, temp_cmd); + break; + + case QUERY_BT_AFH_MAP: + opcode_len = *p++; + btrtl_coex.piconet_id = *p++; + btrtl_coex.mode = *p++; + temp_cmd[0] = HCI_VENDOR_SUB_CMD_GET_AFH_MAP_L; + temp_cmd[1] = 2; + temp_cmd[2] = btrtl_coex.piconet_id; + temp_cmd[3] = btrtl_coex.mode; + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 4, temp_cmd); + break; + + case BT_REGISTER_ACCESS: + opcode_len = *p++; + access_type = *p++; + if (access_type == 0) { //read + temp_cmd[0] = HCI_VENDOR_SUB_CMD_RD_REG_REQ; + temp_cmd[1] = 5; + temp_cmd[2] = *p++; + memcpy(temp_cmd + 3, p, 4); + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 7, + temp_cmd); + } else { //write + temp_cmd[0] = HCI_VENDOR_SUB_CMD_RD_REG_REQ; + temp_cmd[1] = 5; + temp_cmd[2] = *p++; + memcpy(temp_cmd + 3, p, 8); + rtk_vendor_cmd_to_fw(HCI_VENDOR_MAILBOX_CMD, 11, + temp_cmd); + } + break; + + default: + break; + } +} + +static void rtk_handle_event_from_wifi(uint8_t * msg) +{ + uint8_t *p = msg; + uint8_t event_code = *p++; + uint8_t total_length; + uint8_t extension_event; + uint8_t operation; + uint16_t wifi_opcode; + uint8_t op_status; + + if (memcmp(msg, invite_rsp, sizeof(invite_rsp)) == 0) { + RTKBT_DBG("receive invite rsp from wifi, wifi is already on"); + btrtl_coex.wifi_on = 1; + rtk_notify_extension_version_to_wifi(); + } + + if (memcmp(msg, attend_req, sizeof(attend_req)) == 0) { + RTKBT_DBG("receive attend req from wifi, wifi turn on"); + btrtl_coex.wifi_on = 1; + rtkbt_coexmsg_send(attend_ack, sizeof(attend_ack)); + rtk_notify_extension_version_to_wifi(); + } + + if (memcmp(msg, wifi_leave, sizeof(wifi_leave)) == 0) { + RTKBT_DBG("receive wifi leave from wifi, wifi turn off"); + btrtl_coex.wifi_on = 0; + rtkbt_coexmsg_send(leave_ack, sizeof(leave_ack)); + if (btrtl_coex.polling_enable) { + btrtl_coex.polling_enable = 0; + del_timer(&btrtl_coex.polling_timer); + } + } + + if (memcmp(msg, leave_ack, sizeof(leave_ack)) == 0) { + RTKBT_DBG("receive leave ack from wifi"); + } + + if (event_code == 0xFE) { + total_length = *p++; + extension_event = *p++; + switch (extension_event) { + case RTK_HS_EXTENSION_EVENT_WIFI_SCAN: + operation = *p; + RTKBT_DBG("Recv WiFi scan notify event from WiFi, " + "op 0x%02x", operation); + break; + + case RTK_HS_EXTENSION_EVENT_HCI_BT_INFO_CONTROL: + rtk_handle_bt_info_control(p); + break; + + case RTK_HS_EXTENSION_EVENT_HCI_BT_COEX_CONTROL: + rtk_handle_bt_coex_control(p); + break; + + default: + break; + } + } + + if (event_code == 0x0E) { + p += 2; //length, number of complete packets + STREAM_TO_UINT16(wifi_opcode, p); + op_status = *p; + RTKBT_DBG("Recv cmd complete event from WiFi, op 0x%02x, " + "status 0x%02x", wifi_opcode, op_status); + } +} +#endif /* RTB_SOFTWARE_MAILBOX */ + +static inline void rtl_free_frags(struct rtl_coex_struct *coex) +{ + unsigned long flags; + + spin_lock_irqsave(&coex->rxlock, flags); + + coex->elen = 0; + coex->tbuff = NULL; + + spin_unlock_irqrestore(&coex->rxlock, flags); +} + +void rtk_btcoex_open(struct hci_dev *hdev) +{ + if (test_and_set_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) { + RTKBT_WARN("RTL COEX is already running."); + return; + } + + RTKBT_INFO("Open BTCOEX"); + + /* Just for test */ + //struct rtl_btinfo_ctl ctl; + + INIT_DELAYED_WORK(&btrtl_coex.fw_work, (void *)rtl_ev_work); +#ifdef RTB_SOFTWARE_MAILBOX +#ifdef RTK_COEX_OVER_SYMBOL + INIT_WORK(&rtw_work, rtw_work_func); + skb_queue_head_init(&rtw_q); + rtw_coex_on = 1; +#else + INIT_DELAYED_WORK(&btrtl_coex.sock_work, + (void *)udpsocket_recv_data); +#endif +#endif /* RTB_SOFTWARE_MAILBOX */ + INIT_DELAYED_WORK(&btrtl_coex.l2_work, (void *)rtl_l2_work); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 14, 0) +#ifdef RTB_SOFTWARE_MAILBOX + timer_setup(&btrtl_coex.polling_timer, polling_bt_info, 0); +#endif + timer_setup(&btrtl_coex.a2dp_count_timer, count_a2dp_packet_timeout, 0); + timer_setup(&btrtl_coex.pan_count_timer, count_pan_packet_timeout, 0); + timer_setup(&btrtl_coex.hogp_count_timer, count_hogp_packet_timeout, 0); +#else +#ifdef RTB_SOFTWARE_MAILBOX + setup_timer(&btrtl_coex.polling_timer, polling_bt_info, 0); +#endif + setup_timer(&btrtl_coex.a2dp_count_timer, count_a2dp_packet_timeout, 0); + setup_timer(&btrtl_coex.pan_count_timer, count_pan_packet_timeout, 0); + setup_timer(&btrtl_coex.hogp_count_timer, count_hogp_packet_timeout, 0); +#endif + + btrtl_coex.hdev = hdev; +#ifdef RTB_SOFTWARE_MAILBOX + btrtl_coex.wifi_on = 0; +#endif + + init_profile_hash(&btrtl_coex); + init_connection_hash(&btrtl_coex); + + btrtl_coex.pkt_type = 0; + btrtl_coex.expect = 0; + btrtl_coex.elen = 0; + btrtl_coex.tbuff = NULL; + +#ifdef RTB_SOFTWARE_MAILBOX +#ifndef RTK_COEX_OVER_SYMBOL + create_udpsocket(); +#endif + rtkbt_coexmsg_send(invite_req, sizeof(invite_req)); +#endif + + /* Just for test */ + //ctl.polling_enable = 1; + //ctl.polling_time = 1; + //ctl.autoreport_enable = 1; + //rtk_handle_bt_info_control((u8 *)&ctl); +} + +void rtk_btcoex_close(void) +{ + int kk = 0; + + if (!test_and_clear_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) { + RTKBT_WARN("RTL COEX is already closed."); + return; + } + + RTKBT_INFO("Close BTCOEX"); + +#ifdef RTB_SOFTWARE_MAILBOX + /* Close coex socket */ + if (btrtl_coex.wifi_on) + rtkbt_coexmsg_send(bt_leave, sizeof(bt_leave)); +#ifdef RTK_COEX_OVER_SYMBOL + rtw_coex_on = 0; + skb_queue_purge(&rtw_q); + cancel_work_sync(&rtw_work); +#else + cancel_delayed_work_sync(&btrtl_coex.sock_work); + if (btrtl_coex.sock_open) { + btrtl_coex.sock_open = 0; + RTKBT_DBG("release udp socket"); + sock_release(btrtl_coex.udpsock); + } +#endif + + /* Delete all timers */ + if (btrtl_coex.polling_enable) { + btrtl_coex.polling_enable = 0; + del_timer_sync(&(btrtl_coex.polling_timer)); + } +#endif /* RTB_SOFTWARE_MAILBOX */ + + del_timer_sync(&btrtl_coex.a2dp_count_timer); + del_timer_sync(&btrtl_coex.pan_count_timer); + del_timer_sync(&btrtl_coex.hogp_count_timer); + + cancel_delayed_work_sync(&btrtl_coex.fw_work); + cancel_delayed_work_sync(&btrtl_coex.l2_work); + + flush_connection_hash(&btrtl_coex); + flush_profile_hash(&btrtl_coex); + btrtl_coex.profile_bitmap = 0; + btrtl_coex.profile_status = 0; + for (kk = 0; kk < 8; kk++) + btrtl_coex.profile_refcount[kk] = 0; + + rtl_free_frags(&btrtl_coex); + RTKBT_DBG("-x"); +} + +void rtk_btcoex_probe(struct hci_dev *hdev) +{ + btrtl_coex.hdev = hdev; + spin_lock_init(&btrtl_coex.spin_lock_sock); + spin_lock_init(&btrtl_coex.spin_lock_profile); +} + +void rtk_btcoex_init(void) +{ + RTKBT_DBG("%s: version: %s", __func__, RTK_VERSION); + RTKBT_DBG("create workqueue"); +#ifdef RTB_SOFTWARE_MAILBOX +#ifdef RTK_COEX_OVER_SYMBOL + RTKBT_INFO("Coex over Symbol"); + rtw_wq = create_workqueue("btcoexwork"); + skb_queue_head_init(&rtw_q); +#else + RTKBT_INFO("Coex over UDP"); + btrtl_coex.sock_wq = create_workqueue("btudpwork"); +#endif +#endif /* RTB_SOFTWARE_MAILBOX */ + btrtl_coex.fw_wq = create_workqueue("btfwwork"); + rtl_alloc_buff(&btrtl_coex); + spin_lock_init(&btrtl_coex.rxlock); +} + +void rtk_btcoex_exit(void) +{ + RTKBT_DBG("%s: destroy workqueue", __func__); +#ifdef RTB_SOFTWARE_MAILBOX +#ifdef RTK_COEX_OVER_SYMBOL + flush_workqueue(rtw_wq); + destroy_workqueue(rtw_wq); +#else + flush_workqueue(btrtl_coex.sock_wq); + destroy_workqueue(btrtl_coex.sock_wq); +#endif +#endif + flush_workqueue(btrtl_coex.fw_wq); + destroy_workqueue(btrtl_coex.fw_wq); + rtl_free_buff(&btrtl_coex); +} diff --git a/drivers/bluetooth/rtkbt/rtk_coex.h b/drivers/bluetooth/rtkbt/rtk_coex.h new file mode 100644 index 000000000..31c97aea6 --- /dev/null +++ b/drivers/bluetooth/rtkbt/rtk_coex.h @@ -0,0 +1,373 @@ +/* +* +* Realtek Bluetooth USB driver +* +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* 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. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +*/ +#include +#include + +/*********************************** +** Realtek - For coexistence ** +***********************************/ +#define BTRTL_HCIUSB 0 +#define BTRTL_HCIUART 1 + +#define BTRTL_HCI_IF BTRTL_HCIUART + +#define TRUE 1 +#define FALSE 0 + +#define CONNECT_PORT 30001 +#define CONNECT_PORT_WIFI 30000 + +#define invite_req "INVITE_REQ" +#define invite_rsp "INVITE_RSP" +#define attend_req "ATTEND_REQ" +#define attend_ack "ATTEND_ACK" +#define wifi_leave "WIFI_LEAVE" +#define leave_ack "LEAVE_ACK" +#define bt_leave "BT_LEAVE" + +#define HCI_OP_PERIODIC_INQ 0x0403 +#define HCI_EV_LE_META 0x3e +#define HCI_EV_LE_CONN_COMPLETE 0x01 +#define HCI_EV_LE_CONN_UPDATE_COMPLETE 0x03 +#define HCI_EV_LE_ENHANCED_CONN_COMPLETE 0x0a + +//vendor cmd to fw +#define HCI_VENDOR_ENABLE_PROFILE_REPORT_COMMAND 0xfc18 +#define HCI_VENDOR_SET_PROFILE_REPORT_COMMAND 0xfc19 +#define HCI_VENDOR_MAILBOX_CMD 0xfc8f +#define HCI_VENDOR_SET_BITPOOL 0xfc51 + +//subcmd to fw +#define HCI_VENDOR_SUB_CMD_WIFI_CHANNEL_AND_BANDWIDTH_CMD 0x11 +#define HCI_VENDOR_SUB_CMD_WIFI_FORCE_TX_POWER_CMD 0x17 +#define HCI_VENDOR_SUB_CMD_BT_ENABLE_IGNORE_WLAN_ACT_CMD 0x1B +#define HCI_VENDOR_SUB_CMD_BT_REPORT_CONN_SCO_INQ_INFO 0x23 +#define HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_STATUS_INFO 0x27 +#define HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_ENABLE 0x28 +#define HCI_VENDOR_SUB_CMD_BT_SET_TXRETRY_REPORT_PARAM 0x29 +#define HCI_VENDOR_SUB_CMD_BT_SET_PTATABLE 0x2A +#define HCI_VENDOR_SUB_CMD_SET_BT_PSD_MODE 0x31 +#define HCI_VENDOR_SUB_CMD_SET_BT_LNA_CONSTRAINT 0x32 +#define HCI_VENDOR_SUB_CMD_GET_AFH_MAP_L 0x40 +#define HCI_VENDOR_SUB_CMD_GET_AFH_MAP_M 0x41 +#define HCI_VENDOR_SUB_CMD_GET_AFH_MAP_H 0x42 +#define HCI_VENDOR_SUB_CMD_RD_REG_REQ 0x43 +#define HCI_VENDOR_SUB_CMD_WR_REG_REQ 0x44 + +#define HCI_EV_VENDOR_SPECIFIC 0xff + +//sub event from fw start +#define HCI_VENDOR_PTA_REPORT_EVENT 0x24 +#define HCI_VENDOR_PTA_AUTO_REPORT_EVENT 0x25 + +//vendor cmd to wifi driver +#define HCI_GRP_VENDOR_SPECIFIC (0x3f << 10) +#define HCI_OP_HCI_EXTENSION_VERSION_NOTIFY (0x0100 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_OP_BT_OPERATION_NOTIFY (0x0102 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_OP_HCI_BT_INFO_NOTIFY (0x0106 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_OP_HCI_BT_COEX_NOTIFY (0x0107 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_OP_HCI_BT_PATCH_VER_NOTIFY (0x0108 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_OP_HCI_BT_AFH_MAP_NOTIFY (0x0109 | HCI_GRP_VENDOR_SPECIFIC) +#define HCI_OP_HCI_BT_REGISTER_VALUE_NOTIFY (0x010a | HCI_GRP_VENDOR_SPECIFIC) + +//bt info reason to wifi +#define HOST_RESPONSE 0 //Host response when receive the BT Info Control Event +#define POLLING_RESPONSE 1 //The BT Info response for polling by BT firmware. +#define AUTO_REPORT 2 //BT auto report by BT firmware. +#define STACK_REPORT_WHILE_DEVICE_D2 3 //Stack report when BT firmware is under power save state(ex:D2) + +// vendor event from wifi +#define RTK_HS_EXTENSION_EVENT_WIFI_SCAN 0x01 +#define RTK_HS_EXTENSION_EVENT_RADIO_STATUS_NOTIFY 0x02 +#define RTK_HS_EXTENSION_EVENT_HCI_BT_INFO_CONTROL 0x03 +#define RTK_HS_EXTENSION_EVENT_HCI_BT_COEX_CONTROL 0x04 + +//op code from wifi +#define BT_PATCH_VERSION_QUERY 0x00 +#define IGNORE_WLAN_ACTIVE_CONTROL 0x01 +#define LNA_CONSTRAIN_CONTROL 0x02 +#define BT_POWER_DECREASE_CONTROL 0x03 +#define BT_PSD_MODE_CONTROL 0x04 +#define WIFI_BW_CHNL_NOTIFY 0x05 +#define QUERY_BT_AFH_MAP 0x06 +#define BT_REGISTER_ACCESS 0x07 + +//bt operation to notify +#define BT_OPCODE_NONE 0 +#define BT_OPCODE_INQUIRY_START 1 +#define BT_OPCODE_INQUIRY_END 2 +#define BT_OPCODE_PAGE_START 3 +#define BT_OPCODE_PAGE_SUCCESS_END 4 +#define BT_OPCODE_PAGE_UNSUCCESS_END 5 +#define BT_OPCODE_PAIR_START 6 +#define BT_OPCODE_PAIR_END 7 +#define BT_OPCODE_ENABLE_BT 8 +#define BT_OPCODE_DISABLE_BT 9 + +#define HCI_EXTENSION_VERSION 0x0004 +#define HCI_CMD_PREAMBLE_SIZE 3 +#define PAN_PACKET_COUNT 5 + +#define STREAM_TO_UINT16(u16, p) {u16 = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); (p) += 2;} +#define UINT16_TO_STREAM(p, u16) {*(p)++ = (uint8_t)(u16); *(p)++ = (uint8_t)((u16) >> 8);} + +#define PSM_SDP 0x0001 +#define PSM_RFCOMM 0x0003 +#define PSM_PAN 0x000F +#define PSM_HID 0x0011 +#define PSM_HID_INT 0x0013 +#define PSM_AVCTP 0x0017 +#define PSM_AVDTP 0x0019 +#define PSM_FTP 0x1001 +#define PSM_BIP 0x1003 +#define PSM_OPP 0x1015 +//--add more if needed--// + +enum { + profile_sco = 0, + profile_hid = 1, + profile_a2dp = 2, + profile_pan = 3, + profile_hid_interval = 4, + profile_hogp = 5, + profile_voice = 6, + profile_sink = 7, + profile_max = 8 +}; + +#define A2DP_SIGNAL 0x01 +#define A2DP_MEDIA 0x02 +//profile info data +typedef struct { + struct list_head list; + uint16_t handle; + uint16_t psm; + uint16_t dcid; + uint16_t scid; + uint8_t profile_index; + uint8_t flags; +} rtk_prof_info, *prtk_prof_info; + +//profile info for each connection +typedef struct rtl_hci_conn { + struct list_head list; + uint16_t handle; + uint8_t type; // 0:l2cap, 1:sco/esco, 2:le + uint8_t profile_bitmap; + int8_t profile_refcount[8]; +} rtk_conn_prof, *prtk_conn_prof; + +#ifdef RTB_SOFTWARE_MAILBOX + +struct rtl_btinfo { + u8 cmd; + u8 len; + u8 data[6]; +}; +#define RTL_BTINFO_LEN (sizeof(struct rtl_btinfo)) +/* typedef struct { + * uint8_t cmd_index; + * uint8_t cmd_length; + * uint8_t link_status; + * uint8_t retry_cnt; + * uint8_t rssi; + * uint8_t mailbox_info; + * uint16_t acl_throughput; + * } hci_linkstatus_report; */ + +typedef struct { + uint8_t type; + uint32_t offset; + uint32_t value; +} hci_mailbox_register; + +struct rtl_btinfo_ctl { + uint8_t polling_enable; + uint8_t polling_time; + uint8_t autoreport_enable; +}; +#endif /* RTB_SOFTWARE_MAILBOX */ + +#define MAX_LEN_OF_HCI_EV 32 +#define NUM_RTL_HCI_EV 32 +struct rtl_hci_ev { + __u8 data[MAX_LEN_OF_HCI_EV]; + __u16 len; + struct list_head list; +}; + +#define L2_MAX_SUBSEC_LEN 128 +#define L2_MAX_PKTS 16 +struct rtl_l2_buff { + __u8 data[L2_MAX_SUBSEC_LEN]; + __u16 len; + __u16 out; + struct list_head list; +}; + +struct rtl_coex_struct { + struct list_head conn_hash; //hash for connections + struct list_head profile_list; //hash for profile info + struct hci_dev *hdev; +#ifdef RTB_SOFTWARE_MAILBOX + struct socket *udpsock; + struct sockaddr_in addr; + struct sockaddr_in wifi_addr; + struct timer_list polling_timer; +#endif + struct timer_list a2dp_count_timer; + struct timer_list pan_count_timer; + struct timer_list hogp_count_timer; +#ifdef RTB_SOFTWARE_MAILBOX + struct workqueue_struct *sock_wq; + struct delayed_work sock_work; +#endif + struct workqueue_struct *fw_wq; + struct delayed_work fw_work; + struct delayed_work l2_work; +#ifdef RTB_SOFTWARE_MAILBOX + struct sock *sk; +#endif + struct urb *urb; + spinlock_t spin_lock_sock; + spinlock_t spin_lock_profile; + uint32_t a2dp_packet_count; + uint32_t pan_packet_count; + uint32_t hogp_packet_count; + uint32_t voice_packet_count; + uint8_t profile_bitmap; + uint8_t profile_status; + int8_t profile_refcount[8]; + uint8_t ispairing; + uint8_t isinquirying; + uint8_t ispaging; +#ifdef RTB_SOFTWARE_MAILBOX + uint8_t wifi_state; + uint8_t autoreport; + uint8_t polling_enable; + uint8_t polling_interval; + uint8_t piconet_id; + uint8_t mode; + uint8_t afh_map[10]; +#endif + uint16_t hci_reversion; + uint16_t lmp_subversion; +#ifdef RTB_SOFTWARE_MAILBOX + uint8_t wifi_on; + uint8_t sock_open; +#endif + unsigned long cmd_last_tx; + + /* hci ev buff */ + struct list_head ev_used_list; + struct list_head ev_free_list; + + spinlock_t rxlock; + __u8 pkt_type; + __u16 expect; + __u8 *tbuff; + __u16 elen; + __u8 back_buff[HCI_MAX_EVENT_SIZE]; + + /* l2cap rx buff */ + struct list_head l2_used_list; + struct list_head l2_free_list; + + /* buff addr and size */ + spinlock_t buff_lock; + unsigned long pages_addr; + unsigned long buff_size; + +#define RTL_COEX_RUNNING (1 << 0) + unsigned long flags; + +}; + +#ifdef __LITTLE_ENDIAN +struct sbc_frame_hdr { + uint8_t syncword:8; /* Sync word */ + uint8_t subbands:1; /* Subbands */ + uint8_t allocation_method:1; /* Allocation method */ + uint8_t channel_mode:2; /* Channel mode */ + uint8_t blocks:2; /* Blocks */ + uint8_t sampling_frequency:2; /* Sampling frequency */ + uint8_t bitpool:8; /* Bitpool */ + uint8_t crc_check:8; /* CRC check */ +} __attribute__ ((packed)); + +/* NOTE: The code is copied from pa. + * only the bit field in 8-bit is affected by endian, not the 16-bit or 32-bit. + * why? + */ +struct rtp_header { + unsigned cc:4; + unsigned x:1; + unsigned p:1; + unsigned v:2; + + unsigned pt:7; + unsigned m:1; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +#else +/* big endian */ +struct sbc_frame_hdr { + uint8_t syncword:8; /* Sync word */ + uint8_t sampling_frequency:2; /* Sampling frequency */ + uint8_t blocks:2; /* Blocks */ + uint8_t channel_mode:2; /* Channel mode */ + uint8_t allocation_method:1; /* Allocation method */ + uint8_t subbands:1; /* Subbands */ + uint8_t bitpool:8; /* Bitpool */ + uint8_t crc_check:8; /* CRC check */ +} __attribute__ ((packed)); + +struct rtp_header { + unsigned v:2; + unsigned p:1; + unsigned x:1; + unsigned cc:4; + + unsigned m:1; + unsigned pt:7; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); +#endif /* __LITTLE_ENDIAN */ + +void rtk_btcoex_parse_event(uint8_t *buffer, int count); +void rtk_btcoex_parse_cmd(uint8_t *buffer, int count); +void rtk_btcoex_parse_l2cap_data_tx(uint8_t *buffer, int count); +void rtk_btcoex_parse_l2cap_data_rx(uint8_t *buffer, int count); + +void rtk_btcoex_open(struct hci_dev *hdev); +void rtk_btcoex_close(void); +void rtk_btcoex_probe(struct hci_dev *hdev); +void rtk_btcoex_init(void); +void rtk_btcoex_exit(void);