mirror of https://github.com/OpenIPC/firmware.git
				
				
				
			
		
			
				
	
	
		
			2631 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			Diff
		
	
	
			
		
		
	
	
			2631 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			Diff
		
	
	
| diff -drupN a/drivers/bluetooth/rtk_coex.c b/drivers/bluetooth/rtk_coex.c
 | |
| --- a/drivers/bluetooth/rtk_coex.c	1970-01-01 03:00:00.000000000 +0300
 | |
| +++ b/drivers/bluetooth/rtk_coex.c	2022-06-09 05:02:28.000000000 +0300
 | |
| @@ -0,0 +1,2626 @@
 | |
| +#include <linux/kernel.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/init.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/types.h>
 | |
| +#include <linux/sched.h>
 | |
| +#include <linux/errno.h>
 | |
| +#include <linux/skbuff.h>
 | |
| +#include <linux/dcache.h>
 | |
| +#include <linux/version.h>
 | |
| +#include <net/sock.h>
 | |
| +#include <net/bluetooth/bluetooth.h>
 | |
| +#include <net/bluetooth/hci_core.h>
 | |
| +#include <net/bluetooth/l2cap.h>
 | |
| +
 | |
| +#include "rtk_coex.h"
 | |
| +
 | |
| +#if BTRTL_HCI_IF == BTRTL_HCIUSB
 | |
| +#include <linux/usb.h>
 | |
| +#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;
 | |
| +
 | |
| +#define is_profile_connected(profile)   ((btrtl_coex.profile_bitmap & BIT(profile)) > 0)
 | |
| +#define is_profile_busy(profile)        ((btrtl_coex.profile_status & BIT(profile)) > 0)
 | |
| +
 | |
| +static void rtk_handle_event_from_wifi(uint8_t * msg);
 | |
| +static void count_a2dp_packet_timeout(unsigned long data);
 | |
| +static void count_pan_packet_timeout(unsigned long data);
 | |
| +static void count_hogp_packet_timeout(unsigned long data);
 | |
| +
 | |
| +static int rtl_alloc_buff(struct rtl_coex_struct *coex)
 | |
