diff --git a/BSP/linux-kernel/arch/arm/configs/na51089_evb_cardv_defconfig_release b/BSP/linux-kernel/arch/arm/configs/na51089_evb_cardv_defconfig_release index d9f80e571..b56a8b886 100644 --- a/BSP/linux-kernel/arch/arm/configs/na51089_evb_cardv_defconfig_release +++ b/BSP/linux-kernel/arch/arm/configs/na51089_evb_cardv_defconfig_release @@ -80,6 +80,8 @@ CONFIG_PREEMPT_COUNT=y CONFIG_TICK_CPU_ACCOUNTING=y # CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set # CONFIG_IRQ_TIME_ACCOUNTING is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set # # RCU Subsystem @@ -96,6 +98,7 @@ CONFIG_LOG_BUF_SHIFT=15 CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=12 CONFIG_GENERIC_SCHED_CLOCK=y # CONFIG_CGROUPS is not set +# CONFIG_NAMESPACES is not set # CONFIG_CHECKPOINT_RESTORE is not set # CONFIG_SCHED_AUTOGROUP is not set # CONFIG_SYSFS_DEPRECATED is not set @@ -116,7 +119,8 @@ CONFIG_ANON_INODES=y CONFIG_HAVE_UID16=y CONFIG_BPF=y CONFIG_EXPERT=y -# CONFIG_MULTIUSER is not set +CONFIG_UID16=y +CONFIG_MULTIUSER=y # CONFIG_SGETMASK_SYSCALL is not set CONFIG_SYSFS_SYSCALL=y # CONFIG_SYSCTL_SYSCALL is not set @@ -128,7 +132,7 @@ CONFIG_PRINTK_NMI=y # CONFIG_BASE_FULL is not set CONFIG_FUTEX=y CONFIG_FUTEX_PI=y -# CONFIG_EPOLL is not set +CONFIG_EPOLL=y CONFIG_SIGNALFD=y CONFIG_TIMERFD=y CONFIG_EVENTFD=y @@ -653,7 +657,30 @@ CONFIG_BQL=y # CONFIG_NET_PKTGEN is not set # CONFIG_HAMRADIO is not set # CONFIG_CAN is not set -# CONFIG_BT is not set +CONFIG_BT=y +CONFIG_BT_BREDR=y +# CONFIG_BT_RFCOMM is not set +# CONFIG_BT_BNEP is not set +CONFIG_BT_HS=y +CONFIG_BT_LE=y +# CONFIG_BT_SELFTEST is not set +CONFIG_BT_DEBUGFS=y + +# +# Bluetooth device drivers +# +# CONFIG_BT_HCIBTUSB is not set +# CONFIG_BT_HCIBTSDIO is not set +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +# CONFIG_BT_HCIUART_BCSP is not set +CONFIG_BT_HCIUART_RTKH5=y +# CONFIG_BT_HCIUART_LL is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set +# CONFIG_BT_MRVL is not set # CONFIG_AF_RXRPC is not set # CONFIG_AF_KCM is not set CONFIG_WIRELESS=y @@ -2602,6 +2629,7 @@ CONFIG_KEYS=y # CONFIG_ENCRYPTED_KEYS is not set # CONFIG_KEY_DH_OPERATIONS is not set # CONFIG_SECURITY_DMESG_RESTRICT is not set +# CONFIG_SECURITY is not set # CONFIG_SECURITYFS is not set CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR=y # CONFIG_HARDENED_USERCOPY is not set @@ -2628,10 +2656,11 @@ CONFIG_CRYPTO_RNG_DEFAULT=y CONFIG_CRYPTO_AKCIPHER2=y CONFIG_CRYPTO_AKCIPHER=y CONFIG_CRYPTO_KPP2=y +CONFIG_CRYPTO_KPP=y CONFIG_CRYPTO_ACOMP2=y CONFIG_CRYPTO_RSA=y # CONFIG_CRYPTO_DH is not set -# CONFIG_CRYPTO_ECDH is not set +CONFIG_CRYPTO_ECDH=y CONFIG_CRYPTO_MANAGER=y CONFIG_CRYPTO_MANAGER2=y # CONFIG_CRYPTO_USER is not set @@ -2677,7 +2706,7 @@ CONFIG_CRYPTO_ECB=y # # Hash modes # -CONFIG_CRYPTO_CMAC=m +CONFIG_CRYPTO_CMAC=y CONFIG_CRYPTO_HMAC=y # CONFIG_CRYPTO_XCBC is not set # CONFIG_CRYPTO_VMAC is not set diff --git a/BSP/linux-kernel/drivers/bluetooth/Kconfig b/BSP/linux-kernel/drivers/bluetooth/Kconfig index 845b0314c..03576faf3 100644 --- a/BSP/linux-kernel/drivers/bluetooth/Kconfig +++ b/BSP/linux-kernel/drivers/bluetooth/Kconfig @@ -1,28 +1,10 @@ -# SPDX-License-Identifier: GPL-2.0 menu "Bluetooth device drivers" depends on BT -config BT_INTEL - tristate - select REGMAP - -config BT_BCM - tristate - select FW_LOADER - -config BT_RTL - tristate - select FW_LOADER - -config BT_QCA - tristate - select FW_LOADER - config BT_HCIBTUSB tristate "HCI USB driver" depends on USB - select BT_INTEL help Bluetooth HCI USB driver. This driver is required if you want to use Bluetooth devices with @@ -31,38 +13,6 @@ config BT_HCIBTUSB Say Y here to compile support for Bluetooth USB devices into the kernel or say M to compile it as module (btusb). -config BT_HCIBTUSB_AUTOSUSPEND - bool "Enable USB autosuspend for Bluetooth USB devices by default" - depends on BT_HCIBTUSB - help - Say Y here to enable USB autosuspend for Bluetooth USB devices by - default. - - This can be overridden by passing btusb.enable_autosuspend=[y|n] - on the kernel commandline. - -config BT_HCIBTUSB_BCM - bool "Broadcom protocol support" - depends on BT_HCIBTUSB - select BT_BCM - default y - help - The Broadcom protocol support enables firmware and patchram - download support for Broadcom Bluetooth controllers. - - Say Y here to compile support for Broadcom protocol. - -config BT_HCIBTUSB_RTL - bool "Realtek protocol support" - depends on BT_HCIBTUSB - select BT_RTL - default y - help - The Realtek protocol support enables firmware and configuration - download support for Realtek Bluetooth controllers. - - Say Y here to compile support for Realtek protocol. - config BT_HCIBTSDIO tristate "HCI SDIO driver" depends on MMC @@ -76,78 +26,51 @@ config BT_HCIBTSDIO config BT_HCIUART tristate "HCI UART driver" - depends on SERIAL_DEV_BUS || !SERIAL_DEV_BUS - depends on NVMEM || !NVMEM - depends on TTY help Bluetooth HCI UART driver. This driver is required if you want to use Bluetooth devices with - serial port interface. You will also need this driver if you have - UART based Bluetooth PCMCIA and CF devices like Xircom Credit Card + serial port interface. You will also need this driver if you have + UART based Bluetooth PCMCIA and CF devices like Xircom Credit Card adapter and BrainBoxes Bluetooth PC Card. Say Y here to compile support for Bluetooth UART devices into the kernel or say M to compile it as module (hci_uart). -config BT_HCIUART_SERDEV - bool - depends on SERIAL_DEV_BUS && BT_HCIUART - default y - config BT_HCIUART_H4 bool "UART (H4) protocol support" depends on BT_HCIUART help - UART (H4) is serial protocol for communication between Bluetooth - device and host. This protocol is required for most Bluetooth devices - with UART interface, including PCMCIA and CF cards. + UART (H4) is serial protocol for communication between Bluetooth + device and host. This protocol is required for most Bluetooth devices + with UART interface, including PCMCIA and CF cards. Say Y here to compile support for HCI UART (H4) protocol. -config BT_HCIUART_NOKIA - tristate "UART Nokia H4+ protocol support" - depends on BT_HCIUART - depends on BT_HCIUART_SERDEV - depends on GPIOLIB - depends on PM - select BT_HCIUART_H4 - select BT_BCM - help - Nokia H4+ is serial protocol for communication between Bluetooth - device and host. This protocol is required for Bluetooth devices - with UART interface in Nokia devices. - - Say Y here to compile support for Nokia's H4+ protocol. - config BT_HCIUART_BCSP bool "BCSP protocol support" depends on BT_HCIUART select BITREVERSE help - BCSP (BlueCore Serial Protocol) is serial protocol for communication + BCSP (BlueCore Serial Protocol) is serial protocol for communication between Bluetooth device and host. This protocol is required for non USB Bluetooth devices based on CSR BlueCore chip, including PCMCIA and CF cards. Say Y here to compile support for HCI BCSP protocol. -config BT_HCIUART_ATH3K - bool "Atheros AR300x serial support" +config BT_HCIUART_RTKH5 + bool "Realtek H5 protocol support" depends on BT_HCIUART - select BT_HCIUART_H4 help - HCIATH3K (HCI Atheros AR300x) is a serial protocol for - communication between host and Atheros AR300x Bluetooth devices. - This protocol enables AR300x chips to be enabled with - power management support. - Enable this if you have Atheros AR300x serial Bluetooth device. + Realtek H5 is serial protocol for communication + between Realtek Bluetooth device and host. This protocol is required for + Realtek uart h5 bluetooth controller - Say Y here to compile support for HCI UART ATH3K protocol. + Say Y here to compile support for Realtek HCI H5 protocol. config BT_HCIUART_LL bool "HCILL protocol support" - depends on BT_HCIUART_SERDEV - select BT_HCIUART_H4 + depends on BT_HCIUART help HCILL (HCI Low Level) is a serial protocol for communication between Bluetooth device and host. This protocol is required for @@ -156,95 +79,6 @@ config BT_HCIUART_LL Say Y here to compile support for HCILL protocol. -config BT_HCIUART_3WIRE - bool "Three-wire UART (H5) protocol support" - depends on BT_HCIUART - depends on BT_HCIUART_SERDEV - help - The HCI Three-wire UART Transport Layer makes it possible to - user the Bluetooth HCI over a serial port interface. The HCI - Three-wire UART Transport Layer assumes that the UART - communication may have bit errors, overrun errors or burst - errors and thereby making CTS/RTS lines unnecessary. - - Say Y here to compile support for Three-wire UART protocol. - -config BT_HCIUART_INTEL - bool "Intel protocol support" - depends on BT_HCIUART - depends on GPIOLIB - select BT_HCIUART_H4 - select BT_INTEL - help - The Intel protocol support enables Bluetooth HCI over serial - port interface for Intel Bluetooth controllers. - - Say Y here to compile support for Intel protocol. - -config BT_HCIUART_BCM - bool "Broadcom protocol support" - depends on BT_HCIUART - depends on BT_HCIUART_SERDEV - depends on (!ACPI || SERIAL_DEV_CTRL_TTYPORT) - depends on GPIOLIB - select BT_HCIUART_H4 - select BT_BCM - help - The Broadcom protocol support enables Bluetooth HCI over serial - port interface for Broadcom Bluetooth controllers. - - Say Y here to compile support for Broadcom protocol. - -config BT_HCIUART_RTL - bool "Realtek protocol support" - depends on BT_HCIUART - depends on BT_HCIUART_SERDEV - depends on GPIOLIB - depends on ACPI - select BT_HCIUART_3WIRE - select BT_RTL - help - The Realtek protocol support enables Bluetooth HCI over 3-Wire - serial port internface for Realtek Bluetooth controllers. - - Say Y here to compile support for Realtek protocol. - -config BT_HCIUART_QCA - bool "Qualcomm Atheros protocol support" - depends on BT_HCIUART - depends on BT_HCIUART_SERDEV - select BT_HCIUART_H4 - select BT_QCA - help - The Qualcomm Atheros protocol supports HCI In-Band Sleep feature - over serial port interface(H4) between controller and host. - This protocol is required for UART clock control for QCA Bluetooth - devices. - - Say Y here to compile support for QCA protocol. - -config BT_HCIUART_AG6XX - bool "Intel AG6XX protocol support" - depends on BT_HCIUART - select BT_HCIUART_H4 - select BT_INTEL - help - The Intel/AG6XX protocol support enables Bluetooth HCI over serial - port interface for Intel ibt 2.1 Bluetooth controllers. - - Say Y here to compile support for Intel AG6XX protocol. - -config BT_HCIUART_MRVL - bool "Marvell protocol support" - depends on BT_HCIUART - select BT_HCIUART_H4 - help - Marvell is serial protocol for communication between Bluetooth - device and host. This protocol is required for most Marvell Bluetooth - devices with UART interface. - - Say Y here to compile support for HCI MRVL protocol. - config BT_HCIBCM203X tristate "HCI BCM203x USB driver" depends on USB @@ -321,6 +155,22 @@ config BT_HCIBLUECARD Say Y here to compile support for HCI BlueCard devices into the kernel or say M to compile it as module (bluecard_cs). +config BT_HCIBTUART + tristate "HCI UART (PC Card) device driver" + depends on PCMCIA + help + Bluetooth HCI UART (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + an UART interface: + Xircom CreditCard Bluetooth Adapter + Xircom RealPort2 Bluetooth Adapter + Sphinx PICO Card + H-Soft blue+Card + Cyber-blue Compact Flash Card + + Say Y here to compile support for HCI UART devices into the + kernel or say M to compile it as module (btuart_cs). + config BT_HCIVHCI tristate "HCI VHCI (Virtual HCI device) driver" help @@ -336,7 +186,7 @@ config BT_MRVL The core driver to support Marvell Bluetooth devices. This driver is required if you want to support - Marvell Bluetooth devices, such as 8688/8787/8797/8887/8897/8997. + Marvell Bluetooth devices, such as 8688. Say Y here to compile Marvell Bluetooth driver into the kernel or say M to compile it as module. @@ -345,13 +195,12 @@ config BT_MRVL_SDIO tristate "Marvell BT-over-SDIO driver" depends on BT_MRVL && MMC select FW_LOADER - select WANT_DEV_COREDUMP help The driver for Marvell Bluetooth chipsets with SDIO interface. This driver is required if you want to use Marvell Bluetooth - devices with SDIO interface. Currently SD8688/SD8787/SD8797/SD8887/SD8897/SD8997 - chipsets are supported. + devices with SDIO interface. Currently only SD8688 chipset is + supported. Say Y here to compile support for Marvell BT-over-SDIO driver into the kernel or say M to compile it as module. @@ -368,49 +217,4 @@ config BT_ATH3K Say Y here to compile support for "Atheros firmware download driver" into the kernel or say M to compile it as module (ath3k). -config BT_WILINK - tristate "Texas Instruments WiLink7 driver" - depends on TI_ST - help - This enables the Bluetooth driver for Texas Instrument's BT/FM/GPS - combo devices. This makes use of shared transport line discipline - core driver to communicate with the BT core of the combo chip. - - Say Y here to compile support for Texas Instrument's WiLink7 driver - into the kernel or say M to compile it as module (btwilink). - -config BT_MTKUART - tristate "MediaTek HCI UART driver" - depends on SERIAL_DEV_BUS - help - MediaTek Bluetooth HCI UART driver. - This driver is required if you want to use MediaTek Bluetooth - with serial interface. - - Say Y here to compile support for MediaTek Bluetooth UART devices - into the kernel or say M to compile it as module (btmtkuart). - -config BT_QCOMSMD - tristate "Qualcomm SMD based HCI support" - depends on RPMSG || (COMPILE_TEST && RPMSG=n) - depends on QCOM_WCNSS_CTRL || (COMPILE_TEST && QCOM_WCNSS_CTRL=n) - select BT_QCA - help - Qualcomm SMD based HCI driver. - This driver is used to bridge HCI data onto the shared memory - channels to the WCNSS core. - - Say Y here to compile support for HCI over Qualcomm SMD into the - kernel or say M to compile as a module. - -config BT_HCIRSI - tristate - help - Redpine BT driver. - This driver handles BT traffic from upper layers and pass - to the RSI_91x coex module for further scheduling to device - - Say Y here to compile support for HCI over Redpine into the - kernel or say M to compile as a module. - endmenu diff --git a/BSP/linux-kernel/drivers/bluetooth/Makefile b/BSP/linux-kernel/drivers/bluetooth/Makefile index b7e393cfc..a45ee4403 100644 --- a/BSP/linux-kernel/drivers/bluetooth/Makefile +++ b/BSP/linux-kernel/drivers/bluetooth/Makefile @@ -1,48 +1,17 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile for the Linux Bluetooth HCI device drivers. -# +ifneq ($(KERNELRELEASE),) + obj-m := hci_uart.o + hci_uart-y := hci_ldisc.o hci_h4.o hci_rtk_h5.o rtk_coex.o + #EXTRA_CFLAGS += -DDEBUG -obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o -obj-$(CONFIG_BT_HCIUART) += hci_uart.o -obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o -obj-$(CONFIG_BT_HCIBPA10X) += bpa10x.o -obj-$(CONFIG_BT_HCIBFUSB) += bfusb.o -obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o -obj-$(CONFIG_BT_HCIBT3C) += bt3c_cs.o -obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o +else + PWD := $(shell pwd) + KVER := $(shell uname -r) + KDIR := /lib/modules/$(KVER)/build -obj-$(CONFIG_BT_HCIBTUSB) += btusb.o -obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules -obj-$(CONFIG_BT_INTEL) += btintel.o -obj-$(CONFIG_BT_ATH3K) += ath3k.o -obj-$(CONFIG_BT_MRVL) += btmrvl.o -obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o -obj-$(CONFIG_BT_WILINK) += btwilink.o -obj-$(CONFIG_BT_MTKUART) += btmtkuart.o -obj-$(CONFIG_BT_QCOMSMD) += btqcomsmd.o -obj-$(CONFIG_BT_BCM) += btbcm.o -obj-$(CONFIG_BT_RTL) += btrtl.o -obj-$(CONFIG_BT_QCA) += btqca.o +clean: + rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a -obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o - -obj-$(CONFIG_BT_HCIRSI) += btrsi.o - -btmrvl-y := btmrvl_main.o -btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o - -hci_uart-y := hci_ldisc.o -hci_uart-$(CONFIG_BT_HCIUART_SERDEV) += hci_serdev.o -hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o -hci_uart-$(CONFIG_BT_HCIUART_BCSP) += hci_bcsp.o -hci_uart-$(CONFIG_BT_HCIUART_LL) += hci_ll.o -hci_uart-$(CONFIG_BT_HCIUART_ATH3K) += hci_ath.o -hci_uart-$(CONFIG_BT_HCIUART_3WIRE) += hci_h5.o -hci_uart-$(CONFIG_BT_HCIUART_INTEL) += hci_intel.o -hci_uart-$(CONFIG_BT_HCIUART_BCM) += hci_bcm.o -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) +endif diff --git a/BSP/linux-kernel/drivers/bluetooth/hci_h4.c b/BSP/linux-kernel/drivers/bluetooth/hci_h4.c index 5d97d7762..580bdad53 100644 --- a/BSP/linux-kernel/drivers/bluetooth/hci_h4.c +++ b/BSP/linux-kernel/drivers/bluetooth/hci_h4.c @@ -24,7 +24,6 @@ */ #include - #include #include #include @@ -32,7 +31,6 @@ #include #include #include - #include #include #include @@ -40,18 +38,32 @@ #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) { @@ -59,7 +71,7 @@ static int h4_open(struct hci_uart *hu) BT_DBG("hu %p", hu); - h4 = kzalloc(sizeof(*h4), GFP_KERNEL); + h4 = kzalloc(sizeof(*h4), GFP_ATOMIC); if (!h4) return -ENOMEM; @@ -108,33 +120,174 @@ static int h4_enqueue(struct hci_uart *hu, struct sk_buff *skb) BT_DBG("hu %p skb %p", hu, skb); /* Prepend skb with frame type */ - memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); skb_queue_tail(&h4->txq, skb); return 0; } -static const struct h4_recv_pkt h4_recv_pkts[] = { - { H4_RECV_ACL, .recv = hci_recv_frame }, - { H4_RECV_SCO, .recv = hci_recv_frame }, - { H4_RECV_EVENT, .recv = hci_recv_frame }, -}; +#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, const void *data, int count) +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; - if (!test_bit(HCI_UART_REGISTERED, &hu->flags)) - return -EUNATCH; + BT_DBG("hu %p count %d rx_state %ld rx_count %ld", + hu, count, h4->rx_state, h4->rx_count); - h4->rx_skb = h4_recv_buf(hu->hdev, h4->rx_skb, data, count, - h4_recv_pkts, ARRAY_SIZE(h4_recv_pkts)); - if (IS_ERR(h4->rx_skb)) { - int err = PTR_ERR(h4->rx_skb); - bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err); - h4->rx_skb = NULL; - return err; + 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; @@ -146,9 +299,8 @@ static struct sk_buff *h4_dequeue(struct hci_uart *hu) return skb_dequeue(&h4->txq); } -static const struct hci_uart_proto h4p = { +static struct hci_uart_proto h4p = { .id = HCI_UART_H4, - .name = "H4", .open = h4_open, .close = h4_close, .recv = h4_recv, @@ -159,132 +311,17 @@ static const struct hci_uart_proto h4p = { int __init h4_init(void) { - return hci_uart_register_proto(&h4p); + 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); } - -struct sk_buff *h4_recv_buf(struct hci_dev *hdev, struct sk_buff *skb, - const unsigned char *buffer, int count, - const struct h4_recv_pkt *pkts, int pkts_count) -{ - struct hci_uart *hu = hci_get_drvdata(hdev); - u8 alignment = hu->alignment ? hu->alignment : 1; - - /* Check for error from previous call */ - if (IS_ERR(skb)) - skb = NULL; - - while (count) { - int i, len; - - /* remove padding bytes from buffer */ - for (; hu->padding && count > 0; hu->padding--) { - count--; - buffer++; - } - if (!count) - break; - - if (!skb) { - for (i = 0; i < pkts_count; i++) { - if (buffer[0] != (&pkts[i])->type) - continue; - - skb = bt_skb_alloc((&pkts[i])->maxlen, - GFP_ATOMIC); - if (!skb) - return ERR_PTR(-ENOMEM); - - hci_skb_pkt_type(skb) = (&pkts[i])->type; - hci_skb_expect(skb) = (&pkts[i])->hlen; - break; - } - - /* Check for invalid packet type */ - if (!skb) - return ERR_PTR(-EILSEQ); - - count -= 1; - buffer += 1; - } - - len = min_t(uint, hci_skb_expect(skb) - skb->len, count); - skb_put_data(skb, buffer, len); - - count -= len; - buffer += len; - - /* Check for partial packet */ - if (skb->len < hci_skb_expect(skb)) - continue; - - for (i = 0; i < pkts_count; i++) { - if (hci_skb_pkt_type(skb) == (&pkts[i])->type) - break; - } - - if (i >= pkts_count) { - kfree_skb(skb); - return ERR_PTR(-EILSEQ); - } - - if (skb->len == (&pkts[i])->hlen) { - u16 dlen; - - switch ((&pkts[i])->lsize) { - case 0: - /* No variable data length */ - dlen = 0; - break; - case 1: - /* Single octet variable length */ - dlen = skb->data[(&pkts[i])->loff]; - hci_skb_expect(skb) += dlen; - - if (skb_tailroom(skb) < dlen) { - kfree_skb(skb); - return ERR_PTR(-EMSGSIZE); - } - break; - case 2: - /* Double octet variable length */ - dlen = get_unaligned_le16(skb->data + - (&pkts[i])->loff); - hci_skb_expect(skb) += dlen; - - if (skb_tailroom(skb) < dlen) { - kfree_skb(skb); - return ERR_PTR(-EMSGSIZE); - } - break; - default: - /* Unsupported variable length */ - kfree_skb(skb); - return ERR_PTR(-EILSEQ); - } - - if (!dlen) { - hu->padding = (skb->len - 1) % alignment; - hu->padding = (alignment - hu->padding) % alignment; - - /* No more data, complete frame */ - (&pkts[i])->recv(hdev, skb); - skb = NULL; - } - } else { - hu->padding = (skb->len - 1) % alignment; - hu->padding = (alignment - hu->padding) % alignment; - - /* Complete frame */ - (&pkts[i])->recv(hdev, skb); - skb = NULL; - } - } - - return skb; -} -EXPORT_SYMBOL_GPL(h4_recv_buf); diff --git a/BSP/linux-kernel/drivers/bluetooth/hci_ldisc.c b/BSP/linux-kernel/drivers/bluetooth/hci_ldisc.c index efeb8137e..fb396ed94 100644 --- a/BSP/linux-kernel/drivers/bluetooth/hci_ldisc.c +++ b/BSP/linux-kernel/drivers/bluetooth/hci_ldisc.c @@ -24,7 +24,6 @@ */ #include - #include #include #include @@ -32,7 +31,6 @@ #include #include #include - #include #include #include @@ -40,21 +38,53 @@ #include #include #include -#include -#include - +#include +#include #include #include -#include "btintel.h" -#include "btbcm.h" #include "hci_uart.h" -#define VERSION "2.3" +#define NEW_TX_SCHED_POLICY -static const struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; +#if WOBT_NOTIFY +#include +#endif -int hci_uart_register_proto(const struct hci_uart_proto *p) +#ifdef BTCOEX +#include "rtk_coex.h" +#endif + +#define VERSION "2.2.0d4bd5a.20230801-094613" + + +#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; @@ -64,12 +94,10 @@ int hci_uart_register_proto(const struct hci_uart_proto *p) hup[p->id] = p; - BT_INFO("HCI UART protocol %s registered", p->name); - return 0; } -int hci_uart_unregister_proto(const struct hci_uart_proto *p) +int hci_uart_unregister_proto(struct hci_uart_proto *p) { if (p->id >= HCI_UART_MAX_PROTO) return -EINVAL; @@ -82,7 +110,7 @@ int hci_uart_unregister_proto(const struct hci_uart_proto *p) return 0; } -static const struct hci_uart_proto *hci_uart_get_proto(unsigned int id) +static struct hci_uart_proto *hci_uart_get_proto(unsigned int id) { if (id >= HCI_UART_MAX_PROTO) return NULL; @@ -110,17 +138,79 @@ static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) } } +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) { - percpu_down_read(&hu->proto_lock); + hci_proto_read_lock(hu); if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) skb = hu->proto->dequeue(hu); - percpu_up_read(&hu->proto_lock); + hci_proto_read_unlock(hu); } else { hu->tx_skb = NULL; } @@ -128,35 +218,60 @@ static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) return skb; } +/* This may be called in an IRQ context */ int hci_uart_tx_wakeup(struct hci_uart *hu) { - /* This may be called in an IRQ context, so we can't sleep. Therefore - * we try to acquire the lock only, and if that 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 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 (!percpu_down_read_trylock(&hu->proto_lock)) + if (!hci_proto_read_trylock(hu)) return 0; + /* proto_lock is locked */ if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) goto no_schedule; - if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { - set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); +#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: - percpu_up_read(&hu->proto_lock); + hci_proto_read_unlock(hu); return 0; } -EXPORT_SYMBOL_GPL(hci_uart_tx_wakeup); static void hci_uart_write_work(struct work_struct *work) { @@ -169,7 +284,7 @@ static void hci_uart_write_work(struct work_struct *work) * and error value ? */ -restart: + restart: clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); while ((skb = hci_uart_dequeue(hu))) { @@ -185,76 +300,33 @@ restart: break; } - hci_uart_tx_complete(hu, hci_skb_pkt_type(skb)); + 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; - - clear_bit(HCI_UART_SENDING, &hu->tx_state); -} - -void hci_uart_init_work(struct work_struct *work) -{ - struct hci_uart *hu = container_of(work, struct hci_uart, init_ready); - int err; - struct hci_dev *hdev; - - if (!test_and_clear_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags)) - return; - - err = hci_register_dev(hu->hdev); - if (err < 0) { - BT_ERR("Can't register HCI device"); - clear_bit(HCI_UART_PROTO_READY, &hu->flags); - hu->proto->close(hu); - hdev = hu->hdev; - hu->hdev = NULL; - hci_free_dev(hdev); - return; +#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; } - set_bit(HCI_UART_REGISTERED, &hu->flags); -} + clear_bit(HCI_UART_SENDING, &hu->tx_state); + up(&hu->tx_sem); +#endif -int hci_uart_init_ready(struct hci_uart *hu) -{ - if (!test_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags)) - return -EALREADY; - - schedule_work(&hu->init_ready); - - return 0; + return; } /* ------- Interface to HCI layer ------ */ -/* Reset device */ -static int hci_uart_flush(struct hci_dev *hdev) -{ - struct hci_uart *hu = hci_get_drvdata(hdev); - struct tty_struct *tty = hu->tty; - - BT_DBG("hdev %p tty %p", hdev, tty); - - 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); - - percpu_down_read(&hu->proto_lock); - - if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) - hu->proto->flush(hu); - - percpu_up_read(&hu->proto_lock); - - return 0; -} - /* Initialize device */ static int hci_uart_open(struct hci_dev *hdev) { @@ -263,218 +335,579 @@ static int hci_uart_open(struct hci_dev *hdev) /* Undo clearing this from hci_uart_close() */ hdev->flush = hci_uart_flush; + set_bit(HCI_UP, &hdev->flags); +#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_DBG("hdev %p", 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); - hci_uart_flush(hdev); hdev->flush = NULL; + +#ifdef BTCOEX + rtk_btcoex_close(); +#endif + return 0; } /* Send frames from HCI layer */ -static int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +#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 { - struct hci_uart *hu = hci_get_drvdata(hdev); +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0) + struct hci_dev *hdev = (struct hci_dev *)skb->dev; +#endif + struct hci_uart *hu; - BT_DBG("%s: type %d len %d", hdev->name, hci_skb_pkt_type(skb), + 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); - percpu_down_read(&hu->proto_lock); +#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)) { - percpu_up_read(&hu->proto_lock); + hci_proto_read_unlock(hu); return -EUNATCH; } hu->proto->enqueue(hu, skb); - percpu_up_read(&hu->proto_lock); + hci_proto_read_unlock(hu); hci_uart_tx_wakeup(hu); return 0; } -/* Check the underlying device or tty has flow control support */ -bool hci_uart_has_flow_control(struct hci_uart *hu) +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 4, 0) +static void hci_uart_destruct(struct hci_dev *hdev) { - /* serdev nodes check if the needed operations are present */ - if (hu->serdev) - return true; - - if (hu->tty->driver->ops->tiocmget && hu->tty->driver->ops->tiocmset) - return true; - - return false; -} - -/* Flow control or un-flow control the device */ -void hci_uart_set_flow_control(struct hci_uart *hu, bool enable) -{ - struct tty_struct *tty = hu->tty; - struct ktermios ktermios; - int status; - unsigned int set = 0; - unsigned int clear = 0; - - if (hu->serdev) { - serdev_device_set_flow_control(hu->serdev, !enable); - serdev_device_set_rts(hu->serdev, !enable); + if (!hdev) return; - } - if (enable) { - /* Disable hardware flow control */ - ktermios = tty->termios; - ktermios.c_cflag &= ~CRTSCTS; - status = tty_set_termios(tty, &ktermios); - BT_DBG("Disabling hardware flow control: %s", - status ? "failed" : "success"); - - /* Clear RTS to prevent the device from sending */ - /* Most UARTs need OUT2 to enable interrupts */ - status = tty->driver->ops->tiocmget(tty); - BT_DBG("Current tiocm 0x%x", status); - - set &= ~(TIOCM_OUT2 | TIOCM_RTS); - clear = ~set; - set &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | - TIOCM_OUT2 | TIOCM_LOOP; - clear &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | - TIOCM_OUT2 | TIOCM_LOOP; - status = tty->driver->ops->tiocmset(tty, set, clear); - BT_DBG("Clearing RTS: %s", status ? "failed" : "success"); - } else { - /* Set RTS to allow the device to send again */ - status = tty->driver->ops->tiocmget(tty); - BT_DBG("Current tiocm 0x%x", status); - - set |= (TIOCM_OUT2 | TIOCM_RTS); - clear = ~set; - set &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | - TIOCM_OUT2 | TIOCM_LOOP; - clear &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | - TIOCM_OUT2 | TIOCM_LOOP; - status = tty->driver->ops->tiocmset(tty, set, clear); - BT_DBG("Setting RTS: %s", status ? "failed" : "success"); - - /* Re-enable hardware flow control */ - ktermios = tty->termios; - ktermios.c_cflag |= CRTSCTS; - status = tty_set_termios(tty, &ktermios); - BT_DBG("Enabling hardware flow control: %s", - status ? "failed" : "success"); - } + BT_DBG("%s", hdev->name); + kfree(hdev->driver_data); } +#endif -void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed, - unsigned int oper_speed) +#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) { - hu->init_speed = init_speed; - hu->oper_speed = oper_speed; + void *tmp = skb_put(skb, len); + + memcpy(tmp, data, len); + + return tmp; } +#endif -void hci_uart_set_baudrate(struct hci_uart *hu, unsigned int speed) +static int hci_uart_async_send(struct hci_uart *hu, u16 opcode, + u32 plen, const void *param) { - struct tty_struct *tty = hu->tty; - struct ktermios ktermios; - - ktermios = tty->termios; - ktermios.c_cflag &= ~CBAUD; - tty_termios_encode_baud_rate(&ktermios, speed, speed); - - /* tty_set_termios() return not checked as it is always 0 */ - tty_set_termios(tty, &ktermios); - - BT_DBG("%s: New tty speeds: %d/%d", hu->hdev->name, - tty->termios.c_ispeed, tty->termios.c_ospeed); -} - -static int hci_uart_setup(struct hci_dev *hdev) -{ - struct hci_uart *hu = hci_get_drvdata(hdev); - struct hci_rp_read_local_version *ver; + int len = HCI_COMMAND_HDR_SIZE + plen; + struct hci_command_hdr *hdr; struct sk_buff *skb; - unsigned int speed; - int err; - /* Init speed if any */ - if (hu->init_speed) - speed = hu->init_speed; - else if (hu->proto->init_speed) - speed = hu->proto->init_speed; - else - speed = 0; + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; - if (speed) - hci_uart_set_baudrate(hu, speed); + hdr = (struct hci_command_hdr *)skb_put(skb, HCI_COMMAND_HDR_SIZE); + hdr->opcode = cpu_to_le16(opcode); + hdr->plen = plen; - /* Operational speed if any */ - if (hu->oper_speed) - speed = hu->oper_speed; - else if (hu->proto->oper_speed) - speed = hu->proto->oper_speed; - else - speed = 0; + if (plen) + memcpy(skb_put(skb, plen), param, plen); - if (hu->proto->set_baudrate && speed) { - err = hu->proto->set_baudrate(hu, speed); - if (!err) - hci_uart_set_baudrate(hu, speed); - } + BT_INFO("rtl: skb len %d", skb->len); - if (hu->proto->setup) - return hu->proto->setup(hu); + bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; - if (!test_bit(HCI_UART_VND_DETECT, &hu->hdev_flags)) - return 0; +#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 - skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL, - HCI_INIT_TIMEOUT); + /* 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("%s: Reading local version information failed (%ld)", - hdev->name, PTR_ERR(skb)); - return 0; + BT_ERR("rtl: Could not read lmp subversion"); + return PTR_ERR(skb); } - if (skb->len != sizeof(*ver)) { - BT_ERR("%s: Event length mismatch for version information", - hdev->name); - goto done; + 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_rp_read_local_version *)skb->data; + 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); - switch (le16_to_cpu(ver->manufacturer)) { -#ifdef CONFIG_BT_HCIUART_INTEL - case 2: - hdev->set_bdaddr = btintel_set_bdaddr; - btintel_check_bdaddr(hdev); + 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 + +#ifndef RTKBT_POWERKEY_WAKEUP + /*for any key wakeup, don't need to send 0xfc28 */ break; #endif -#ifdef CONFIG_BT_HCIUART_BCM - case 15: - hdev->set_bdaddr = btbcm_set_bdaddr; - btbcm_check_bdaddr(hdev); + 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); +#ifndef RTKBT_POWERKEY_WAKEUP + /*for any key wakeup, keep connections for key event report */ break; #endif + 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; +} + +int rtkbt_shutdown_notify(struct notifier_block *notifier, + ulong pm_event, void *unused) +{ + int result; + struct hci_uart *hu = container_of(notifier, struct hci_uart, shutdown_notifier); + + BT_INFO("%s: pm_event %ld", __func__, pm_event); + switch (pm_event) { + case SYS_POWER_OFF: + case SYS_RESTART: + result = rtkbt_notify_suspend(hu); + if (result < 0) { + BT_ERR("rtkbt_notify_suspend error: %d", result); + } + break; default: break; } -done: - kfree_skb(skb); - return 0; + return NOTIFY_DONE; } +#endif /* ------ LDISC part ------ */ /* hci_uart_tty_open * - * Called when line discipline changed to HCI_UART. + * Called when line discipline changed to HCI_UART. * * Arguments: * tty pointer to tty info structure @@ -483,18 +916,33 @@ done: */ static int hci_uart_tty_open(struct tty_struct *tty) { - struct hci_uart *hu; + 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; - hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL); - if (!hu) { + if (!(hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL))) { BT_ERR("Can't allocate control structure"); return -ENFILE; } @@ -503,32 +951,44 @@ static int hci_uart_tty_open(struct tty_struct *tty) hu->tty = tty; tty->receive_room = 65536; - /* disable alignment support by default */ - hu->alignment = 1; - hu->padding = 0; - - INIT_WORK(&hu->init_ready, hci_uart_init_work); INIT_WORK(&hu->write_work, hci_uart_write_work); - percpu_init_rwsem(&hu->proto_lock); + hci_proto_init_rwlock(hu); + sema_init(&hu->tx_sem, 1); - /* Flush any pending characters in the driver */ + /* 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); + /* Register POWER-OFF notifier */ + + BT_INFO("%s, register power off", __func__); + hu->shutdown_notifier.notifier_call = rtkbt_shutdown_notify; + register_reboot_notifier(&hu->shutdown_notifier); +#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. + * 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 = tty->disc_data; + struct hci_uart *hu = (void *)tty->disc_data; struct hci_dev *hdev; - BT_DBG("tty %p", tty); + BT_INFO("%s: tty %p", __func__, tty); /* Detach from the tty */ tty->disc_data = NULL; @@ -541,9 +1001,9 @@ static void hci_uart_tty_close(struct tty_struct *tty) hci_uart_close(hdev); if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { - percpu_down_write(&hu->proto_lock); + hci_proto_write_lock(hu); clear_bit(HCI_UART_PROTO_READY, &hu->flags); - percpu_up_write(&hu->proto_lock); + hci_proto_write_unlock(hu); cancel_work_sync(&hu->write_work); @@ -556,22 +1016,26 @@ static void hci_uart_tty_close(struct tty_struct *tty) } clear_bit(HCI_UART_PROTO_SET, &hu->flags); - percpu_free_rwsem(&hu->proto_lock); + hci_proto_free_rwlock(hu); +#if WOBT_NOTIFY + unregister_pm_notifier(&hu->pm_notify_block); + unregister_reboot_notifier(&hu->shutdown_notifier); +#endif kfree(hu); } /* hci_uart_tty_wakeup() * - * Callback for transmit wakeup. Called when low level - * device driver can accept more send data. + * 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 = tty->disc_data; + struct hci_uart *hu = (void *)tty->disc_data; BT_DBG(""); @@ -589,8 +1053,8 @@ static void hci_uart_tty_wakeup(struct tty_struct *tty) /* hci_uart_tty_receive() * - * Called by tty low level driver when receive data is - * available. + * Called by tty low level driver when receive data is + * available. * * Arguments: tty pointer to tty isntance data * data pointer to received data @@ -599,26 +1063,37 @@ static void hci_uart_tty_wakeup(struct tty_struct *tty) * * Return Value: None */ -static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, +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 = tty->disc_data; + 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; - percpu_down_read(&hu->proto_lock); + hci_proto_read_lock(hu); if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) { - percpu_up_read(&hu->proto_lock); + 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 */ - hu->proto->recv(hu, data, count); - percpu_up_read(&hu->proto_lock); + proto_receive(hu, (void *)data, count); +#endif if (hu->hdev) hu->hdev->stat.byte_rx += count; @@ -629,9 +1104,8 @@ static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, static int hci_uart_register_dev(struct hci_uart *hu) { struct hci_dev *hdev; - int err; - BT_DBG(""); + BT_INFO("hci_uart_register_dev"); /* Initialize and register HCI device */ hdev = hci_alloc_dev(); @@ -642,91 +1116,130 @@ static int hci_uart_register_dev(struct hci_uart *hu) 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 - /* Only when vendor specific setup callback is provided, consider - * the manufacturer information valid. This avoids filling in the - * value for Ericsson when nothing is specified. - */ - if (hu->proto->setup) - hdev->manufacturer = hu->proto->manufacturer; - - hdev->open = hci_uart_open; + hdev->open = hci_uart_open; hdev->close = hci_uart_close; hdev->flush = hci_uart_flush; - hdev->send = hci_uart_send_frame; - hdev->setup = hci_uart_setup; + 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 - /* Only call open() for the protocol after hdev is fully initialized as - * open() (or a timer/workqueue it starts) may attempt to reference it. - */ - err = hu->proto->open(hu); - if (err) { - hu->hdev = NULL; - hci_free_dev(hdev); - return err; - } +#if HCI_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks); +#endif - if (test_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags)) - return 0; +#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"); - hu->proto->close(hu); - hu->hdev = NULL; 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) { - const struct hci_uart_proto *p; + struct hci_uart_proto *p; int err; p = hci_uart_get_proto(id); if (!p) return -EPROTONOSUPPORT; - hu->proto = p; + 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; } - set_bit(HCI_UART_PROTO_READY, &hu->flags); 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_INIT_PENDING) | - BIT(HCI_UART_EXT_CONFIG) | - BIT(HCI_UART_VND_DETECT); + BIT(HCI_UART_EXT_CONFIG); if (flags & ~valid_flags) return -EINVAL; @@ -735,6 +1248,7 @@ static int hci_uart_set_flags(struct hci_uart *hu, unsigned long flags) return 0; } +#endif /* hci_uart_tty_ioctl() * @@ -749,10 +1263,15 @@ static int hci_uart_set_flags(struct hci_uart *hu, unsigned long flags) * * Return Value: Command dependent */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) +static int hci_uart_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +#else static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) +#endif { - struct hci_uart *hu = tty->disc_data; + struct hci_uart *hu = (void *)tty->disc_data; int err = 0; BT_DBG(""); @@ -789,7 +1308,11 @@ static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file *file, if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) err = -EBUSY; else +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) err = hci_uart_set_flags(hu, arg); +#else + hu->hdev_flags = arg; +#endif break; case HCIUARTGETFLAGS: @@ -797,9 +1320,13 @@ static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file *file, break; 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; } @@ -807,8 +1334,16 @@ static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file *file, /* * 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 __user *buf, size_t nr) + 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; } @@ -819,69 +1354,62 @@ static ssize_t hci_uart_tty_write(struct tty_struct *tty, struct file *file, return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) static __poll_t hci_uart_tty_poll(struct tty_struct *tty, - struct file *filp, poll_table *wait) + struct file *filp, poll_table * wait) +#else +static unsigned int hci_uart_tty_poll(struct tty_struct *tty, + struct file *filp, poll_table * wait) +#endif { 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) { - static struct tty_ldisc_ops hci_uart_ldisc; int err; BT_INFO("HCI UART driver ver %s", VERSION); /* Register the tty discipline */ - - memset(&hci_uart_ldisc, 0, sizeof(hci_uart_ldisc)); - hci_uart_ldisc.magic = TTY_LDISC_MAGIC; - hci_uart_ldisc.name = "n_hci"; - hci_uart_ldisc.open = hci_uart_tty_open; - hci_uart_ldisc.close = hci_uart_tty_close; - hci_uart_ldisc.read = hci_uart_tty_read; - hci_uart_ldisc.write = hci_uart_tty_write; - hci_uart_ldisc.ioctl = hci_uart_tty_ioctl; - hci_uart_ldisc.poll = hci_uart_tty_poll; - hci_uart_ldisc.receive_buf = hci_uart_tty_receive; - hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup; - hci_uart_ldisc.owner = THIS_MODULE; - - err = tty_register_ldisc(N_HCI, &hci_uart_ldisc); - if (err) { +#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 -#ifdef CONFIG_BT_HCIUART_BCSP - bcsp_init(); -#endif -#ifdef CONFIG_BT_HCIUART_LL - ll_init(); -#endif -#ifdef CONFIG_BT_HCIUART_ATH3K - ath_init(); -#endif -#ifdef CONFIG_BT_HCIUART_3WIRE + /* Add realtek h5 support */ h5_init(); -#endif -#ifdef CONFIG_BT_HCIUART_INTEL - intel_init(); -#endif -#ifdef CONFIG_BT_HCIUART_BCM - bcm_init(); -#endif -#ifdef CONFIG_BT_HCIUART_QCA - qca_init(); -#endif -#ifdef CONFIG_BT_HCIUART_AG6XX - ag6xx_init(); -#endif -#ifdef CONFIG_BT_HCIUART_MRVL - mrvl_init(); + +#ifdef BTCOEX + rtk_btcoex_init(); #endif return 0; @@ -889,48 +1417,36 @@ static int __init hci_uart_init(void) 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 -#ifdef CONFIG_BT_HCIUART_BCSP - bcsp_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_LL - ll_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_ATH3K - ath_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_3WIRE h5_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_INTEL - intel_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_BCM - bcm_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_QCA - qca_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_AG6XX - ag6xx_deinit(); -#endif -#ifdef CONFIG_BT_HCIUART_MRVL - mrvl_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 - /* Release tty registration of line discipline */ - err = tty_unregister_ldisc(N_HCI); - if (err) - BT_ERR("Can't unregister HCI line discipline (%d)", err); +#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); diff --git a/BSP/linux-kernel/drivers/bluetooth/hci_rtk_h5.c b/BSP/linux-kernel/drivers/bluetooth/hci_rtk_h5.c new file mode 100755 index 000000000..7fbce6689 --- /dev/null +++ b/BSP/linux-kernel/drivers/bluetooth/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/BSP/linux-kernel/drivers/bluetooth/hci_uart.h b/BSP/linux-kernel/drivers/bluetooth/hci_uart.h index 067a610f1..bdc2c33b0 100644 --- a/BSP/linux-kernel/drivers/bluetooth/hci_uart.h +++ b/BSP/linux-kernel/drivers/bluetooth/hci_uart.h @@ -22,11 +22,40 @@ * 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 */ + +#define RTKBT_POWERKEY_WAKEUP + /* Ioctls */ #define HCIUARTSETPROTO _IOW('U', 200, int) #define HCIUARTGETPROTO _IOR('U', 201, int) @@ -35,7 +64,7 @@ #define HCIUARTGETFLAGS _IOR('U', 204, int) /* UART protocols */ -#define HCI_UART_MAX_PROTO 12 +#define HCI_UART_MAX_PROTO 6 #define HCI_UART_H4 0 #define HCI_UART_BCSP 1 @@ -43,12 +72,6 @@ #define HCI_UART_H4DS 3 #define HCI_UART_LL 4 #define HCI_UART_ATH3K 5 -#define HCI_UART_INTEL 6 -#define HCI_UART_BCM 7 -#define HCI_UART_QCA 8 -#define HCI_UART_AG6XX 9 -#define HCI_UART_NOKIA 10 -#define HCI_UART_MRVL 11 #define HCI_UART_RAW_DEVICE 0 #define HCI_UART_RESET_ON_INIT 1 @@ -58,46 +81,43 @@ #define HCI_UART_VND_DETECT 5 struct hci_uart; -struct serdev_device; struct hci_uart_proto { unsigned int id; - const char *name; - unsigned int manufacturer; - unsigned int init_speed; - unsigned int oper_speed; int (*open)(struct hci_uart *hu); int (*close)(struct hci_uart *hu); int (*flush)(struct hci_uart *hu); - int (*setup)(struct hci_uart *hu); - int (*set_baudrate)(struct hci_uart *hu, unsigned int speed); - int (*recv)(struct hci_uart *hu, const void *data, int len); + 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 serdev_device *serdev; struct hci_dev *hdev; unsigned long flags; unsigned long hdev_flags; - struct work_struct init_ready; struct work_struct write_work; + struct workqueue_struct *hci_uart_wq; - const struct hci_uart_proto *proto; - struct percpu_rw_semaphore proto_lock; /* Stop work for proto close */ + 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; - unsigned int init_speed; - unsigned int oper_speed; - - u8 alignment; - u8 padding; +#if WOBT_NOTIFY + struct notifier_block pm_notify_block; + struct notifier_block shutdown_notifier; +#endif }; /* HCI_UART proto flag bits */ @@ -109,100 +129,20 @@ struct hci_uart { #define HCI_UART_SENDING 1 #define HCI_UART_TX_WAKEUP 2 -int hci_uart_register_proto(const struct hci_uart_proto *p); -int hci_uart_unregister_proto(const struct hci_uart_proto *p); -int hci_uart_register_device(struct hci_uart *hu, const struct hci_uart_proto *p); -void hci_uart_unregister_device(struct hci_uart *hu); - -int hci_uart_tx_wakeup(struct hci_uart *hu); -int hci_uart_init_ready(struct hci_uart *hu); -void hci_uart_init_work(struct work_struct *work); -void hci_uart_set_baudrate(struct hci_uart *hu, unsigned int speed); -bool hci_uart_has_flow_control(struct hci_uart *hu); -void hci_uart_set_flow_control(struct hci_uart *hu, bool enable); -void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed, - unsigned int oper_speed); +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 -int h4_init(void); -int h4_deinit(void); - -struct h4_recv_pkt { - u8 type; /* Packet type */ - u8 hlen; /* Header length */ - u8 loff; /* Data length offset in header */ - u8 lsize; /* Data length field size */ - u16 maxlen; /* Max overall packet length */ - int (*recv)(struct hci_dev *hdev, struct sk_buff *skb); -}; - -#define H4_RECV_ACL \ - .type = HCI_ACLDATA_PKT, \ - .hlen = HCI_ACL_HDR_SIZE, \ - .loff = 2, \ - .lsize = 2, \ - .maxlen = HCI_MAX_FRAME_SIZE \ - -#define H4_RECV_SCO \ - .type = HCI_SCODATA_PKT, \ - .hlen = HCI_SCO_HDR_SIZE, \ - .loff = 2, \ - .lsize = 1, \ - .maxlen = HCI_MAX_SCO_SIZE - -#define H4_RECV_EVENT \ - .type = HCI_EVENT_PKT, \ - .hlen = HCI_EVENT_HDR_SIZE, \ - .loff = 1, \ - .lsize = 1, \ - .maxlen = HCI_MAX_EVENT_SIZE - -struct sk_buff *h4_recv_buf(struct hci_dev *hdev, struct sk_buff *skb, - const unsigned char *buffer, int count, - const struct h4_recv_pkt *pkts, int pkts_count); +extern int h4_init(void); +extern int h4_deinit(void); #endif -#ifdef CONFIG_BT_HCIUART_BCSP -int bcsp_init(void); -int bcsp_deinit(void); -#endif +extern int h5_init(void); +extern int h5_deinit(void); -#ifdef CONFIG_BT_HCIUART_LL -int ll_init(void); -int ll_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_ATH3K -int ath_init(void); -int ath_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_3WIRE -int h5_init(void); -int h5_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_INTEL -int intel_init(void); -int intel_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_BCM -int bcm_init(void); -int bcm_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_QCA -int qca_init(void); -int qca_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_AG6XX -int ag6xx_init(void); -int ag6xx_deinit(void); -#endif - -#ifdef CONFIG_BT_HCIUART_MRVL -int mrvl_init(void); -int mrvl_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/BSP/linux-kernel/drivers/bluetooth/rtk_coex.c b/BSP/linux-kernel/drivers/bluetooth/rtk_coex.c new file mode 100755 index 000000000..61fb29ba8 --- /dev/null +++ b/BSP/linux-kernel/drivers/bluetooth/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(conn, profile) ((conn->profile_bitmap & BIT(profile)) > 0) +#define is_profile_busy(conn, profile) ((conn->profile_status & BIT(profile)) > 0) + +#ifdef RTB_SOFTWARE_MAILBOX +static void rtk_handle_event_from_wifi(uint8_t * msg); +#endif + +static void count_a2dp_packet_timeout(struct work_struct *work); +static void count_pan_packet_timeout(struct work_struct *work); +static void count_hogp_packet_timeout(struct work_struct *work); + +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 uint8_t psm_to_profile_index(uint16_t psm) +{ + switch (psm) { + case PSM_AVCTP: + case PSM_SDP: + return 0xFF; //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 handle, 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 ((handle & 0xfff) == (desc->handle & 0xfff) && + desc->psm == psm) + return desc; + } + + return NULL; +} + +static void rtk_check_setup_timer(rtk_conn_prof * phci_conn, uint8_t profile_index) +{ + int delay = msecs_to_jiffies(1000); + if (profile_index == profile_a2dp) { + phci_conn->a2dp_packet_count = 0; + queue_delayed_work(btrtl_coex.timer_wq, &phci_conn->a2dp_count_work, delay); + } + + if (profile_index == profile_pan) { + phci_conn->pan_packet_count = 0; + queue_delayed_work(btrtl_coex.timer_wq, &phci_conn->pan_count_work, delay); + } + + /* hogp & voice share one timer now */ + if ((profile_index == profile_hogp) || (profile_index == profile_voice)) { + if ((0 == phci_conn->profile_refcount[profile_hogp]) + && (0 == phci_conn->profile_refcount[profile_voice])) { + phci_conn->hogp_packet_count = 0; + phci_conn->voice_packet_count = 0; + queue_delayed_work(btrtl_coex.timer_wq, &phci_conn->hogp_count_work, delay); + } + } +} + +static void rtk_check_del_timer(uint8_t profile_index, rtk_conn_prof * phci_conn) +{ + RTKBT_DBG("%s: handle 0x%4x", __func__, phci_conn->handle); + if (profile_a2dp == profile_index) { + phci_conn->a2dp_packet_count = 0; + cancel_delayed_work_sync(&phci_conn->a2dp_count_work); + } + if (profile_pan == profile_index) { + phci_conn->pan_packet_count = 0; + cancel_delayed_work_sync(&phci_conn->pan_count_work); + } + if (profile_hogp == profile_index) { + phci_conn->hogp_packet_count = 0; + if (phci_conn->profile_refcount[profile_voice] == 0) { + cancel_delayed_work_sync(&phci_conn->hogp_count_work); + } + } + if (profile_voice == profile_index) { + phci_conn->voice_packet_count = 0; + if (phci_conn->profile_refcount[profile_hogp] == 0) { + cancel_delayed_work_sync(&phci_conn->hogp_count_work); + } + } +} + + + +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 & 0xFFF) == 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); + INIT_DELAYED_WORK(&desc->a2dp_count_work, (void *)count_a2dp_packet_timeout); + INIT_DELAYED_WORK(&desc->pan_count_work, (void *)count_pan_packet_timeout); + INIT_DELAYED_WORK(&desc->hogp_count_work, (void *)count_hogp_packet_timeout); +} + +static void delete_connection_from_hash(rtk_conn_prof * desc) +{ + if (desc) { + cancel_delayed_work_sync(&desc->a2dp_count_work); + cancel_delayed_work_sync(&desc->pan_count_work); + cancel_delayed_work_sync(&desc->hogp_count_work); + 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) { + cancel_delayed_work_sync(&desc->a2dp_count_work); + cancel_delayed_work_sync(&desc->pan_count_work); + cancel_delayed_work_sync(&desc->hogp_count_work); + 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, + uint8_t profile_index, uint16_t dcid, + uint16_t scid) +{ + rtk_prof_info *pprof_info = NULL; + + 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(handle, 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) +{ + if (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); + + 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; + + mutex_lock(&btrtl_coex.profile_mutex); + list_for_each_safe(iter, temp, head) { + desc = list_entry(iter, rtk_prof_info, list); + if (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); + + list_del(&desc->list); + kfree(desc); + desc = NULL; + } + } + //INIT_LIST_HEAD(head); + mutex_unlock(&btrtl_coex.profile_mutex); +} + +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 uint8_t profileinfo_cmd = 0; +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++; + } + + if(!profileinfo_cmd) { + buffer_size = 1 + handle_number * 3 + 1; + } else { + buffer_size = 1 + handle_number * 6; + } + + p_buf = kmalloc(buffer_size, GFP_ATOMIC); + + if (NULL == p_buf) { + RTKBT_ERR("%s: alloc error", __func__); + return; + } + p = p_buf; + *p++ = handle_number; + + RTKBT_DBG("%s: BufferSize %u", __func__, buffer_size); + 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) { + if(!profileinfo_cmd) { + UINT16_TO_STREAM(p, hci_conn->handle); + RTKBT_DBG("%s: handle 0x%04x", __func__, + hci_conn->handle); + *p++ = hci_conn->profile_bitmap; + btrtl_coex.profile_status |= hci_conn->profile_status; + } else { + UINT16_TO_STREAM(p, hci_conn->handle); + UINT16_TO_STREAM(p, hci_conn->profile_bitmap); + UINT16_TO_STREAM(p, hci_conn->profile_status); + RTKBT_DBG("%s: profile_status 0x%02x", __func__, + hci_conn->profile_status); + } + RTKBT_DBG("%s: profile_bitmap 0x%02x", __func__, + hci_conn->profile_bitmap); + handle_number--; + } + if (0 == handle_number) + break; + } + + if(!profileinfo_cmd) { + *p++ = btrtl_coex.profile_status; + btrtl_coex.profile_status = 0; + rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_PROFILE_REPORT_LEGACY_COMMAND, buffer_size, + p_buf); + } else { + rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_PROFILE_REPORT_COMMAND, buffer_size, + p_buf); + } + + kfree(p_buf); + return; +} + +static void update_profile_state(rtk_conn_prof * phci_conn, + uint8_t profile_index, uint8_t is_busy) +{ + uint8_t need_update = FALSE; + + RTKBT_DBG("%s: is_busy %d, profile_index %x", __func__, + is_busy, profile_index); + if ((phci_conn->profile_bitmap & BIT(profile_index)) == 0) { + RTKBT_ERR("%s: : ERROR!!! profile(Index: %x) does not exist", + __func__, profile_index); + return; + } + + + if (is_busy) { + if ((phci_conn->profile_status & BIT(profile_index)) == 0) { + need_update = TRUE; + phci_conn->profile_status |= BIT(profile_index); + } + } else { + if ((phci_conn->profile_status & BIT(profile_index)) > 0) { + need_update = TRUE; + phci_conn->profile_status &= ~(BIT(profile_index)); + } + } + + if (need_update) { + RTKBT_DBG("%s: phci_conn->profile_status 0x%02x", + __func__, phci_conn->profile_status); + rtk_notify_profileinfo_to_fw(); + } +} + +static void update_profile_connection(rtk_conn_prof * phci_conn, + uint8_t profile_index, uint8_t is_add) +{ + uint8_t need_update = FALSE; + + RTKBT_DBG("%s: is_add %d, profile_index %x", __func__, + is_add, profile_index); + + if (is_add) { + + if (0 == phci_conn->profile_refcount[profile_index]) { + need_update = TRUE; + phci_conn->profile_bitmap |= BIT(profile_index); + /* SCO is always busy */ + if (profile_index == profile_sco) + phci_conn->profile_status |= + BIT(profile_index); + + rtk_check_setup_timer(phci_conn, profile_index); + } + phci_conn->profile_refcount[profile_index]++; + } else { + if (!phci_conn->profile_refcount[profile_index]) { + RTKBT_WARN("profile %u refcount is already zero", + profile_index); + return; + } + + phci_conn->profile_refcount[profile_index]--; + + if (profile_index == profile_a2dp && + phci_conn->profile_refcount[profile_index] == 1) { + phci_conn->profile_status &= ~(BIT(profile_index)); + RTKBT_DBG("%s: clear a2dp status",__func__); + } + + if (0 == phci_conn->profile_refcount[profile_index]) { + need_update = TRUE; + phci_conn->profile_bitmap &= ~(BIT(profile_index)); + + phci_conn->profile_status &= ~(BIT(profile_index)); + rtk_check_del_timer(profile_index, phci_conn); + /* 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)); + } + } + } + + RTKBT_DBG("%s: phci_conn->profile_bitmap 0x%02x", __func__, + phci_conn->profile_bitmap); + + 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); + + phci_conn->profile_refcount[profile_hid_interval]++; + if (phci_conn-> + profile_refcount[profile_hid_interval] == 1) + phci_conn->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)); + + phci_conn->profile_refcount[profile_hid_interval]--; + if (phci_conn-> + profile_refcount[profile_hid_interval] == 0) + phci_conn->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; + uint8_t profile_index = psm_to_profile_index(psm); + + if (profile_index == 0xFF) { + RTKBT_DBG("PSM(0x%04x) do not need parse", psm); + return status; + } + + mutex_lock(&btrtl_coex.profile_mutex); + 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__); + mutex_unlock(&btrtl_coex.profile_mutex); + 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); + + mutex_unlock(&btrtl_coex.profile_mutex); + + 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; + + mutex_lock(&btrtl_coex.profile_mutex); + 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!!"); + mutex_unlock(&btrtl_coex.profile_mutex); + 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); + } else if (result != 0x0001) { + delete_profile_from_hash(prof_info); + } + + mutex_unlock(&btrtl_coex.profile_mutex); + 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); + + mutex_lock(&btrtl_coex.profile_mutex); + 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!"); + mutex_unlock(&btrtl_coex.profile_mutex); + return 0; + } + + phci_conn = find_connection_by_handle(&btrtl_coex, handle); + if (!phci_conn) { + mutex_unlock(&btrtl_coex.profile_mutex); + 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); + mutex_unlock(&btrtl_coex.profile_mutex); + + 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 }; + +static 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(hci_conn, profile_a2dp)) { + struct sbc_frame_hdr *sbc_header; + struct rtp_header *rtph; + u8 bitpool; + + update_profile_state(hci_conn, profile_a2dp, TRUE); + if (!direction) { + if (!(hci_conn->profile_bitmap & BIT(profile_sink))) { + hci_conn->profile_bitmap |= BIT(profile_sink); + update_profile_connection(hci_conn, profile_sink, 1); + } + update_profile_state(hci_conn, 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); + } + } + hci_conn->a2dp_packet_count++; + } + + if (prof_info->profile_index == profile_pan) + hci_conn->pan_packet_count++; + } +} + +static void count_a2dp_packet_timeout(struct work_struct *work) +{ + rtk_conn_prof *hci_conn = container_of(work, rtk_conn_prof, + a2dp_count_work.work); + if (hci_conn->a2dp_packet_count) + RTKBT_DBG("%s: a2dp_packet_count %d", __func__, + hci_conn->a2dp_packet_count); + if (hci_conn->a2dp_packet_count == 0) { + if (is_profile_busy(hci_conn, profile_a2dp)) { + RTKBT_DBG("%s: a2dp busy->idle!", __func__); + update_profile_state(hci_conn, profile_a2dp, FALSE); + if (hci_conn->profile_bitmap & BIT(profile_sink)) + update_profile_state(hci_conn, profile_sink, FALSE); + } + } + hci_conn->a2dp_packet_count = 0; + + queue_delayed_work(btrtl_coex.timer_wq, &hci_conn->a2dp_count_work, msecs_to_jiffies(1000)); +} + +static void count_pan_packet_timeout(struct work_struct *work) +{ + rtk_conn_prof *hci_conn = container_of(work, rtk_conn_prof, + pan_count_work.work); + if (hci_conn->pan_packet_count) + RTKBT_DBG("%s: pan_packet_count %d", __func__, + hci_conn->pan_packet_count); + if (hci_conn->pan_packet_count < PAN_PACKET_COUNT) { + if (is_profile_busy(hci_conn, profile_pan)) { + RTKBT_DBG("%s: pan busy->idle!", __func__); + update_profile_state(hci_conn, profile_pan, FALSE); + } + } else { + if (!is_profile_busy(hci_conn, profile_pan)) { + RTKBT_DBG("timeout_handler: pan idle->busy!"); + update_profile_state(hci_conn, profile_pan, TRUE); + } + } + hci_conn->pan_packet_count = 0; + queue_delayed_work(btrtl_coex.timer_wq, &hci_conn->pan_count_work, msecs_to_jiffies(1000)); +} + +static void count_hogp_packet_timeout(struct work_struct *work) +{ + rtk_conn_prof *hci_conn = container_of(work, rtk_conn_prof, + hogp_count_work.work); + if (hci_conn->hogp_packet_count) + RTKBT_DBG("%s: hogp_packet_count %d", __func__, + hci_conn->hogp_packet_count); + if (hci_conn->hogp_packet_count == 0) { + if (is_profile_busy(hci_conn, profile_hogp)) { + RTKBT_DBG("%s: hogp busy->idle!", __func__); + update_profile_state(hci_conn, profile_hogp, FALSE); + } + } + hci_conn->hogp_packet_count = 0; + + if (hci_conn->voice_packet_count) + RTKBT_DBG("%s: voice_packet_count %d", __func__, + hci_conn->voice_packet_count); + if (hci_conn->voice_packet_count == 0) { + if (is_profile_busy(hci_conn, profile_voice)) { + RTKBT_DBG("%s: voice busy->idle!", __func__); + update_profile_state(hci_conn, profile_voice, FALSE); + } + } + hci_conn->voice_packet_count = 0; + queue_delayed_work(btrtl_coex.timer_wq, &hci_conn->hogp_count_work, 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 + if (opcode == HCI_VENDOR_SET_PROFILE_REPORT_COMMAND) { + //0x01-unknown hci command + if((*p++) == 0x01) { + //RTKBT_DBG("unknown hci command"); + return; + } else { + profileinfo_cmd = 1; + } + } +} + +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; + hci_conn->profile_status = 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; + hci_conn->profile_status = 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 (!conn->profile_refcount[pfe_index]) { + RTKBT_WARN("profile %u ref is 0", pfe_index); + return 0; + } + + RTKBT_INFO("%s: profile_ref[%u] %u", __func__, pfe_index, + conn->profile_refcount[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)); + + /* if profile does not exist, status is meaningless */ + conn->profile_status &= ~(BIT(pfe_index)); + rtk_check_del_timer(pfe_index, conn); + } + + 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; + + mutex_lock(&coex->profile_mutex); + + 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(); + mutex_unlock(&coex->profile_mutex); +} + +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 profile_mutex */ + 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; + } +} + +static const char l2_dir_str[][4] = { + "RX", "TX", +}; + +static 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; + rtk_conn_prof *hci_conn = NULL; + + /* 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); + + hci_conn = + find_connection_by_handle(&btrtl_coex, handle); + if (NULL == hci_conn) + return; + + + 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 { + //RTKBT_DBG("%s: handle:%x, flag:%x, pan:%d, a2dp:%d", __func__, handle, flag, + // is_profile_connected(profile_a2dp), is_profile_connected(profile_pan)); + if ((flag != 0x01) && (is_profile_connected(hci_conn, profile_a2dp) || + is_profile_connected(hci_conn, 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 + case HCI_VENDOR_SET_PROFILE_REPORT_COMMAND: + 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; + } +} + +static 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); +} + +static void check_profileinfo_cmd(void) +{ + //1 + 6 * handle_bumfer, handle_number = 0 + uint8_t profileinfo_buf[] = {0x00}; + rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_PROFILE_REPORT_COMMAND, 1, + profileinfo_buf); +} + +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); + + 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 + check_profileinfo_cmd(); + /* 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) +{ + + 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 */ + + 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; + + rtl_free_frags(&btrtl_coex); + profileinfo_cmd = 0; + RTKBT_DBG("-x"); +} + +void rtk_btcoex_probe(struct hci_dev *hdev) +{ + btrtl_coex.hdev = hdev; + spin_lock_init(&btrtl_coex.spin_lock_sock); + mutex_init(&btrtl_coex.profile_mutex); +} + +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"); + btrtl_coex.timer_wq = create_workqueue("bttimerwork"); + 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); + flush_workqueue(btrtl_coex.timer_wq); + destroy_workqueue(btrtl_coex.timer_wq); + rtl_free_buff(&btrtl_coex); +} diff --git a/BSP/linux-kernel/drivers/bluetooth/rtk_coex.h b/BSP/linux-kernel/drivers/bluetooth/rtk_coex.h new file mode 100755 index 000000000..bb182bbf8 --- /dev/null +++ b/BSP/linux-kernel/drivers/bluetooth/rtk_coex.h @@ -0,0 +1,377 @@ +/* +* +* 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_LEGACY_COMMAND 0xfc19 +#define HCI_VENDOR_SET_PROFILE_REPORT_COMMAND 0xfc1B +#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 0x1005 +//--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; + struct delayed_work a2dp_count_work; + struct delayed_work pan_count_work; + struct delayed_work hogp_count_work; + uint32_t a2dp_packet_count; + uint32_t pan_packet_count; + uint32_t hogp_packet_count; + uint32_t voice_packet_count; + uint8_t type; // 0:l2cap, 1:sco/esco, 2:le + uint16_t profile_bitmap; + uint16_t profile_status; + 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 +#ifdef RTB_SOFTWARE_MAILBOX + struct workqueue_struct *sock_wq; + struct delayed_work sock_work; +#endif + struct workqueue_struct *fw_wq; + struct workqueue_struct *timer_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; + struct mutex profile_mutex; + uint16_t profile_bitmap; + uint16_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);