538 lines
16 KiB
C
538 lines
16 KiB
C
/*
|
|
* Copyright (c) 202X Amlogic, Inc. All rights reserved.
|
|
*
|
|
* This source code is subject to the terms and conditions defined in the
|
|
* file 'LICENSE' which is part of this source code package.
|
|
*
|
|
* Description:
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/version.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/string.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/semaphore.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/amlogic/pm.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <net/bluetooth/bluetooth.h>
|
|
#include <net/bluetooth/hci_core.h>
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0)
|
|
#include <asm/unaligned.h>
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0)
|
|
#include <asm-generic/unaligned.h>
|
|
#else
|
|
#include <linux/unaligned.h>
|
|
#endif
|
|
|
|
//#include "hci_uart.h"
|
|
#include "common.h"
|
|
#include "intf.h"
|
|
#include "intf_uart.h"
|
|
#include "intf_uart_coex.h"
|
|
#include "rc_list.h"
|
|
#include "debug_dev.h"
|
|
#include "driver.h"
|
|
#include "chip.h"
|
|
|
|
#define AML_COEX_VERSION "2026-0203,1430 LEA"
|
|
#define IS_VALID_PKT_TYPE(t) ( (t >= HCI_COMMAND_PKT && t <= HCI_ISO_PKT) || (t == HCI_15P4_PKT) )
|
|
static int amlbt_coex_recv_event(struct hci_dev *hdev, struct sk_buff *skb);
|
|
static int aml_15p4_recv_frame(struct hci_dev *hdev, struct sk_buff *skb);
|
|
|
|
static const struct h4_recv_pkt aml_coex_recv_pkts[] = {
|
|
{ H4_RECV_ACL, .recv = hci_recv_frame },
|
|
{ H4_RECV_SCO, .recv = hci_recv_frame },
|
|
{ H4_RECV_EVENT, .recv = amlbt_coex_recv_event },
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
|
|
{ H4_RECV_ISO, .recv = hci_recv_frame },
|
|
#endif
|
|
{ H4_RECV_15P4, .recv = aml_15p4_recv_frame },
|
|
};
|
|
|
|
static int aml_thread_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
//unsigned char pkt_type;
|
|
|
|
if (!skb) {
|
|
BTE("%s: invalid param", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
//pkt_type = hci_skb_pkt_type(skb);
|
|
//BTI("%s: Thread pkt, type %#x, len %d", __func__, pkt_type, skb->len);
|
|
BTD("%s: Thread pkt, len %d", __func__, skb->len);
|
|
|
|
if (skb_headroom(skb) < 1) {
|
|
BTE("no headroom\n");
|
|
kfree_skb(skb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
skb_push(skb, 1);
|
|
skb->data[0] = HCI_15P4_PKT;
|
|
BTD("%s:thread rx: %02x %02x\n", __func__,skb->data[0], skb->data[1]);
|
|
skb_queue_tail(&p_bt->thread_res.thread_rx_queue, skb);
|
|
wake_up_interruptible(&p_bt->thread_res.thread_wait_queue);
|
|
return 0;
|
|
}
|
|
|
|
static int aml_zigbee_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
//unsigned char pkt_type;
|
|
|
|
if (!skb) {
|
|
BTE("%s: invalid param", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
//pkt_type = hci_skb_pkt_type(skb);
|
|
//BTI("%s: zigbee pkt, type %#x, len %d", __func__, pkt_type, skb->len);
|
|
BTD("%s: zigbee pkt, len %d", __func__, skb->len);
|
|
|
|
if (skb_headroom(skb) < 1) {
|
|
BTE("no headroom\n");
|
|
kfree_skb(skb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
skb_push(skb, 1);
|
|
skb->data[0] = HCI_15P4_PKT;
|
|
BTD("%s:zigbee rx: %02x %02x\n", __func__,skb->data[0], skb->data[1]);
|
|
skb_queue_tail(&p_bt->zigbee_res.zigbee_rx_queue, skb);
|
|
wake_up_interruptible(&p_bt->zigbee_res.zigbee_wait_queue);
|
|
return 0;
|
|
}
|
|
|
|
static int aml_15p4_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
|
{
|
|
unsigned char pkt_type;
|
|
unsigned char sub_pkt_type;
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
|
|
if (!skb) {
|
|
BTE("%s: invalid param",__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pkt_type = hci_skb_pkt_type(skb);
|
|
BTD("%s: 15.4 pkt, type %#x, len %d", __func__, pkt_type, skb->len);
|
|
|
|
if (pkt_type != HCI_15P4_PKT) {
|
|
BTE("%s: not 15.4 pkt (type %#x), drop", __func__, pkt_type);
|
|
kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sub_pkt_type = skb->data[0];
|
|
switch (sub_pkt_type) {
|
|
case HCI_15P4_THREAD_PKT:
|
|
aml_thread_recv_frame(hdev, skb);
|
|
break;
|
|
case HCI_15P4_ZIGBEE_PKT:
|
|
aml_zigbee_recv_frame(hdev, skb);
|
|
break;
|
|
case HCI_15P4_LOG_PKT:
|
|
if (p_bt->zigbee_res.zigbee_start) {
|
|
aml_zigbee_recv_frame(hdev, skb);
|
|
} else if (p_bt->thread_res.thread_start) {
|
|
aml_thread_recv_frame(hdev, skb);
|
|
} else {
|
|
BTE("%s: No handler for sub-type %#x", __func__, sub_pkt_type);
|
|
kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
BTE("%s: unknown 15.4 sub-type %#x, drop", __func__, sub_pkt_type);
|
|
kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int amlbt_coex_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = amlbt_intf_uart_check_hci_event(skb);
|
|
if (ret == 0) {
|
|
BTD("event skip -> stack\n");
|
|
hci_recv_frame(hdev, skb);
|
|
} else {
|
|
BTD("event match (ret=%d)\n", ret);
|
|
kfree_skb(skb);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct sk_buff *aml_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)) {
|
|
BTE("%s skb err\n", __func__);
|
|
skb = NULL;
|
|
}
|
|
BTD("%s:count:%d\n", __func__, count);
|
|
BTD("%s:buffer:[%#x,%#x,%#x,%#x]\n", __func__, buffer[0], buffer[1], buffer[2], buffer[3]);
|
|
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) {
|
|
BTE("%s bt_skb_alloc failed\n", __func__);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
hci_skb_pkt_type(skb) = (&pkts[i])->type;
|
|
hci_skb_expect(skb) = (&pkts[i])->hlen;
|
|
break;
|
|
}
|
|
BTD("aml_h4_recv_buf hci_skb_pkt_type(skb) %#x\n", hci_skb_pkt_type(skb));
|
|
|
|
/* Check for invalid packet type */
|
|
if (!skb) {
|
|
BTE("%s invalid packet type: %#x\n", __func__, buffer[0]);
|
|
return ERR_PTR(-EILSEQ);
|
|
}
|
|
count -= 1;
|
|
buffer += 1;
|
|
}
|
|
|
|
BTD("buffer:[%#x,%#x,%#x,%#x]\n", buffer[0], buffer[1], buffer[2], buffer[3]);
|
|
len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
|
|
skb_put_data(skb, buffer, len);
|
|
|
|
count -= len;
|
|
buffer += len;
|
|
BTD("aml_h4_recv_buf len %#x skb->len %#x hci_skb_expect(skb) %#x\n", len, skb->len, hci_skb_expect(skb));
|
|
|
|
/* 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) {
|
|
BTE("%s unknown packet type in completion: %#x\n", __func__, hci_skb_pkt_type(skb));
|
|
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;
|
|
BTD("1 dlen %#x\n", dlen);
|
|
|
|
if (skb_tailroom(skb) < dlen) {
|
|
BTE("%s lsize=1, tailroom insufficient\n", __func__);
|
|
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;
|
|
BTD("2 %#x\n", dlen);
|
|
|
|
if (skb_tailroom(skb) < dlen) {
|
|
BTE("%s lsize=2, tailroom insufficient\n", __func__);
|
|
kfree_skb(skb);
|
|
return ERR_PTR(-EMSGSIZE);
|
|
}
|
|
break;
|
|
case 3:
|
|
/* Double octet variable length */
|
|
dlen = get_unaligned_le16(skb->data +
|
|
(&pkts[i])->loff);
|
|
dlen += 2;
|
|
hci_skb_expect(skb) += dlen;
|
|
BTD("3 %#x\n", dlen);
|
|
|
|
if (skb_tailroom(skb) < dlen) {
|
|
BTE("%s lsize=3, tailroom insufficient\n", __func__);
|
|
kfree_skb(skb);
|
|
return ERR_PTR(-EMSGSIZE);
|
|
}
|
|
break;
|
|
default:
|
|
/* Unsupported variable length */
|
|
BTE("%s unsupported lsize: %d\n", __func__, (&pkts[i])->lsize);
|
|
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;
|
|
BTD("i %#x\n", i);
|
|
}
|
|
} else {
|
|
hu->padding = (skb->len + 1) % alignment;
|
|
hu->padding = (alignment - hu->padding) % alignment;
|
|
|
|
/* Complete frame */
|
|
(&pkts[i])->recv(hdev, skb);
|
|
skb = NULL;
|
|
BTD("J %#x\n", i);
|
|
}
|
|
}
|
|
|
|
return skb;
|
|
}
|
|
|
|
static int aml_coex_common_txq_init(amlbt_t *p_bt)
|
|
{
|
|
if (!p_bt)
|
|
return -EINVAL;
|
|
|
|
skb_queue_head_init(&p_bt->common_res.tx_queue);
|
|
return 0;
|
|
}
|
|
|
|
static int aml_coex_open(struct hci_uart *hu)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
amlbt_res_uart_linux_t *uart_res_linux = &p_bt->uart_res_linux;
|
|
|
|
BTI("%s, hu %p\n", __func__, hu);
|
|
|
|
if (!uart_res_linux->initialized) {
|
|
uart_res_linux->initialized = true;
|
|
uart_res_linux->rx_skb = NULL;
|
|
}
|
|
|
|
skb_queue_purge(&p_bt->common_res.tx_queue);
|
|
|
|
hu->priv = uart_res_linux;
|
|
uart_res_linux->hu = hu;
|
|
return 0;
|
|
}
|
|
|
|
static int aml_coex_flush(struct hci_uart *hu)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
amlbt_res_uart_linux_t *uart_res_linux = hu->priv;
|
|
struct sk_buff *skb, *tmp;
|
|
unsigned long flags;
|
|
unsigned char pkt_type;
|
|
|
|
if (!uart_res_linux) {
|
|
BTE("%s: uart_linux_res is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (&p_bt->uart_res_linux != uart_res_linux) {
|
|
BTE("%s: uart_res_linux instance mismatch! expected: %p, actual: %p\n",
|
|
__func__, &p_bt->uart_res_linux, uart_res_linux);
|
|
return -EINVAL;
|
|
}
|
|
|
|
BTI("%s: flush bt pkts hu=%p\n", __func__, hu);
|
|
|
|
spin_lock_irqsave(&p_bt->common_res.tx_queue.lock, flags);
|
|
skb_queue_walk_safe(&p_bt->common_res.tx_queue, skb, tmp) {
|
|
pkt_type = hci_skb_pkt_type(skb);
|
|
|
|
switch (pkt_type) {
|
|
case HCI_COMMAND_PKT:
|
|
case HCI_ACLDATA_PKT:
|
|
case HCI_SCODATA_PKT:
|
|
case HCI_ISO_PKT:
|
|
BTI(" flush BT pkt type 0x%02x\n", pkt_type);
|
|
skb_unlink(skb, &p_bt->common_res.tx_queue);
|
|
kfree_skb(skb);
|
|
break;
|
|
|
|
case HCI_15P4_PKT:
|
|
BTI(" keep 15.4 pkt type 0x%02x\n", pkt_type);
|
|
break;
|
|
|
|
default:
|
|
BTE(" unknown pkt type 0x%02x, keep\n", pkt_type);
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&p_bt->common_res.tx_queue.lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/* Close protocol */
|
|
static int aml_coex_close(struct hci_uart *hu)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
amlbt_res_uart_linux_t *uart_res_linux = hu->priv;
|
|
|
|
BTI("%s, close hu %p\n", __func__, hu);
|
|
|
|
if (!uart_res_linux) {
|
|
BTE("uart_linux_res already freed!\n");
|
|
return 0;
|
|
}
|
|
|
|
skb_queue_purge(&p_bt->common_res.tx_queue);
|
|
|
|
if (uart_res_linux->rx_skb) {
|
|
kfree_skb(uart_res_linux->rx_skb);
|
|
uart_res_linux->rx_skb = NULL;
|
|
}
|
|
|
|
if (uart_res_linux) {
|
|
uart_res_linux->initialized = false;
|
|
uart_res_linux->hu = NULL;
|
|
hu->priv = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Enqueue frame for transmittion (padding, crc, etc) */
|
|
static int aml_coex_enqueue(struct hci_uart *hu, struct sk_buff *skb)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
amlbt_res_uart_linux_t *uart_res_linux = hu->priv;
|
|
unsigned char pkt_type;
|
|
|
|
if (!uart_res_linux || !skb) {
|
|
BTE("%s: invalid param (uart_linux_res %p, skb %p)", __func__, uart_res_linux, skb);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pkt_type = hci_skb_pkt_type(skb);
|
|
BTD("%s: pkt_type %#x, skb %p", __func__, pkt_type, skb);
|
|
|
|
if (!IS_VALID_PKT_TYPE(pkt_type)) {
|
|
BTE("%s: invalid pkt_type %#x, drop skb %p", __func__, pkt_type, skb);
|
|
kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Prepend skb with frame type */
|
|
memcpy(skb_push(skb, 1), &pkt_type, 1);
|
|
skb_queue_tail(&p_bt->common_res.tx_queue, skb);
|
|
|
|
BTD("%s, pkt_type %#x, skb len %d", __func__, pkt_type, skb->len);
|
|
return 0;
|
|
}
|
|
|
|
/* Recv data */
|
|
static int aml_coex_recv(struct hci_uart *hu, const void *data, int count)
|
|
{
|
|
amlbt_res_uart_linux_t *uart_res_linux = hu->priv;
|
|
|
|
if (!uart_res_linux || !data || count <= 0) {
|
|
BTE("%s: invalid param (uart_res_linux %p, data %p, count %d)",
|
|
__func__, uart_res_linux, data, count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!test_bit(HCI_UART_REGISTERED, &hu->flags)) {
|
|
BTE("aml_coex_recv HCI_UART_REGISTERED flag err!\n");
|
|
return -EUNATCH;
|
|
}
|
|
|
|
uart_res_linux->rx_skb = aml_h4_recv_buf(hu->hdev, uart_res_linux->rx_skb, data, count,
|
|
aml_coex_recv_pkts, ARRAY_SIZE(aml_coex_recv_pkts));
|
|
if (IS_ERR(uart_res_linux->rx_skb)) {
|
|
int err = PTR_ERR(uart_res_linux->rx_skb);
|
|
BTE("%s uart_res_linux->rx_skb err!\n",__func__);
|
|
bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err);
|
|
uart_res_linux->rx_skb = NULL;
|
|
return err;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct sk_buff *aml_coex_dequeue(struct hci_uart *hu)
|
|
{
|
|
amlbt_t *p_bt = amlbt_intf_get_p_bt();
|
|
amlbt_res_uart_linux_t *uart_res_linux = hu->priv;
|
|
|
|
BTD("%s: hu %p", __func__, hu);
|
|
if (!uart_res_linux) {
|
|
BTE("%s: uart_res_linux is NULL (hu->priv invalid)", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
return skb_dequeue(&p_bt->common_res.tx_queue);
|
|
}
|
|
|
|
static const struct hci_uart_proto aml_coex_proto = {
|
|
.id = HCI_UART_LL,
|
|
.name = "AML",
|
|
.open = aml_coex_open,
|
|
.close = aml_coex_close,
|
|
.recv = aml_coex_recv,
|
|
.enqueue = aml_coex_enqueue,
|
|
.dequeue = aml_coex_dequeue,
|
|
.flush = aml_coex_flush,
|
|
};
|
|
|
|
int aml_coex_init(void)
|
|
{
|
|
BTI("%s, %s", __func__, AML_COEX_VERSION);
|
|
return amlbt_hci_uart_register_proto(&aml_coex_proto);
|
|
}
|
|
|
|
void aml_coex_deinit(void)
|
|
{
|
|
BTI("%s", __func__);
|
|
amlbt_hci_uart_unregister_proto(&aml_coex_proto);
|
|
}
|
|
|