| +{
 | |
| +	struct rtl_hci_ev *ev;
 | |
| +	struct rtl_l2_buff *l2;
 | |
| +	int i;
 | |
| +	int order;
 | |
| +	unsigned long addr;
 | |
| +	unsigned long addr2;
 | |
| +	int ev_size;
 | |
| +	int l2_size;
 | |
| +	int n;
 | |
| +
 | |
| +	spin_lock_init(&coex->buff_lock);
 | |
| +
 | |
| +	INIT_LIST_HEAD(&coex->ev_used_list);
 | |
| +	INIT_LIST_HEAD(&coex->ev_free_list);
 | |
| +
 | |
| +	INIT_LIST_HEAD(&coex->l2_used_list);
 | |
| +	INIT_LIST_HEAD(&coex->l2_free_list);
 | |
| +
 | |
| +	n = NUM_RTL_HCI_EV * sizeof(struct rtl_hci_ev);
 | |
| +	ev_size = ALIGN(n, sizeof(unsigned long));
 | |
| +
 | |
| +	n = L2_MAX_PKTS * sizeof(struct rtl_l2_buff);
 | |
| +	l2_size = ALIGN(n, sizeof(unsigned long));
 | |
| +
 | |
| +	RTKBT_DBG("alloc buffers %d, %d for ev and l2", ev_size, l2_size);
 | |
| +
 | |
| +	order = get_order(ev_size + l2_size);
 | |
| +	addr = __get_free_pages(GFP_KERNEL, order);
 | |
| +	if (!addr) {
 | |
| +		RTKBT_ERR("failed to alloc buffers for ev and l2.");
 | |
| +		return -ENOMEM;
 | |
| +	}
 | |
| +	memset((void *)addr, 0, ev_size + l2_size);
 | |
| +
 | |
| +	coex->pages_addr = addr;
 | |
| +	coex->buff_size = ev_size + l2_size;
 | |
| +
 | |
| +	ev = (struct rtl_hci_ev *)addr;
 | |
| +	for (i = 0; i < NUM_RTL_HCI_EV; i++) {
 | |
| +		list_add_tail(&ev->list, &coex->ev_free_list);
 | |
| +		ev++;
 | |
| +	}
 | |
| +
 | |
| +	addr2 = addr + ev_size;
 | |
| +	l2 = (struct rtl_l2_buff *)addr2;
 | |
| +	for (i = 0; i < L2_MAX_PKTS; i++) {
 | |
| +		list_add_tail(&l2->list, &coex->l2_free_list);
 | |
| +		l2++;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void rtl_free_buff(struct rtl_coex_struct *coex)
 | |
| +{
 | |
| +	struct rtl_hci_ev *ev;
 | |
| +	struct rtl_l2_buff *l2;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +
 | |
| +	while (!list_empty(&coex->ev_used_list)) {
 | |
| +		ev = list_entry(coex->ev_used_list.next, struct rtl_hci_ev,
 | |
| +				list);
 | |
| +		list_del(&ev->list);
 | |
| +	}
 | |
| +
 | |
| +	while (!list_empty(&coex->ev_free_list)) {
 | |
| +		ev = list_entry(coex->ev_free_list.next, struct rtl_hci_ev,
 | |
| +				list);
 | |
| +		list_del(&ev->list);
 | |
| +	}
 | |
| +
 | |
| +	while (!list_empty(&coex->l2_used_list)) {
 | |
| +		l2 = list_entry(coex->l2_used_list.next, struct rtl_l2_buff,
 | |
| +				list);
 | |
| +		list_del(&l2->list);
 | |
| +	}
 | |
| +
 | |
| +	while (!list_empty(&coex->l2_free_list)) {
 | |
| +		l2 = list_entry(coex->l2_free_list.next, struct rtl_l2_buff,
 | |
| +				list);
 | |
| +		list_del(&l2->list);
 | |
| +	}
 | |
| +
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +
 | |
| +	if (coex->buff_size > 0) {
 | |
| +		free_pages(coex->pages_addr, get_order(coex->buff_size));
 | |
| +		coex->pages_addr = 0;
 | |
| +		coex->buff_size = 0;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static struct rtl_hci_ev *rtl_ev_node_get(struct rtl_coex_struct *coex)
 | |
| +{
 | |
| +	struct rtl_hci_ev *ev;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	if (!coex->buff_size)
 | |
| +		return NULL;
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +	if (!list_empty(&coex->ev_free_list)) {
 | |
| +		ev = list_entry(coex->ev_free_list.next, struct rtl_hci_ev,
 | |
| +				list);
 | |
| +		list_del(&ev->list);
 | |
| +	} else
 | |
| +		ev = NULL;
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +	return ev;
 | |
| +}
 | |
| +
 | |
| +static int rtl_ev_node_to_used(struct rtl_coex_struct *coex,
 | |
| +		struct rtl_hci_ev *ev)
 | |
| +{
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +	list_add_tail(&ev->list, &coex->ev_used_list);
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static struct rtl_l2_buff *rtl_l2_node_get(struct rtl_coex_struct *coex)
 | |
| +{
 | |
| +	struct rtl_l2_buff *l2;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	if (!coex->buff_size)
 | |
| +		return NULL;
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +
 | |
| +	if(!list_empty(&coex->l2_free_list)) {
 | |
| +		l2 = list_entry(coex->l2_free_list.next, struct rtl_l2_buff,
 | |
| +				list);
 | |
| +		list_del(&l2->list);
 | |
| +	} else
 | |
| +		l2 = NULL;
 | |
| +
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +	return l2;
 | |
| +}
 | |
| +
 | |
| +static int rtl_l2_node_to_used(struct rtl_coex_struct *coex,
 | |
| +		struct rtl_l2_buff *l2)
 | |
| +{
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +	list_add_tail(&l2->list, &coex->l2_used_list);
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int8_t psm_to_profile_index(uint16_t psm)
 | |
| +{
 | |
| +	switch (psm) {
 | |
| +	case PSM_AVCTP:
 | |
| +	case PSM_SDP:
 | |
| +		return -1;	//ignore
 | |
| +
 | |
| +	case PSM_HID:
 | |
| +	case PSM_HID_INT:
 | |
| +		return profile_hid;
 | |
| +
 | |
| +	case PSM_AVDTP:
 | |
| +		return profile_a2dp;
 | |
| +
 | |
| +	case PSM_PAN:
 | |
| +	case PSM_OPP:
 | |
| +	case PSM_FTP:
 | |
| +	case PSM_BIP:
 | |
| +	case PSM_RFCOMM:
 | |
| +		return profile_pan;
 | |
| +
 | |
| +	default:
 | |
| +		return profile_pan;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static rtk_conn_prof *find_connection_by_handle(struct rtl_coex_struct * coex,
 | |
| +						uint16_t handle)
 | |
| +{
 | |
| +	struct list_head *head = &coex->conn_hash;
 | |
| +	struct list_head *iter = NULL, *temp = NULL;
 | |
| +	rtk_conn_prof *desc = NULL;
 | |
| +
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		desc = list_entry(iter, rtk_conn_prof, list);
 | |
| +		if ((handle & 0xEFF) == desc->handle) {
 | |
| +			return desc;
 | |
| +		}
 | |
| +	}
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +static rtk_conn_prof *allocate_connection_by_handle(uint16_t handle)
 | |
| +{
 | |
| +	rtk_conn_prof *phci_conn = NULL;
 | |
| +	phci_conn = kmalloc(sizeof(rtk_conn_prof), GFP_ATOMIC);
 | |
| +	if (phci_conn)
 | |
| +		phci_conn->handle = handle;
 | |
| +
 | |
| +	return phci_conn;
 | |
| +}
 | |
| +
 | |
| +static void init_connection_hash(struct rtl_coex_struct * coex)
 | |
| +{
 | |
| +	struct list_head *head = &coex->conn_hash;
 | |
| +	INIT_LIST_HEAD(head);
 | |
| +}
 | |
| +
 | |
| +static void add_connection_to_hash(struct rtl_coex_struct * coex,
 | |
| +				   rtk_conn_prof * desc)
 | |
| +{
 | |
| +	struct list_head *head = &coex->conn_hash;
 | |
| +	list_add_tail(&desc->list, head);
 | |
| +}
 | |
| +
 | |
| +static void delete_connection_from_hash(rtk_conn_prof * desc)
 | |
| +{
 | |
| +	if (desc) {
 | |
| +		list_del(&desc->list);
 | |
| +		kfree(desc);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void flush_connection_hash(struct rtl_coex_struct * coex)
 | |
| +{
 | |
| +	struct list_head *head = &coex->conn_hash;
 | |
| +	struct list_head *iter = NULL, *temp = NULL;
 | |
| +	rtk_conn_prof *desc = NULL;
 | |
| +
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		desc = list_entry(iter, rtk_conn_prof, list);
 | |
| +		if (desc) {
 | |
| +			list_del(&desc->list);
 | |
| +			kfree(desc);
 | |
| +		}
 | |
| +	}
 | |
| +	//INIT_LIST_HEAD(head);
 | |
| +}
 | |
| +
 | |
| +static void init_profile_hash(struct rtl_coex_struct * coex)
 | |
| +{
 | |
| +	struct list_head *head = &coex->profile_list;
 | |
| +	INIT_LIST_HEAD(head);
 | |
| +}
 | |
| +
 | |
| +static uint8_t list_allocate_add(uint16_t handle, uint16_t psm,
 | |
| +				 int8_t profile_index, uint16_t dcid,
 | |
| +				 uint16_t scid)
 | |
| +{
 | |
| +	rtk_prof_info *pprof_info = NULL;
 | |
| +
 | |
| +	if (profile_index < 0) {
 | |
| +		RTKBT_ERR("PSM(0x%x) do not need parse", psm);
 | |
| +		return FALSE;
 | |
| +	}
 | |
| +
 | |
| +	pprof_info = kmalloc(sizeof(rtk_prof_info), GFP_ATOMIC);
 | |
| +
 | |
| +	if (NULL == pprof_info) {
 | |
| +		RTKBT_ERR("list_allocate_add: allocate error");
 | |
| +		return FALSE;
 | |
| +	}
 | |
| +
 | |
| +	pprof_info->handle = handle;
 | |
| +	pprof_info->psm = psm;
 | |
| +	pprof_info->scid = scid;
 | |
| +	pprof_info->dcid = dcid;
 | |
| +	pprof_info->profile_index = profile_index;
 | |
| +	list_add_tail(&(pprof_info->list), &(btrtl_coex.profile_list));
 | |
| +
 | |
| +	return TRUE;
 | |
| +}
 | |
| +
 | |
| +static void delete_profile_from_hash(rtk_prof_info * desc)
 | |
| +{
 | |
| +	RTKBT_DBG("Delete profile: hndl 0x%04x, psm 0x%04x, dcid 0x%04x, "
 | |
| +		  "scid 0x%04x", desc->handle, desc->psm, desc->dcid,
 | |
| +		  desc->scid);
 | |
| +	if (desc) {
 | |
| +		list_del(&desc->list);
 | |
| +		kfree(desc);
 | |
| +		desc = NULL;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void flush_profile_hash(struct rtl_coex_struct * coex)
 | |
| +{
 | |
| +	struct list_head *head = &coex->profile_list;
 | |
| +	struct list_head *iter = NULL, *temp = NULL;
 | |
| +	rtk_prof_info *desc = NULL;
 | |
| +
 | |
| +	spin_lock(&btrtl_coex.spin_lock_profile);
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		desc = list_entry(iter, rtk_prof_info, list);
 | |
| +		delete_profile_from_hash(desc);
 | |
| +	}
 | |
| +	//INIT_LIST_HEAD(head);
 | |
| +	spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +}
 | |
| +
 | |
| +static rtk_prof_info *find_profile_by_handle_scid(struct rtl_coex_struct *
 | |
| +						  coex, uint16_t handle,
 | |
| +						  uint16_t scid)
 | |
| +{
 | |
| +	struct list_head *head = &coex->profile_list;
 | |
| +	struct list_head *iter = NULL, *temp = NULL;
 | |
| +	rtk_prof_info *desc = NULL;
 | |
| +
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		desc = list_entry(iter, rtk_prof_info, list);
 | |
| +		if (((handle & 0xFFF) == desc->handle) && (scid == desc->scid)) {
 | |
| +			return desc;
 | |
| +		}
 | |
| +	}
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +static rtk_prof_info *find_profile_by_handle_dcid(struct rtl_coex_struct *
 | |
| +						  coex, uint16_t handle,
 | |
| +						  uint16_t dcid)
 | |
| +{
 | |
| +	struct list_head *head = &coex->profile_list;
 | |
| +	struct list_head *iter = NULL, *temp = NULL;
 | |
| +	rtk_prof_info *desc = NULL;
 | |
| +
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		desc = list_entry(iter, rtk_prof_info, list);
 | |
| +		if (((handle & 0xFFF) == desc->handle) && (dcid == desc->dcid)) {
 | |
| +			return desc;
 | |
| +		}
 | |
| +	}
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +static rtk_prof_info *find_profile_by_handle_dcid_scid(struct rtl_coex_struct
 | |
| +						       * coex, uint16_t handle,
 | |
| +						       uint16_t dcid,
 | |
| +						       uint16_t scid)
 | |
| +{
 | |
| +	struct list_head *head = &coex->profile_list;
 | |
| +	struct list_head *iter = NULL, *temp = NULL;
 | |
| +	rtk_prof_info *desc = NULL;
 | |
| +
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		desc = list_entry(iter, rtk_prof_info, list);
 | |
| +		if (((handle & 0xFFF) == desc->handle) && (dcid == desc->dcid)
 | |
| +		    && (scid == desc->scid)) {
 | |
| +			return desc;
 | |
| +		}
 | |
| +	}
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +static void rtk_vendor_cmd_to_fw(uint16_t opcode, uint8_t parameter_len,
 | |
| +				 uint8_t * parameter)
 | |
| +{
 | |
| +	int len = HCI_CMD_PREAMBLE_SIZE + parameter_len;
 | |
| +	uint8_t *p;
 | |
| +	struct sk_buff *skb;
 | |
| +	struct hci_dev *hdev = btrtl_coex.hdev;
 | |
| +
 | |
| +	skb = bt_skb_alloc(len, GFP_ATOMIC);
 | |
| +	if (!skb) {
 | |
| +		RTKBT_DBG("there is no room for cmd 0x%x", opcode);
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	p = (uint8_t *) skb_put(skb, HCI_CMD_PREAMBLE_SIZE);
 | |
| +	UINT16_TO_STREAM(p, opcode);
 | |
| +	*p++ = parameter_len;
 | |
| +
 | |
| +	if (parameter_len)
 | |
| +		memcpy(skb_put(skb, parameter_len), parameter, parameter_len);
 | |
| +
 | |
| +	bt_cb(skb)->pkt_type = HCI_COMMAND_PKT;
 | |
| +
 | |
| +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
 | |
| +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
 | |
| +	bt_cb(skb)->opcode = opcode;
 | |
| +#else
 | |
| +	bt_cb(skb)->hci.opcode = opcode;
 | |
| +#endif
 | |
| +#endif
 | |
| +
 | |
| +	/* Stand-alone HCI commands must be flagged as
 | |
| +	 * single-command requests.
 | |
| +	 */
 | |
| +#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
 | |
| +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
 | |
| +	bt_cb(skb)->req.start = true;
 | |
| +#else
 | |
| +
 | |
| +#if HCI_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
 | |
| +	bt_cb(skb)->hci.req_start = true;
 | |
| +#else
 | |
| +
 | |
| +	bt_cb(skb)->hci.req_flags |= HCI_REQ_START;
 | |
| +#endif
 | |
| +
 | |
| +#endif /* 4.4.0 */
 | |
| +#endif /* 3.10.0 */
 | |
| +	RTKBT_DBG("%s: opcode 0x%x", __func__, opcode);
 | |
| +
 | |
| +	/* It is harmless if set skb->dev twice. The dev will be used in
 | |
| +	 * btusb_send_frame() after or equal to kernel/hci 3.13.0,
 | |
| +	 * the hdev will not come from skb->dev. */
 | |
| +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
 | |
| +	skb->dev = (void *)btrtl_coex.hdev;
 | |
| +#endif
 | |
| +	/* Put the skb to the global hdev->cmd_q */
 | |
| +	skb_queue_tail(&hdev->cmd_q, skb);
 | |
| +
 | |
| +#if HCI_VERSION_CODE < KERNEL_VERSION(3, 3, 0)
 | |
| +	tasklet_schedule(&hdev->cmd_task);
 | |
| +#else
 | |
| +	queue_work(hdev->workqueue, &hdev->cmd_work);
 | |
| +#endif
 | |
| +
 | |
| +	return;
 | |
| +}
 | |
| +
 | |
| +static void rtk_notify_profileinfo_to_fw(void)
 | |
| +{
 | |
| +	struct list_head *head = NULL;
 | |
| +	struct list_head *iter = NULL;
 | |
| +	struct list_head *temp = NULL;
 | |
| +	rtk_conn_prof *hci_conn = NULL;
 | |
| +	uint8_t handle_number = 0;
 | |
| +	uint32_t buffer_size = 0;
 | |
| +	uint8_t *p_buf = NULL;
 | |
| +	uint8_t *p = NULL;
 | |
| +
 | |
| +	head = &btrtl_coex.conn_hash;
 | |
| +	list_for_each_safe(iter, temp, head) {
 | |
| +		hci_conn = list_entry(iter, rtk_conn_prof, list);
 | |
| +		if (hci_conn && hci_conn->profile_bitmap)
 | |
| +			handle_number++;
 | |
| +	}
 | |
| +
 | |
| +	buffer_size = 1 + handle_number * 3 + 1;
 | |
| +
 | |
| +	p_buf = kmalloc(buffer_size, GFP_ATOMIC);
 | |
| +
 | |
| +	if (NULL == p_buf) {
 | |
| +		RTKBT_ERR("%s: alloc error", __func__);
 | |
| +		return;
 | |
| +	}
 | |
| +	p = p_buf;
 | |
| +
 | |
| +	RTKBT_DBG("%s: BufferSize %u", __func__, buffer_size);
 | |
| +	*p++ = handle_number;
 | |
| +	RTKBT_DBG("%s: NumberOfHandles %u", __func__, handle_number);
 | |
| +	head = &btrtl_coex.conn_hash;
 | |
| +	list_for_each(iter, head) {
 | |
| +		hci_conn = list_entry(iter, rtk_conn_prof, list);
 | |
| +		if (hci_conn && hci_conn->profile_bitmap) {
 | |
| +			UINT16_TO_STREAM(p, hci_conn->handle);
 | |
| +			RTKBT_DBG("%s: handle 0x%04x", __func__,
 | |
| +					hci_conn->handle);
 | |
| +			*p++ = hci_conn->profile_bitmap;
 | |
| +			RTKBT_DBG("%s: profile_bitmap 0x%02x", __func__,
 | |
| +					hci_conn->profile_bitmap);
 | |
| +			handle_number--;
 | |
| +		}
 | |
| +		if (0 == handle_number)
 | |
| +			break;
 | |
| +	}
 | |
| +
 | |
| +	*p++ = btrtl_coex.profile_status;
 | |
| +	RTKBT_DBG("%s: profile_status 0x%02x", __func__,
 | |
| +			btrtl_coex.profile_status);
 | |
| +
 | |
| +	rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_PROFILE_REPORT_COMMAND, buffer_size,
 | |
| +			     p_buf);
 | |
| +
 | |
| +	kfree(p_buf);
 | |
| +	return;
 | |
| +}
 | |
| +
 | |
| +static void rtk_check_setup_timer(int8_t profile_index)
 | |
| +{
 | |
| +	if (profile_index == profile_a2dp) {
 | |
| +		btrtl_coex.a2dp_packet_count = 0;
 | |
| +		setup_timer(&(btrtl_coex.a2dp_count_timer),
 | |
| +			    count_a2dp_packet_timeout, 0);
 | |
| +		btrtl_coex.a2dp_count_timer.expires =
 | |
| +		    jiffies + msecs_to_jiffies(1000);
 | |
| +		add_timer(&(btrtl_coex.a2dp_count_timer));
 | |
| +	}
 | |
| +
 | |
| +	if (profile_index == profile_pan) {
 | |
| +		btrtl_coex.pan_packet_count = 0;
 | |
| +		setup_timer(&(btrtl_coex.pan_count_timer),
 | |
| +			    count_pan_packet_timeout, 0);
 | |
| +		btrtl_coex.pan_count_timer.expires =
 | |
| +		    jiffies + msecs_to_jiffies(1000);
 | |
| +		add_timer(&(btrtl_coex.pan_count_timer));
 | |
| +	}
 | |
| +
 | |
| +	/* hogp & voice share one timer now */
 | |
| +	if ((profile_index == profile_hogp) || (profile_index == profile_voice)) {
 | |
| +		if ((0 == btrtl_coex.profile_refcount[profile_hogp])
 | |
| +		    && (0 == btrtl_coex.profile_refcount[profile_voice])) {
 | |
| +			btrtl_coex.hogp_packet_count = 0;
 | |
| +			btrtl_coex.voice_packet_count = 0;
 | |
| +			setup_timer(&(btrtl_coex.hogp_count_timer),
 | |
| +				    count_hogp_packet_timeout, 0);
 | |
| +			btrtl_coex.hogp_count_timer.expires =
 | |
| +			    jiffies + msecs_to_jiffies(1000);
 | |
| +			add_timer(&(btrtl_coex.hogp_count_timer));
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_check_del_timer(int8_t profile_index)
 | |
| +{
 | |
| +	if (profile_a2dp == profile_index) {
 | |
| +		btrtl_coex.a2dp_packet_count = 0;
 | |
| +		del_timer(&(btrtl_coex.a2dp_count_timer));
 | |
| +	}
 | |
| +	if (profile_pan == profile_index) {
 | |
| +		btrtl_coex.pan_packet_count = 0;
 | |
| +		del_timer(&(btrtl_coex.pan_count_timer));
 | |
| +	}
 | |
| +	if (profile_hogp == profile_index) {
 | |
| +		btrtl_coex.hogp_packet_count = 0;
 | |
| +		if (btrtl_coex.profile_refcount[profile_voice] == 0) {
 | |
| +			del_timer(&(btrtl_coex.hogp_count_timer));
 | |
| +		}
 | |
| +	}
 | |
| +	if (profile_voice == profile_index) {
 | |
| +		btrtl_coex.voice_packet_count = 0;
 | |
| +		if (btrtl_coex.profile_refcount[profile_hogp] == 0) {
 | |
| +			del_timer(&(btrtl_coex.hogp_count_timer));
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void update_profile_state(uint8_t profile_index, uint8_t is_busy)
 | |
| +{
 | |
| +	uint8_t need_update = FALSE;
 | |
| +
 | |
| +	if ((btrtl_coex.profile_bitmap & BIT(profile_index)) == 0) {
 | |
| +		RTKBT_ERR("%s: : ERROR!!! profile(Index: %x) does not exist",
 | |
| +				__func__, profile_index);
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	if (is_busy) {
 | |
| +		if ((btrtl_coex.profile_status & BIT(profile_index)) == 0) {
 | |
| +			need_update = TRUE;
 | |
| +			btrtl_coex.profile_status |= BIT(profile_index);
 | |
| +		}
 | |
| +	} else {
 | |
| +		if ((btrtl_coex.profile_status & BIT(profile_index)) > 0) {
 | |
| +			need_update = TRUE;
 | |
| +			btrtl_coex.profile_status &= ~(BIT(profile_index));
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (need_update) {
 | |
| +		RTKBT_DBG("%s: btrtl_coex.profie_bitmap = %x",
 | |
| +				__func__, btrtl_coex.profile_bitmap);
 | |
| +		RTKBT_DBG("%s: btrtl_coex.profile_status = %x",
 | |
| +				__func__, btrtl_coex.profile_status);
 | |
| +		rtk_notify_profileinfo_to_fw();
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void update_profile_connection(rtk_conn_prof * phci_conn,
 | |
| +				      int8_t profile_index, uint8_t is_add)
 | |
| +{
 | |
| +	uint8_t need_update = FALSE;
 | |
| +	uint8_t kk;
 | |
| +
 | |
| +	RTKBT_DBG("%s: is_add %d, profile_index %x", __func__,
 | |
| +			is_add, profile_index);
 | |
| +	if (profile_index < 0)
 | |
| +		return;
 | |
| +
 | |
| +	if (is_add) {
 | |
| +		if (btrtl_coex.profile_refcount[profile_index] == 0) {
 | |
| +			need_update = TRUE;
 | |
| +			btrtl_coex.profile_bitmap |= BIT(profile_index);
 | |
| +
 | |
| +			/* SCO is always busy */
 | |
| +			if (profile_index == profile_sco)
 | |
| +				btrtl_coex.profile_status |=
 | |
| +				    BIT(profile_index);
 | |
| +
 | |
| +			rtk_check_setup_timer(profile_index);
 | |
| +		}
 | |
| +		btrtl_coex.profile_refcount[profile_index]++;
 | |
| +
 | |
| +		if (0 == phci_conn->profile_refcount[profile_index]) {
 | |
| +			need_update = TRUE;
 | |
| +			phci_conn->profile_bitmap |= BIT(profile_index);
 | |
| +		}
 | |
| +		phci_conn->profile_refcount[profile_index]++;
 | |
| +	} else {
 | |
| +		btrtl_coex.profile_refcount[profile_index]--;
 | |
| +		RTKBT_DBG("%s: btrtl_coex.profile_refcount[%x] = %x",
 | |
| +				__func__, profile_index,
 | |
| +				btrtl_coex.profile_refcount[profile_index]);
 | |
| +		if (btrtl_coex.profile_refcount[profile_index] == 0) {
 | |
| +			need_update = TRUE;
 | |
| +			btrtl_coex.profile_bitmap &= ~(BIT(profile_index));
 | |
| +
 | |
| +			/* if profile does not exist, status is meaningless */
 | |
| +			btrtl_coex.profile_status &= ~(BIT(profile_index));
 | |
| +			rtk_check_del_timer(profile_index);
 | |
| +		}
 | |
| +
 | |
| +		phci_conn->profile_refcount[profile_index]--;
 | |
| +		if (0 == phci_conn->profile_refcount[profile_index]) {
 | |
| +			need_update = TRUE;
 | |
| +			phci_conn->profile_bitmap &= ~(BIT(profile_index));
 | |
| +
 | |
| +			/* clear profile_hid_interval if need */
 | |
| +			if ((profile_hid == profile_index)
 | |
| +			    && (phci_conn->
 | |
| +				profile_bitmap & (BIT(profile_hid_interval)))) {
 | |
| +				phci_conn->profile_bitmap &=
 | |
| +				    ~(BIT(profile_hid_interval));
 | |
| +				btrtl_coex.
 | |
| +				    profile_refcount[profile_hid_interval]--;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	RTKBT_DBG("%s: btrtl_coex.profile_bitmap 0x%02x", __func__,
 | |
| +			btrtl_coex.profile_bitmap);
 | |
| +	for (kk = 0; kk < 8; kk++)
 | |
| +		RTKBT_DBG("%s: btrtl_coex.profile_refcount[%d] = %d",
 | |
| +				__func__, kk,
 | |
| +				btrtl_coex.profile_refcount[kk]);
 | |
| +
 | |
| +	if (need_update)
 | |
| +		rtk_notify_profileinfo_to_fw();
 | |
| +}
 | |
| +
 | |
| +static void update_hid_active_state(uint16_t handle, uint16_t interval)
 | |
| +{
 | |
| +	uint8_t need_update = 0;
 | |
| +	rtk_conn_prof *phci_conn =
 | |
| +	    find_connection_by_handle(&btrtl_coex, handle);
 | |
| +
 | |
| +	if (phci_conn == NULL)
 | |
| +		return;
 | |
| +
 | |
| +	RTKBT_DBG("%s: handle 0x%04x, interval %u", __func__, handle, interval);
 | |
| +	if (((phci_conn->profile_bitmap) & (BIT(profile_hid))) == 0) {
 | |
| +		RTKBT_DBG("HID not connected, nothing to be down");
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	if (interval < 60) {
 | |
| +		if ((phci_conn->profile_bitmap & (BIT(profile_hid_interval))) ==
 | |
| +		    0) {
 | |
| +			need_update = 1;
 | |
| +			phci_conn->profile_bitmap |= BIT(profile_hid_interval);
 | |
| +
 | |
| +			btrtl_coex.profile_refcount[profile_hid_interval]++;
 | |
| +			if (btrtl_coex.
 | |
| +			    profile_refcount[profile_hid_interval] == 1)
 | |
| +				btrtl_coex.profile_status |=
 | |
| +				    BIT(profile_hid);
 | |
| +		}
 | |
| +	} else {
 | |
| +		if ((phci_conn->profile_bitmap & (BIT(profile_hid_interval)))) {
 | |
| +			need_update = 1;
 | |
| +			phci_conn->profile_bitmap &=
 | |
| +			    ~(BIT(profile_hid_interval));
 | |
| +
 | |
| +			btrtl_coex.profile_refcount[profile_hid_interval]--;
 | |
| +			if (btrtl_coex.
 | |
| +			    profile_refcount[profile_hid_interval] == 0)
 | |
| +				btrtl_coex.profile_status &=
 | |
| +				    ~(BIT(profile_hid));
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (need_update)
 | |
| +		rtk_notify_profileinfo_to_fw();
 | |
| +}
 | |
| +
 | |
| +static uint8_t handle_l2cap_con_req(uint16_t handle, uint16_t psm,
 | |
| +				    uint16_t scid, uint8_t direction)
 | |
| +{
 | |
| +	uint8_t status = FALSE;
 | |
| +	rtk_prof_info *prof_info = NULL;
 | |
| +	int8_t profile_index = psm_to_profile_index(psm);
 | |
| +
 | |
| +	if (profile_index < 0) {
 | |
| +		RTKBT_DBG("PSM(0x%04x) do not need parse", psm);
 | |
| +		return status;
 | |
| +	}
 | |
| +
 | |
| +	spin_lock(&btrtl_coex.spin_lock_profile);
 | |
| +	if (direction)		//1: out
 | |
| +		prof_info =
 | |
| +		    find_profile_by_handle_scid(&btrtl_coex, handle, scid);
 | |
| +	else			// 0:in
 | |
| +		prof_info =
 | |
| +		    find_profile_by_handle_dcid(&btrtl_coex, handle, scid);
 | |
| +
 | |
| +	if (prof_info) {
 | |
| +		RTKBT_DBG("%s: this profile is already exist!", __func__);
 | |
| +		spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +		return status;
 | |
| +	}
 | |
| +
 | |
| +	if (direction)		//1: out
 | |
| +		status = list_allocate_add(handle, psm, profile_index, 0, scid);
 | |
| +	else			// 0:in
 | |
| +		status = list_allocate_add(handle, psm, profile_index, scid, 0);
 | |
| +
 | |
| +	spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +
 | |
| +	if (!status)
 | |
| +		RTKBT_ERR("%s: list_allocate_add failed!", __func__);
 | |
| +
 | |
| +	return status;
 | |
| +}
 | |
| +
 | |
| +static uint8_t handle_l2cap_con_rsp(uint16_t handle, uint16_t dcid,
 | |
| +				    uint16_t scid, uint8_t direction,
 | |
| +				    uint8_t result)
 | |
| +{
 | |
| +	rtk_prof_info *prof_info = NULL;
 | |
| +	rtk_conn_prof *phci_conn = NULL;
 | |
| +
 | |
| +	spin_lock(&btrtl_coex.spin_lock_profile);
 | |
| +	if (!direction)		//0, in
 | |
| +		prof_info =
 | |
| +		    find_profile_by_handle_scid(&btrtl_coex, handle, scid);
 | |
| +	else			//1, out
 | |
| +		prof_info =
 | |
| +		    find_profile_by_handle_dcid(&btrtl_coex, handle, scid);
 | |
| +
 | |
| +	if (!prof_info) {
 | |
| +		//RTKBT_DBG("handle_l2cap_con_rsp: prof_info Not Find!!");
 | |
| +		spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +		return FALSE;
 | |
| +	}
 | |
| +
 | |
| +	if (!result) {		//success
 | |
| +		RTKBT_DBG("l2cap connection success, update connection");
 | |
| +		if (!direction)	//0, in
 | |
| +			prof_info->dcid = dcid;
 | |
| +		else		//1, out
 | |
| +			prof_info->scid = dcid;
 | |
| +
 | |
| +		phci_conn = find_connection_by_handle(&btrtl_coex, handle);
 | |
| +		if (phci_conn)
 | |
| +			update_profile_connection(phci_conn,
 | |
| +						  prof_info->profile_index,
 | |
| +						  TRUE);
 | |
| +	}
 | |
| +
 | |
| +	spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +	return TRUE;
 | |
| +}
 | |
| +
 | |
| +static uint8_t handle_l2cap_discon_req(uint16_t handle, uint16_t dcid,
 | |
| +				       uint16_t scid, uint8_t direction)
 | |
| +{
 | |
| +	rtk_prof_info *prof_info = NULL;
 | |
| +	rtk_conn_prof *phci_conn = NULL;
 | |
| +	RTKBT_DBG("%s: handle 0x%04x, dcid 0x%04x, scid 0x%04x, dir %u",
 | |
| +			__func__, handle, dcid, scid, direction);
 | |
| +
 | |
| +	spin_lock(&btrtl_coex.spin_lock_profile);
 | |
| +	if (!direction)		//0: in
 | |
| +		prof_info =
 | |
| +		    find_profile_by_handle_dcid_scid(&btrtl_coex, handle,
 | |
| +						     scid, dcid);
 | |
| +	else			//1: out
 | |
| +		prof_info =
 | |
| +		    find_profile_by_handle_dcid_scid(&btrtl_coex, handle,
 | |
| +						     dcid, scid);
 | |
| +
 | |
| +	if (!prof_info) {
 | |
| +		//LogMsg("handle_l2cap_discon_req: prof_info Not Find!");
 | |
| +		spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	phci_conn = find_connection_by_handle(&btrtl_coex, handle);
 | |
| +	if (!phci_conn) {
 | |
| +		spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	update_profile_connection(phci_conn, prof_info->profile_index, FALSE);
 | |
| +	delete_profile_from_hash(prof_info);
 | |
| +	spin_unlock(&btrtl_coex.spin_lock_profile);
 | |
| +
 | |
| +	return 1;
 | |
| +}
 | |
| +
 | |
| +static const char sample_freqs[4][8] = {
 | |
| +	"16", "32", "44.1", "48"
 | |
| +};
 | |
| +
 | |
| +static const uint8_t sbc_blocks[4] = { 4, 8, 12, 16 };
 | |
| +
 | |
| +static const char chan_modes[4][16] = {
 | |
| +	"MONO", "DUAL_CHANNEL", "STEREO", "JOINT_STEREO"
 | |
| +};
 | |
| +
 | |
| +static const char alloc_methods[2][12] = {
 | |
| +	"LOUDNESS", "SNR"
 | |
| +};
 | |
| +
 | |
| +static const uint8_t subbands[2] = { 4, 8 };
 | |
| +
 | |
| +void print_sbc_header(struct sbc_frame_hdr *hdr)
 | |
| +{
 | |
| +	RTKBT_DBG("syncword: %02x", hdr->syncword);
 | |
| +	RTKBT_DBG("freq %skHz", sample_freqs[hdr->sampling_frequency]);
 | |
| +	RTKBT_DBG("blocks %u", sbc_blocks[hdr->blocks]);
 | |
| +	RTKBT_DBG("channel mode %s", chan_modes[hdr->channel_mode]);
 | |
| +	RTKBT_DBG("allocation method %s",
 | |
| +		  alloc_methods[hdr->allocation_method]);
 | |
| +	RTKBT_DBG("subbands %u", subbands[hdr->subbands]);
 | |
| +}
 | |
| +
 | |
| +static void packets_count(uint16_t handle, uint16_t scid, uint16_t length,
 | |
| +			  uint8_t direction, u8 *user_data)
 | |
| +{
 | |
| +	rtk_prof_info *prof_info = NULL;
 | |
| +
 | |
| +	rtk_conn_prof *hci_conn =
 | |
| +	    find_connection_by_handle(&btrtl_coex, handle);
 | |
| +	if (NULL == hci_conn)
 | |
| +		return;
 | |
| +
 | |
| +	if (0 == hci_conn->type) {
 | |
| +		if (!direction)	//0: in
 | |
| +			prof_info =
 | |
| +			    find_profile_by_handle_scid(&btrtl_coex, handle,
 | |
| +							scid);
 | |
| +		else		//1: out
 | |
| +			prof_info =
 | |
| +			    find_profile_by_handle_dcid(&btrtl_coex, handle,
 | |
| +							scid);
 | |
| +
 | |
| +		if (!prof_info) {
 | |
| +			//RTKBT_DBG("packets_count: prof_info Not Find!");
 | |
| +			return;
 | |
| +		}
 | |
| +
 | |
| +		if ((prof_info->profile_index == profile_a2dp) && (length > 100)) {	//avdtp media data
 | |
| +			if (!is_profile_busy(profile_a2dp)) {
 | |
| +				struct sbc_frame_hdr *sbc_header;
 | |
| +				struct rtp_header *rtph;
 | |
| +				u8 bitpool;
 | |
| +				update_profile_state(profile_a2dp, TRUE);
 | |
| +				rtph = (struct rtp_header *)user_data;
 | |
| +
 | |
| +				RTKBT_DBG("rtp: v %u, cc %u, pt %u",
 | |
| +					  rtph->v, rtph->cc, rtph->pt);
 | |
| +				/* move forward */
 | |
| +				user_data += sizeof(struct rtp_header) +
 | |
| +					rtph->cc * 4 + 1;
 | |
| +
 | |
| +				/* point to the sbc frame header */
 | |
| +				sbc_header = (struct sbc_frame_hdr *)user_data;
 | |
| +				bitpool = sbc_header->bitpool;
 | |
| +
 | |
| +				print_sbc_header(sbc_header);
 | |
| +
 | |
| +				RTKBT_DBG("bitpool %u", bitpool);
 | |
| +
 | |
| +				rtk_vendor_cmd_to_fw(HCI_VENDOR_SET_BITPOOL,
 | |
| +						1, &bitpool);
 | |
| +			}
 | |
| +			btrtl_coex.a2dp_packet_count++;
 | |
| +		}
 | |
| +
 | |
| +		if (prof_info->profile_index == profile_pan)
 | |
| +			btrtl_coex.pan_packet_count++;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void count_a2dp_packet_timeout(unsigned long data)
 | |
| +{
 | |
| +	RTKBT_DBG("%s: a2dp_packet_count %d", __func__,
 | |
| +			btrtl_coex.a2dp_packet_count);
 | |
| +	if (btrtl_coex.a2dp_packet_count == 0) {
 | |
| +		if (is_profile_busy(profile_a2dp)) {
 | |
| +			RTKBT_DBG("%s: a2dp busy->idle!", __func__);
 | |
| +			update_profile_state(profile_a2dp, FALSE);
 | |
| +		}
 | |
| +	}
 | |
| +	btrtl_coex.a2dp_packet_count = 0;
 | |
| +	mod_timer(&(btrtl_coex.a2dp_count_timer),
 | |
| +		  jiffies + msecs_to_jiffies(1000));
 | |
| +}
 | |
| +
 | |
| +static void count_pan_packet_timeout(unsigned long data)
 | |
| +{
 | |
| +	RTKBT_DBG("%s: pan_packet_count %d", __func__,
 | |
| +			btrtl_coex.pan_packet_count);
 | |
| +	if (btrtl_coex.pan_packet_count < PAN_PACKET_COUNT) {
 | |
| +		if (is_profile_busy(profile_pan)) {
 | |
| +			RTKBT_DBG("%s: pan busy->idle!", __func__);
 | |
| +			update_profile_state(profile_pan, FALSE);
 | |
| +		}
 | |
| +	} else {
 | |
| +		if (!is_profile_busy(profile_pan)) {
 | |
| +			RTKBT_DBG("timeout_handler: pan idle->busy!");
 | |
| +			update_profile_state(profile_pan, TRUE);
 | |
| +		}
 | |
| +	}
 | |
| +	btrtl_coex.pan_packet_count = 0;
 | |
| +	mod_timer(&(btrtl_coex.pan_count_timer),
 | |
| +		  jiffies + msecs_to_jiffies(1000));
 | |
| +}
 | |
| +
 | |
| +static void count_hogp_packet_timeout(unsigned long data)
 | |
| +{
 | |
| +	RTKBT_DBG("%s: hogp_packet_count %d", __func__,
 | |
| +			btrtl_coex.hogp_packet_count);
 | |
| +	if (btrtl_coex.hogp_packet_count == 0) {
 | |
| +		if (is_profile_busy(profile_hogp)) {
 | |
| +			RTKBT_DBG("%s: hogp busy->idle!", __func__);
 | |
| +			update_profile_state(profile_hogp, FALSE);
 | |
| +		}
 | |
| +	}
 | |
| +	btrtl_coex.hogp_packet_count = 0;
 | |
| +
 | |
| +	RTKBT_DBG("%s: voice_packet_count %d", __func__,
 | |
| +			btrtl_coex.voice_packet_count);
 | |
| +	if (btrtl_coex.voice_packet_count == 0) {
 | |
| +		if (is_profile_busy(profile_voice)) {
 | |
| +			RTKBT_DBG("%s: voice busy->idle!", __func__);
 | |
| +			update_profile_state(profile_voice, FALSE);
 | |
| +		}
 | |
| +	}
 | |
| +	btrtl_coex.voice_packet_count = 0;
 | |
| +	mod_timer(&(btrtl_coex.hogp_count_timer),
 | |
| +		  jiffies + msecs_to_jiffies(1000));
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +}
 | |
| +
 | |
| +static void rtk_notify_extension_version_to_wifi(void)
 | |
| +{
 | |
| +	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_EXTENSION_VERSION_NOTIFY);
 | |
| +	*p++ = para_length;
 | |
| +	UINT16_TO_STREAM(p, HCI_EXTENSION_VERSION);
 | |
| +	RTKBT_DBG("extension version is 0x%x", HCI_EXTENSION_VERSION);
 | |
| +	if (udpsocket_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 (udpsocket_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 (udpsocket_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 (udpsocket_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 (udpsocket_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 (udpsocket_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 (udpsocket_send(p_buf, para_length + HCI_CMD_PREAMBLE_SIZE) < 0)
 | |
| +		RTKBT_ERR("%s: sock send error", __func__);
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +	}
 | |
| +
 | |
| +	if ((opcode == HCI_OP_INQUIRY) || (opcode == HCI_OP_PERIODIC_INQ)) {
 | |
| +		if (!btrtl_coex.isinquirying) {
 | |
| +			btrtl_coex.isinquirying = 1;
 | |
| +			RTKBT_DBG("hci (periodic)inq, notify wifi "
 | |
| +				  "inquiry start");
 | |
| +			rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_START,
 | |
| +						       0, NULL);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if ((opcode == HCI_OP_INQUIRY_CANCEL)
 | |
| +	    || (opcode == HCI_OP_EXIT_PERIODIC_INQ)) {
 | |
| +		if (btrtl_coex.isinquirying) {
 | |
| +			btrtl_coex.isinquirying = 0;
 | |
| +			RTKBT_DBG("hci (periodic)inq cancel/exit, notify wifi "
 | |
| +				  "inquiry stop");
 | |
| +			rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0,
 | |
| +						       NULL);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (opcode == HCI_OP_ACCEPT_CONN_REQ) {
 | |
| +		if (!btrtl_coex.ispaging) {
 | |
| +			btrtl_coex.ispaging = 1;
 | |
| +			RTKBT_DBG("hci accept connreq, notify wifi page start");
 | |
| +			rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_START, 0,
 | |
| +						       NULL);
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_inquiry_complete(void)
 | |
| +{
 | |
| +	if (btrtl_coex.isinquirying) {
 | |
| +		btrtl_coex.isinquirying = 0;
 | |
| +		RTKBT_DBG("inq complete, notify wifi inquiry end");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0, NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_pin_code_req(void)
 | |
| +{
 | |
| +	if (!btrtl_coex.ispairing) {
 | |
| +		btrtl_coex.ispairing = 1;
 | |
| +		RTKBT_DBG("pin code req, notify wifi pair start");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_START, 0, NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_io_capa_req(void)
 | |
| +{
 | |
| +	if (!btrtl_coex.ispairing) {
 | |
| +		btrtl_coex.ispairing = 1;
 | |
| +		RTKBT_DBG("io cap req, notify wifi pair start");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_START, 0, NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_auth_request(void)
 | |
| +{
 | |
| +	if (btrtl_coex.ispairing) {
 | |
| +		btrtl_coex.ispairing = 0;
 | |
| +		RTKBT_DBG("auth req, notify wifi pair end");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_END, 0, NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_link_key_notify(void)
 | |
| +{
 | |
| +	if (btrtl_coex.ispairing) {
 | |
| +		btrtl_coex.ispairing = 0;
 | |
| +		RTKBT_DBG("link key notify, notify wifi pair end");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_END, 0, NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +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);
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +			RTKBT_DBG("hci period inq, start error, notify wifi "
 | |
| +				  "inquiry stop");
 | |
| +			rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0,
 | |
| +						       NULL);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	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);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (opcode == HCI_VENDOR_MAILBOX_CMD) {
 | |
| +		rtk_parse_vendor_mailbox_cmd_evt(p, total_len);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +			RTKBT_DBG("hci inq, start error, notify wifi inq stop");
 | |
| +			rtk_notify_btoperation_to_wifi(BT_OPCODE_INQUIRY_END, 0,
 | |
| +						       NULL);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (opcode == HCI_OP_CREATE_CONN) {
 | |
| +		if (!status && !btrtl_coex.ispaging) {
 | |
| +			btrtl_coex.ispaging = 1;
 | |
| +			RTKBT_DBG("hci create conn, notify wifi start page");
 | |
| +			rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_START, 0,
 | |
| +						       NULL);
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +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++;
 | |
| +
 | |
| +	if (status == 0) {
 | |
| +		if (btrtl_coex.ispaging) {
 | |
| +			btrtl_coex.ispaging = 0;
 | |
| +			RTKBT_DBG("notify wifi page success end");
 | |
| +			rtk_notify_btoperation_to_wifi
 | |
| +			    (BT_OPCODE_PAGE_SUCCESS_END, 0, NULL);
 | |
| +		}
 | |
| +
 | |
| +		hci_conn = find_connection_by_handle(&btrtl_coex, handle);
 | |
| +		if (hci_conn == NULL) {
 | |
| +			hci_conn = allocate_connection_by_handle(handle);
 | |
| +			if (hci_conn) {
 | |
| +				add_connection_to_hash(&btrtl_coex,
 | |
| +						       hci_conn);
 | |
| +				hci_conn->profile_bitmap = 0;
 | |
| +				memset(hci_conn->profile_refcount, 0, 8);
 | |
| +				if ((0 == link_type) || (2 == link_type)) {	//sco or esco
 | |
| +					hci_conn->type = 1;
 | |
| +					update_profile_connection(hci_conn,
 | |
| +								  profile_sco,
 | |
| +								  TRUE);
 | |
| +				} else
 | |
| +					hci_conn->type = 0;
 | |
| +			} else {
 | |
| +				RTKBT_ERR("hci connection allocate fail");
 | |
| +			}
 | |
| +		} else {
 | |
| +			RTKBT_DBG("hci conn handle 0x%04x already existed!",
 | |
| +				  handle);
 | |
| +			hci_conn->profile_bitmap = 0;
 | |
| +			memset(hci_conn->profile_refcount, 0, 8);
 | |
| +			if ((0 == link_type) || (2 == link_type)) {	//sco or esco
 | |
| +				hci_conn->type = 1;
 | |
| +				update_profile_connection(hci_conn, profile_sco,
 | |
| +							  TRUE);
 | |
| +			} else
 | |
| +				hci_conn->type = 0;
 | |
| +		}
 | |
| +	} else if (btrtl_coex.ispaging) {
 | |
| +		btrtl_coex.ispaging = 0;
 | |
| +		RTKBT_DBG("notify wifi page unsuccess end");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_UNSUCCESS_END, 0,
 | |
| +					       NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_le_connection_complete_evt(u8 * p)
 | |
| +{
 | |
| +	u16 handle, interval;
 | |
| +	u8 status;
 | |
| +	rtk_conn_prof *hci_conn = NULL;
 | |
| +
 | |
| +	status = *p++;
 | |
| +	STREAM_TO_UINT16(handle, p);
 | |
| +	p += 8;			//role, address type, address
 | |
| +	STREAM_TO_UINT16(interval, p);
 | |
| +
 | |
| +	if (status == 0) {
 | |
| +		if (btrtl_coex.ispaging) {
 | |
| +			btrtl_coex.ispaging = 0;
 | |
| +			RTKBT_DBG("notify wifi page success end");
 | |
| +			rtk_notify_btoperation_to_wifi
 | |
| +			    (BT_OPCODE_PAGE_SUCCESS_END, 0, NULL);
 | |
| +		}
 | |
| +
 | |
| +		hci_conn = find_connection_by_handle(&btrtl_coex, handle);
 | |
| +		if (hci_conn == NULL) {
 | |
| +			hci_conn = allocate_connection_by_handle(handle);
 | |
| +			if (hci_conn) {
 | |
| +				add_connection_to_hash(&btrtl_coex,
 | |
| +						       hci_conn);
 | |
| +				hci_conn->profile_bitmap = 0;
 | |
| +				memset(hci_conn->profile_refcount, 0, 8);
 | |
| +				hci_conn->type = 2;
 | |
| +				update_profile_connection(hci_conn, profile_hid, TRUE);	//for coex, le is the same as hid
 | |
| +				update_hid_active_state(handle, interval);
 | |
| +			} else {
 | |
| +				RTKBT_ERR("hci connection allocate fail");
 | |
| +			}
 | |
| +		} else {
 | |
| +			RTKBT_DBG("hci conn handle 0x%04x already existed!",
 | |
| +				  handle);
 | |
| +			hci_conn->profile_bitmap = 0;
 | |
| +			memset(hci_conn->profile_refcount, 0, 8);
 | |
| +			hci_conn->type = 2;
 | |
| +			update_profile_connection(hci_conn, profile_hid, TRUE);
 | |
| +			update_hid_active_state(handle, interval);
 | |
| +		}
 | |
| +	} else if (btrtl_coex.ispaging) {
 | |
| +		btrtl_coex.ispaging = 0;
 | |
| +		RTKBT_DBG("notify wifi page unsuccess end");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAGE_UNSUCCESS_END, 0,
 | |
| +					       NULL);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +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(p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_LE_CONN_UPDATE_COMPLETE:
 | |
| +		rtk_handle_le_connection_update_complete_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	default:
 | |
| +		break;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +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;
 | |
| +
 | |
| +	spin_lock(&coex->spin_lock_profile);
 | |
| +
 | |
| +	list_for_each_safe(iter, temp, &coex->profile_list) {
 | |
| +		prof_info = list_entry(iter, rtk_prof_info, list);
 | |
| +		if (handle == prof_info->handle && prof_info->scid
 | |
| +		    && prof_info->dcid) {
 | |
| +			RTKBT_DBG("hci disconn, hndl %x, psm %x, dcid %x, "
 | |
| +				  "scid %x", prof_info->handle,
 | |
| +				  prof_info->psm, prof_info->dcid,
 | |
| +				  prof_info->scid);
 | |
| +			//If both scid and dcid > 0, L2cap connection is exist.
 | |
| +			update_profile_connection(conn,
 | |
| +					prof_info->profile_index, FALSE);
 | |
| +			delete_profile_from_hash(prof_info);
 | |
| +		}
 | |
| +	}
 | |
| +	spin_unlock(&coex->spin_lock_profile);
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_disconnect_complete_evt(u8 * p)
 | |
| +{
 | |
| +	u16 handle;
 | |
| +	u8 status;
 | |
| +	/* u8 reason; */
 | |
| +	rtk_conn_prof *hci_conn = NULL;
 | |
| +
 | |
| +	if (btrtl_coex.ispairing) {	//for slave: connection will be disconnected if authentication fail
 | |
| +		btrtl_coex.ispairing = 0;
 | |
| +		RTKBT_DBG("hci disc complete, notify wifi pair end");
 | |
| +		rtk_notify_btoperation_to_wifi(BT_OPCODE_PAIR_END, 0, NULL);
 | |
| +	}
 | |
| +
 | |
| +	status = *p++;
 | |
| +	STREAM_TO_UINT16(handle, p);
 | |
| +
 | |
| +	/* reason = *p; */
 | |
| +
 | |
| +	if (status == 0) {
 | |
| +		RTKBT_DBG("process disconn complete event.");
 | |
| +		hci_conn = find_connection_by_handle(&btrtl_coex, handle);
 | |
| +		if (hci_conn) {
 | |
| +			switch (hci_conn->type) {
 | |
| +			case 0:
 | |
| +				/* FIXME: If this is interrupted by l2cap rx,
 | |
| +				 * there may be deadlock on spin_lock_profile */
 | |
| +				disconn_acl(handle, hci_conn);
 | |
| +				break;
 | |
| +
 | |
| +			case 1:
 | |
| +				update_profile_connection(hci_conn, profile_sco,
 | |
| +							  FALSE);
 | |
| +				break;
 | |
| +
 | |
| +			case 2:
 | |
| +				update_profile_connection(hci_conn, profile_hid,
 | |
| +							  FALSE);
 | |
| +				break;
 | |
| +
 | |
| +			default:
 | |
| +				break;
 | |
| +			}
 | |
| +			delete_connection_from_hash(hci_conn);
 | |
| +		} else
 | |
| +			RTKBT_ERR("hci conn handle 0x%04x not found", handle);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_handle_specific_evt(u8 * p)
 | |
| +{
 | |
| +	u16 subcode;
 | |
| +
 | |
| +	STREAM_TO_UINT16(subcode, p);
 | |
| +	if (subcode == HCI_VENDOR_PTA_AUTO_REPORT_EVENT) {
 | |
| +		RTKBT_DBG("notify wifi driver with autoreport data");
 | |
| +		rtk_notify_info_to_wifi(AUTO_REPORT, RTL_BTINFO_LEN,
 | |
| +			(uint8_t *)p);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_parse_event_data(struct rtl_coex_struct *coex,
 | |
| +		u8 *data, u16 len)
 | |
| +{
 | |
| +	u8 *p = data;
 | |
| +	u8 event_code = *p++;
 | |
| +	u8 total_len = *p++;
 | |
| +
 | |
| +	(void)coex;
 | |
| +	(void)&len;
 | |
| +
 | |
| +	switch (event_code) {
 | |
| +	case HCI_EV_INQUIRY_COMPLETE:
 | |
| +		rtk_handle_inquiry_complete();
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_PIN_CODE_REQ:
 | |
| +		rtk_handle_pin_code_req();
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_IO_CAPA_REQUEST:
 | |
| +		rtk_handle_io_capa_req();
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_AUTH_COMPLETE:
 | |
| +		rtk_handle_auth_request();
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_LINK_KEY_NOTIFY:
 | |
| +		rtk_handle_link_key_notify();
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_MODE_CHANGE:
 | |
| +		rtk_handle_mode_change_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_CMD_COMPLETE:
 | |
| +		rtk_handle_cmd_complete_evt(total_len, p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_CMD_STATUS:
 | |
| +		rtk_handle_cmd_status_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_CONN_COMPLETE:
 | |
| +	case HCI_EV_SYNC_CONN_COMPLETE:
 | |
| +		rtk_handle_connection_complete_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_DISCONN_COMPLETE:
 | |
| +		rtk_handle_disconnect_complete_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_LE_META:
 | |
| +		rtk_handle_le_meta_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	case HCI_EV_VENDOR_SPECIFIC:
 | |
| +		rtk_handle_specific_evt(p);
 | |
| +		break;
 | |
| +
 | |
| +	default:
 | |
| +		break;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +const char l2_dir_str[][4] = {
 | |
| +	"RX", "TX",
 | |
| +};
 | |
| +
 | |
| +void rtl_process_l2_sig(struct rtl_l2_buff *l2)
 | |
| +{
 | |
| +	/* u8 flag; */
 | |
| +	u8 code;
 | |
| +	/* u8 identifier; */
 | |
| +	u16 handle;
 | |
| +	/* u16 total_len; */
 | |
| +	/* u16 pdu_len, channel_id; */
 | |
| +	/* u16 command_len; */
 | |
| +	u16 psm, scid, dcid, result;
 | |
| +	/* u16 status; */
 | |
| +	u8 *pp = l2->data;
 | |
| +
 | |
| +	STREAM_TO_UINT16(handle, pp);
 | |
| +	/* flag = handle >> 12; */
 | |
| +	handle = handle & 0x0FFF;
 | |
| +	/* STREAM_TO_UINT16(total_len, pp); */
 | |
| +	pp += 2; /* data total length */
 | |
| +
 | |
| +	/* STREAM_TO_UINT16(pdu_len, pp);
 | |
| +	 * STREAM_TO_UINT16(channel_id, pp); */
 | |
| +	pp += 4; /* l2 len and channel id */
 | |
| +
 | |
| +	code = *pp++;
 | |
| +	switch (code) {
 | |
| +	case L2CAP_CONN_REQ:
 | |
| +		/* identifier = *pp++; */
 | |
| +		pp++;
 | |
| +		/* STREAM_TO_UINT16(command_len, pp); */
 | |
| +		pp += 2;
 | |
| +		STREAM_TO_UINT16(psm, pp);
 | |
| +		STREAM_TO_UINT16(scid, pp);
 | |
| +		RTKBT_DBG("%s l2cap conn req, hndl 0x%04x, PSM 0x%04x, "
 | |
| +			  "scid 0x%04x", l2_dir_str[l2->out], handle, psm,
 | |
| +			  scid);
 | |
| +		handle_l2cap_con_req(handle, psm, scid, l2->out);
 | |
| +		break;
 | |
| +
 | |
| +	case L2CAP_CONN_RSP:
 | |
| +		/* identifier = *pp++; */
 | |
| +		pp++;
 | |
| +		/* STREAM_TO_UINT16(command_len, pp); */
 | |
| +		pp += 2;
 | |
| +		STREAM_TO_UINT16(dcid, pp);
 | |
| +		STREAM_TO_UINT16(scid, pp);
 | |
| +		STREAM_TO_UINT16(result, pp);
 | |
| +		/* STREAM_TO_UINT16(status, pp); */
 | |
| +		pp += 2;
 | |
| +		RTKBT_DBG("%s l2cap conn rsp, hndl 0x%04x, dcid 0x%04x, "
 | |
| +			  "scid 0x%04x, result 0x%04x", l2_dir_str[l2->out],
 | |
| +			  handle, dcid, scid, result);
 | |
| +		handle_l2cap_con_rsp(handle, dcid, scid, l2->out, result);
 | |
| +		break;
 | |
| +
 | |
| +	case L2CAP_DISCONN_REQ:
 | |
| +		/* identifier = *pp++; */
 | |
| +		pp++;
 | |
| +		/* STREAM_TO_UINT16(command_len, pp); */
 | |
| +		pp += 2;
 | |
| +		STREAM_TO_UINT16(dcid, pp);
 | |
| +		STREAM_TO_UINT16(scid, pp);
 | |
| +		RTKBT_DBG("%s l2cap disconn req, hndl 0x%04x, dcid 0x%04x, "
 | |
| +			  "scid 0x%04x", l2_dir_str[l2->out], handle, dcid, scid);
 | |
| +		handle_l2cap_discon_req(handle, dcid, scid, l2->out);
 | |
| +		break;
 | |
| +	default:
 | |
| +		RTKBT_DBG("undesired l2 command %u", code);
 | |
| +		break;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtl_l2_data_process(u8 *pp, u16 len, int dir)
 | |
| +{
 | |
| +	u8 code;
 | |
| +	u8 flag;
 | |
| +	u16 handle, pdu_len, channel_id;
 | |
| +	/* u16 total_len; */
 | |
| +	struct rtl_l2_buff *l2 = NULL;
 | |
| +	u8 *hd = pp;
 | |
| +
 | |
| +	/* RTKBT_DBG("l2 sig data %p, len %u, dir %d", pp, len, dir); */
 | |
| +
 | |
| +	STREAM_TO_UINT16(handle, pp);
 | |
| +	flag = handle >> 12;
 | |
| +	handle = handle & 0x0FFF;
 | |
| +	/* STREAM_TO_UINT16(total_len, pp); */
 | |
| +	pp += 2; /* data total length */
 | |
| +
 | |
| +	STREAM_TO_UINT16(pdu_len, pp);
 | |
| +	STREAM_TO_UINT16(channel_id, pp);
 | |
| +
 | |
| +	if (channel_id == 0x0001) {
 | |
| +		code = *pp++;
 | |
| +		switch (code) {
 | |
| +		case L2CAP_CONN_REQ:
 | |
| +		case L2CAP_CONN_RSP:
 | |
| +		case L2CAP_DISCONN_REQ:
 | |
| +			RTKBT_DBG("l2cap op %u, len %u, out %d", code, len,
 | |
| +				  dir);
 | |
| +			l2 = rtl_l2_node_get(&btrtl_coex);
 | |
| +			if (l2) {
 | |
| +				u16 n;
 | |
| +				n = min_t(uint, len, L2_MAX_SUBSEC_LEN);
 | |
| +				memcpy(l2->data, hd, n);
 | |
| +				l2->out = dir;
 | |
| +				rtl_l2_node_to_used(&btrtl_coex, l2);
 | |
| +				queue_delayed_work(btrtl_coex.fw_wq,
 | |
| +						&btrtl_coex.l2_work, 0);
 | |
| +			} else
 | |
| +				RTKBT_ERR("%s: failed to get l2 node",
 | |
| +					  __func__);
 | |
| +			break;
 | |
| +		case L2CAP_DISCONN_RSP:
 | |
| +			break;
 | |
| +		default:
 | |
| +			break;
 | |
| +		}
 | |
| +	} else {
 | |
| +		if ((flag != 0x01) && (is_profile_connected(profile_a2dp) ||
 | |
| +				       is_profile_connected(profile_pan)))
 | |
| +			/* Do not count the continuous packets */
 | |
| +			packets_count(handle, channel_id, pdu_len, dir, pp);
 | |
| +	}
 | |
| +	return;
 | |
| +}
 | |
| +
 | |
| +
 | |
| +static void rtl_l2_work(struct work_struct *work)
 | |
| +{
 | |
| +	struct rtl_coex_struct *coex;
 | |
| +	struct rtl_l2_buff *l2;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	coex = container_of(work, struct rtl_coex_struct, l2_work.work);
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +	while (!list_empty(&coex->l2_used_list)) {
 | |
| +		l2 = list_entry(coex->l2_used_list.next, struct rtl_l2_buff,
 | |
| +				list);
 | |
| +		list_del(&l2->list);
 | |
| +
 | |
| +		spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +
 | |
| +		rtl_process_l2_sig(l2);
 | |
| +
 | |
| +		spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +
 | |
| +		list_add_tail(&l2->list, &coex->l2_free_list);
 | |
| +	}
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +
 | |
| +	return;
 | |
| +}
 | |
| +
 | |
| +static void rtl_ev_work(struct work_struct *work)
 | |
| +{
 | |
| +	struct rtl_coex_struct *coex;
 | |
| +	struct rtl_hci_ev *ev;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	coex = container_of(work, struct rtl_coex_struct, fw_work.work);
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +	while (!list_empty(&coex->ev_used_list)) {
 | |
| +		ev = list_entry(coex->ev_used_list.next, struct rtl_hci_ev,
 | |
| +				list);
 | |
| +		list_del(&ev->list);
 | |
| +		spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +
 | |
| +		rtk_parse_event_data(coex, ev->data, ev->len);
 | |
| +
 | |
| +		spin_lock_irqsave(&coex->buff_lock, flags);
 | |
| +		list_add_tail(&ev->list, &coex->ev_free_list);
 | |
| +	}
 | |
| +	spin_unlock_irqrestore(&coex->buff_lock, flags);
 | |
| +}
 | |
| +
 | |
| +int ev_filter_out(u8 ev_code)
 | |
| +{
 | |
| +	switch (ev_code) {
 | |
| +	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_CMD_COMPLETE:
 | |
| +	case HCI_EV_CMD_STATUS:
 | |
| +	case HCI_EV_CONN_COMPLETE:
 | |
| +	case HCI_EV_SYNC_CONN_COMPLETE:
 | |
| +	case HCI_EV_DISCONN_COMPLETE:
 | |
| +	case HCI_EV_LE_META:
 | |
| +	case HCI_EV_VENDOR_SPECIFIC:
 | |
| +		return 0;
 | |
| +	default:
 | |
| +		return 1;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void rtk_btcoex_evt_enqueue(__u8 *s, __u16 count)
 | |
| +{
 | |
| +	struct rtl_hci_ev *ev;
 | |
| +
 | |
| +	if (ev_filter_out(s[0]))
 | |
| +		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);
 | |
| +	//}
 | |
| +}
 | |
| +
 | |
| +static void polling_bt_info(unsigned long data)
 | |
| +{
 | |
| +	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 */
 | |
| +		setup_timer(&(btrtl_coex.polling_timer), polling_bt_info, 0);
 | |
| +		btrtl_coex.polling_timer.expires =
 | |
| +		    jiffies + msecs_to_jiffies(ctl->polling_time * 1000);
 | |
| +		add_timer(&(btrtl_coex.polling_timer));
 | |
| +	}
 | |
| +
 | |
| +	/* 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;
 | |
| +		udpsocket_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;
 | |
| +		udpsocket_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);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static inline void rtl_free_frags(struct rtl_coex_struct *coex)
 | |
| +{
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&coex->rxlock, flags);
 | |
| +
 | |
| +	coex->elen = 0;
 | |
| +	coex->tbuff = NULL;
 | |
| +
 | |
| +	spin_unlock_irqrestore(&coex->rxlock, flags);
 | |
| +}
 | |
| +
 | |
| +void rtk_btcoex_open(struct hci_dev *hdev)
 | |
| +{
 | |
| +	if (test_and_set_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) {
 | |
| +		RTKBT_WARN("RTL COEX is already running.");
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	RTKBT_INFO("Open BTCOEX");
 | |
| +
 | |
| +	/* Just for test */
 | |
| +	//struct rtl_btinfo_ctl ctl;
 | |
| +
 | |
| +	INIT_DELAYED_WORK(&btrtl_coex.fw_work, (void *)rtl_ev_work);
 | |
| +	INIT_DELAYED_WORK(&btrtl_coex.sock_work,
 | |
| +			  (void *)udpsocket_recv_data);
 | |
| +	INIT_DELAYED_WORK(&btrtl_coex.l2_work, (void *)rtl_l2_work);
 | |
| +
 | |
| +	init_timer(&btrtl_coex.polling_timer);
 | |
| +	init_timer(&btrtl_coex.a2dp_count_timer);
 | |
| +	init_timer(&btrtl_coex.pan_count_timer);
 | |
| +	init_timer(&btrtl_coex.hogp_count_timer);
 | |
| +
 | |
| +	btrtl_coex.hdev = hdev;
 | |
| +	btrtl_coex.wifi_on = 0;
 | |
| +
 | |
| +	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;
 | |
| +
 | |
| +	create_udpsocket();
 | |
| +	udpsocket_send(invite_req, sizeof(invite_req));
 | |
| +
 | |
| +	/* Just for test */
 | |
| +	//ctl.polling_enable = 1;
 | |
| +	//ctl.polling_time = 1;
 | |
| +	//ctl.autoreport_enable = 1;
 | |
| +	//rtk_handle_bt_info_control((u8 *)&ctl);
 | |
| +}
 | |
| +
 | |
| +void rtk_btcoex_close(void)
 | |
| +{
 | |
| +	int kk = 0;
 | |
| +
 | |
| +	if (!test_and_clear_bit(RTL_COEX_RUNNING, &btrtl_coex.flags)) {
 | |
| +		RTKBT_WARN("RTL COEX is already closed.");
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	RTKBT_INFO("Close BTCOEX");
 | |
| +
 | |
| +	/* Close coex socket */
 | |
| +	if (btrtl_coex.wifi_on)
 | |
| +		udpsocket_send(bt_leave, sizeof(bt_leave));
 | |
| +	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);
 | |
| +	}
 | |
| +
 | |
| +	/* Delete all timers */
 | |
| +	if (btrtl_coex.polling_enable) {
 | |
| +		btrtl_coex.polling_enable = 0;
 | |
| +		del_timer_sync(&(btrtl_coex.polling_timer));
 | |
| +	}
 | |
| +	del_timer_sync(&(btrtl_coex.a2dp_count_timer));
 | |
| +	del_timer_sync(&(btrtl_coex.pan_count_timer));
 | |
| +
 | |
| +	cancel_delayed_work_sync(&btrtl_coex.fw_work);
 | |
| +	cancel_delayed_work_sync(&btrtl_coex.l2_work);
 | |
| +
 | |
| +	flush_connection_hash(&btrtl_coex);
 | |
| +	flush_profile_hash(&btrtl_coex);
 | |
| +	btrtl_coex.profile_bitmap = 0;
 | |
| +	btrtl_coex.profile_status = 0;
 | |
| +	for (kk = 0; kk < 8; kk++)
 | |
| +		btrtl_coex.profile_refcount[kk] = 0;
 | |
| +
 | |
| +	rtl_free_frags(&btrtl_coex);
 | |
| +	RTKBT_DBG("-x");
 | |
| +}
 | |
| +
 | |
| +void rtk_btcoex_probe(struct hci_dev *hdev)
 | |
| +{
 | |
| +	btrtl_coex.hdev = hdev;
 | |
| +	spin_lock_init(&btrtl_coex.spin_lock_sock);
 | |
| +	spin_lock_init(&btrtl_coex.spin_lock_profile);
 | |
| +}
 | |
| +
 | |
| +void rtk_btcoex_init(void)
 | |
| +{
 | |
| +	RTKBT_DBG("%s: version: %s", __func__, RTK_VERSION);
 | |
| +	RTKBT_DBG("create workqueue");
 | |
| +	btrtl_coex.sock_wq = create_workqueue("btudpwork");
 | |
| +	btrtl_coex.fw_wq = create_workqueue("btfwwork");
 | |
| +	rtl_alloc_buff(&btrtl_coex);
 | |
| +	spin_lock_init(&btrtl_coex.rxlock);
 | |
| +}
 | |
| +
 | |
| +void rtk_btcoex_exit(void)
 | |
| +{
 | |
| +	RTKBT_DBG("%s: destroy workqueue", __func__);
 | |
| +	flush_workqueue(btrtl_coex.sock_wq);
 | |
| +	destroy_workqueue(btrtl_coex.sock_wq);
 | |
| +	flush_workqueue(btrtl_coex.fw_wq);
 | |
| +	destroy_workqueue(btrtl_coex.fw_wq);
 | |
| +	rtl_free_buff(&btrtl_coex);
 | |
| +}
 |