lineage_kernel_xcoverpro/drivers/misc/samsung/scsc_bt/scsc_shm.c

1694 lines
65 KiB
C
Executable File

/****************************************************************************
*
* Copyright (c) 2015 Samsung Electronics Co., Ltd
*
****************************************************************************/
/* MX BT shared memory interface */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <asm/io.h>
#include <linux/version.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
#include <scsc/scsc_wakelock.h>
#else
#include <linux/wakelock.h>
#endif
#include <scsc/scsc_mx.h>
#include <scsc/scsc_mifram.h>
#include <scsc/api/bsmhcp.h>
#include <scsc/scsc_logring.h>
#ifdef CONFIG_SCSC_LOG_COLLECTION
#include <scsc/scsc_log_collector.h>
#endif
#include "scsc_bt_priv.h"
#include "scsc_shm.h"
#include "scsc_bt_hci.h"
struct hci_credit_entry {
u16 hci_connection_handle;
u16 credits;
};
static u8 h4_write_buffer[BSMHCP_ACL_PACKET_SIZE + H4DMUX_HEADER_ACL];
static u8 h4_acl_header[5];
static u8 h4_hci_event_ncp_header[4 + BSMHCP_TRANSFER_RING_ACL_COUNT * sizeof(struct hci_credit_entry)];
static u32 h4_hci_event_ncp_header_len = 8;
static struct hci_credit_entry *h4_hci_credit_entries = (struct hci_credit_entry *) &h4_hci_event_ncp_header[4];
static u8 h4_hci_event_hardware_error[4] = { HCI_EVENT_PKT, HCI_EVENT_HARDWARE_ERROR_EVENT, 1, 0 };
static u8 h4_iq_report_evt[HCI_IQ_REPORT_MAX_LEN];
static u32 h4_iq_report_evt_len;
static u16 h4_irq_mask;
static void scsc_bt_shm_irq_handler(int irqbit, void *data)
{
/* Clear interrupt */
scsc_service_mifintrbit_bit_clear(bt_service.service, irqbit);
/* Ensure irq bit is cleared before reading the mailbox indexes */
mb();
bt_service.interrupt_count++;
/* Wake the reader operation */
if (bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write !=
bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read ||
bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write !=
bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read ||
bt_service.bsmhcp_protocol->header.mailbox_acl_free_write !=
bt_service.bsmhcp_protocol->header.mailbox_acl_free_read ||
bt_service.bsmhcp_protocol->header.mailbox_iq_report_write !=
bt_service.bsmhcp_protocol->header.mailbox_iq_report_read ||
0 != atomic_read(&bt_service.error_count) ||
bt_service.bsmhcp_protocol->header.panic_deathbed_confession) {
bt_service.interrupt_read_count++;
wake_lock_timeout(&bt_service.read_wake_lock, HZ);
wake_up(&bt_service.read_wait);
}
if (bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_write ==
bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_read &&
bt_service.bsmhcp_protocol->header.mailbox_acl_tx_write ==
bt_service.bsmhcp_protocol->header.mailbox_acl_tx_read) {
bt_service.interrupt_write_count++;
if (wake_lock_active(&bt_service.write_wake_lock)) {
bt_service.write_wake_unlock_count++;
wake_unlock(&bt_service.write_wake_lock);
}
}
}
/* Assign firmware/host interrupts */
static int scsc_bt_shm_init_interrupt(void)
{
int irq_ret = 0;
u16 irq_num = 0;
/* To-host f/w IRQ allocations and ISR registrations */
irq_ret = scsc_service_mifintrbit_register_tohost(
bt_service.service, scsc_bt_shm_irq_handler, NULL);
if (irq_ret < 0)
return irq_ret;
bt_service.bsmhcp_protocol->header.bg_to_ap_int_src = irq_ret;
h4_irq_mask |= 1 << irq_num++;
irq_ret = scsc_service_mifintrbit_register_tohost(
bt_service.service, scsc_bt_shm_irq_handler, NULL);
if (irq_ret < 0)
return irq_ret;
bt_service.bsmhcp_protocol->header.fg_to_ap_int_src = irq_ret;
h4_irq_mask |= 1 << irq_num++;
/* From-host f/w IRQ allocations */
irq_ret = scsc_service_mifintrbit_alloc_fromhost(
bt_service.service, SCSC_MIFINTR_TARGET_R4);
if (irq_ret < 0)
return irq_ret;
bt_service.bsmhcp_protocol->header.ap_to_bg_int_src = irq_ret;
h4_irq_mask |= 1 << irq_num++;
irq_ret = scsc_service_mifintrbit_alloc_fromhost(
bt_service.service, SCSC_MIFINTR_TARGET_R4);
if (irq_ret < 0)
return irq_ret;
bt_service.bsmhcp_protocol->header.ap_to_fg_int_src = irq_ret;
h4_irq_mask |= 1 << irq_num++;
irq_ret = scsc_service_mifintrbit_alloc_fromhost(
bt_service.service, SCSC_MIFINTR_TARGET_M4);
if (irq_ret < 0)
return irq_ret;
bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src = irq_ret;
h4_irq_mask |= 1 << irq_num++;
SCSC_TAG_DEBUG(BT_COMMON, "Registered to-host IRQ bits %d:%d, from-host IRQ bits %d:%d:%d\n",
bt_service.bsmhcp_protocol->header.bg_to_ap_int_src,
bt_service.bsmhcp_protocol->header.fg_to_ap_int_src,
bt_service.bsmhcp_protocol->header.ap_to_bg_int_src,
bt_service.bsmhcp_protocol->header.ap_to_fg_int_src,
bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src);
return 0;
}
bool scsc_bt_shm_h4_avdtp_detect_write(uint32_t flags,
uint16_t l2cap_cid,
uint16_t hci_connection_handle)
{
uint32_t tr_read;
uint32_t tr_write;
struct BSMHCP_TD_AVDTP *td;
spin_lock(&bt_service.avdtp_detect.fw_write_lock);
/* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */
tr_read = bt_service.bsmhcp_protocol->header.mailbox_avdtp_read;
tr_write = bt_service.bsmhcp_protocol->header.mailbox_avdtp_write;
td = &bt_service.bsmhcp_protocol->avdtp_transfer_ring[tr_write];
SCSC_TAG_DEBUG(BT_H4,
"AVDTP_DETECT_PKT (flags: 0x%08X, cid: 0x%04X, handle: 0x%04X, read=%u, write=%u)\n",
flags,
l2cap_cid,
hci_connection_handle,
tr_read,
tr_write);
/* Index out of bounds check */
if (tr_read >= BSMHCP_TRANSFER_RING_AVDTP_SIZE || tr_write >= BSMHCP_TRANSFER_RING_AVDTP_SIZE) {
spin_unlock(&bt_service.avdtp_detect.fw_write_lock);
SCSC_TAG_ERR(BT_H4,
"AVDTP_DETECT_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n",
tr_read,
tr_write);
atomic_inc(&bt_service.error_count);
return false;
}
/* Does the transfer ring have room for an entry */
if (BSMHCP_HAS_ROOM(tr_write, tr_read, BSMHCP_TRANSFER_RING_AVDTP_SIZE)) {
/* Fill the transfer descriptor with the AVDTP data */
td->flags = flags;
td->l2cap_cid = l2cap_cid;
td->hci_connection_handle = hci_connection_handle;
/* Ensure the wake lock is acquired */
if (!wake_lock_active(&bt_service.write_wake_lock)) {
bt_service.write_wake_lock_count++;
wake_lock(&bt_service.write_wake_lock);
}
/* Increate the write pointer */
BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_AVDTP_SIZE);
bt_service.bsmhcp_protocol->header.mailbox_avdtp_write = tr_write;
spin_unlock(&bt_service.avdtp_detect.fw_write_lock);
/* Memory barrier to ensure out-of-order execution is completed */
wmb();
/* Trigger the interrupt in the mailbox */
scsc_service_mifintrbit_bit_set(
bt_service.service,
bt_service.bsmhcp_protocol->header.ap_to_bg_int_src,
SCSC_MIFINTR_TARGET_R4);
} else {
/* Transfer ring full */
spin_unlock(&bt_service.avdtp_detect.fw_write_lock);
SCSC_TAG_ERR(BT_H4,
"AVDTP_DETECT_PKT - No more room for messages (tr_read=%u, tr_write=%u)\n",
tr_read,
tr_write);
scsc_service_force_panic(bt_service.service);
return false;
}
return true;
}
static ssize_t scsc_bt_shm_h4_hci_cmd_write(const unsigned char *data, size_t count)
{
/* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */
uint32_t tr_read = bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_read;
uint32_t tr_write = bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_write;
#ifdef CONFIG_SCSC_PRINTK
uint16_t op_code = *(uint16_t *) data;
#endif
/* Temp vars */
struct BSMHCP_TD_CONTROL *td = &bt_service.bsmhcp_protocol->hci_cmd_transfer_ring[tr_write];
SCSC_TAG_DEBUG(BT_H4, "HCI_COMMAND_PKT (op_code=0x%04x, len=%zu, read=%u, write=%u)\n", op_code, count, tr_read, tr_write);
/* Index out of bounds check */
if (tr_read >= BSMHCP_TRANSFER_RING_CMD_SIZE || tr_write >= BSMHCP_TRANSFER_RING_CMD_SIZE) {
SCSC_TAG_ERR(BT_H4, "HCI_COMMAND_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", tr_read, tr_write);
atomic_inc(&bt_service.error_count);
return -EIO;
}
/* Does the transfer ring have room for an entry */
if (BSMHCP_HAS_ROOM(tr_write, tr_read, BSMHCP_TRANSFER_RING_CMD_SIZE)) {
/* Fill the transfer descriptor with the HCI command data */
memcpy(td->data, data, count);
td->length = (u16)count;
/* Ensure the wake lock is acquired */
if (!wake_lock_active(&bt_service.write_wake_lock)) {
bt_service.write_wake_lock_count++;
wake_lock(&bt_service.write_wake_lock);
}
/* Increate the write pointer */
BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_CMD_SIZE);
bt_service.bsmhcp_protocol->header.mailbox_hci_cmd_write = tr_write;
/* Memory barrier to ensure out-of-order execution is completed */
wmb();
/* Trigger the interrupt in the mailbox */
scsc_service_mifintrbit_bit_set(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, SCSC_MIFINTR_TARGET_R4);
} else
/* Transfer ring full. Only happens if the user attempt to send more HCI command packets than
* available credits */
count = 0;
return count;
}
static ssize_t scsc_bt_shm_h4_acl_write(const unsigned char *data, size_t count)
{
/* Store the read/write pointer on the stack since both are placed in unbuffered/uncached memory */
uint32_t tr_read = bt_service.bsmhcp_protocol->header.mailbox_acl_tx_read;
uint32_t tr_write = bt_service.bsmhcp_protocol->header.mailbox_acl_tx_write;
/* Temp vars */
struct BSMHCP_TD_ACL_TX_DATA *td = &bt_service.bsmhcp_protocol->acl_tx_data_transfer_ring[tr_write];
int acldata_buf_index = -1;
u16 l2cap_length;
u32 i;
size_t payload_len = count - ACLDATA_HEADER_SIZE;
/* Index out of bounds check */
if (tr_read >= BSMHCP_TRANSFER_RING_ACL_SIZE || tr_write >= BSMHCP_TRANSFER_RING_ACL_SIZE) {
SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - Index out of bounds (tr_read=%u, tr_write=%u)\n", tr_read, tr_write);
atomic_inc(&bt_service.error_count);
return -EIO;
}
/* Allocate a data slot */
for (i = 0; i < BSMHCP_DATA_BUFFER_TX_ACL_SIZE; i++) {
/* Wrap the offset index around the buffer max */
if (++bt_service.last_alloc == BSMHCP_DATA_BUFFER_TX_ACL_SIZE)
bt_service.last_alloc = 0;
/* Claim a free slot */
if (bt_service.allocated[bt_service.last_alloc] == 0) {
bt_service.allocated[bt_service.last_alloc] = 1;
acldata_buf_index = bt_service.last_alloc;
bt_service.allocated_count++;
break;
}
}
/* Is a buffer available to hold the data */
if (acldata_buf_index < 0) {
SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - No buffers available\n");
atomic_inc(&bt_service.error_count);
return -EIO;
}
/* Does the transfer ring have room for an entry */
if (BSMHCP_HAS_ROOM(tr_write, tr_read, BSMHCP_TRANSFER_RING_ACL_SIZE)) {
/* Extract the ACL data header and L2CAP header and fill it into the transfer descriptor */
td->buffer_index = (uint8_t)acldata_buf_index;
td->flags = HCI_ACL_DATA_FLAGS(data);
td->hci_connection_handle = HCI_ACL_DATA_CON_HDL(data);
td->length = (u16)payload_len;
if ((td->flags & BSMHCP_ACL_BC_FLAG_BCAST_ACTIVE) ||
(td->flags & BSMHCP_ACL_BC_FLAG_BCAST_ALL)) {
SCSC_TAG_DEBUG(BT_H4,
"Setting broadcast handle (hci_connection_handle=0x%03x)\n",
td->hci_connection_handle);
bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_ACTIVE;
}
/* Is this a packet marked with the start flag */
if ((td->flags & BSMHCP_ACL_PB_FLAG_MASK) == BSMHCP_ACL_PB_FLAG_START_NONFLUSH ||
(td->flags & BSMHCP_ACL_PB_FLAG_MASK) == BSMHCP_ACL_PB_FLAG_START_FLUSH) {
/* Extract the L2CAP payload length and connection identifier */
td->l2cap_cid = HCI_L2CAP_CID(data);
/* data+4 to skip the HCI header, to align offsets with the rx detection. The "true" argument is to tell
* the detection that this is TX */
scsc_avdtp_detect_rxtx(td->hci_connection_handle, data+4, td->length, true);
l2cap_length = HCI_L2CAP_LENGTH(data);
SCSC_TAG_DEBUG(BT_TX, "ACL[START] (len=%u, buffer=%u, credits=%u, l2cap_cid=0x%04x, l2cap_length=%u)\n",
td->length, acldata_buf_index,
BSMHCP_DATA_BUFFER_TX_ACL_SIZE - (bt_service.allocated_count - bt_service.freed_count),
HCI_L2CAP_CID(data), l2cap_length);
if (l2cap_length == payload_len - L2CAP_HEADER_SIZE)
/* Mark it with the END flag if packet length matches the L2CAP payload length */
td->flags |= BSMHCP_ACL_L2CAP_FLAG_END;
else if (l2cap_length < payload_len - L2CAP_HEADER_SIZE) {
/* Mark it with the END flag if packet length is greater than the L2CAP payload length
and generate a warning notifying that this is incorrect according to the specification.
This is allowed to support the BITE tester. */
SCSC_TAG_WARNING(BT_H4, "ACL_DATA_PKT - H4 ACL payload length > L2CAP Length (payload_len=%zu, l2cap_length=%u)\n",
payload_len - L2CAP_HEADER_SIZE, l2cap_length);
td->flags |= BSMHCP_ACL_L2CAP_FLAG_END;
} else if (l2cap_length > (payload_len - L2CAP_HEADER_SIZE)) {
/* This is only a fragment of the packet. Save the remaining number of octets required
* to complete the packet */
bt_service.connection_handle_list[td->hci_connection_handle].length = (u16)(l2cap_length - payload_len + L2CAP_HEADER_SIZE);
bt_service.connection_handle_list[td->hci_connection_handle].l2cap_cid = HCI_L2CAP_CID(data);
} else {
/* The packet is larger than the L2CAP payload length - protocol error */
SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - L2CAP Length Error (l2cap_length=%u, payload_len=%zu)\n",
l2cap_length, payload_len - L2CAP_HEADER_SIZE);
atomic_inc(&bt_service.error_count);
return -EIO;
}
} else if ((td->flags & BSMHCP_ACL_PB_FLAG_MASK) == BSMHCP_ACL_PB_FLAG_CONT) {
/* Set the L2CAP connection identifer set by the start packet */
td->l2cap_cid = bt_service.connection_handle_list[td->hci_connection_handle].l2cap_cid;
SCSC_TAG_DEBUG(BT_TX, "ACL[CONT] (len=%u, buffer=%u, credits=%u, l2cap_cid=0x%04x, length=%u)\n",
td->length, acldata_buf_index,
BSMHCP_DATA_BUFFER_TX_ACL_SIZE - (bt_service.allocated_count - bt_service.freed_count),
bt_service.connection_handle_list[td->hci_connection_handle].l2cap_cid,
bt_service.connection_handle_list[td->hci_connection_handle].length);
/* Does this packet complete the L2CAP frame */
if (bt_service.connection_handle_list[td->hci_connection_handle].length == payload_len) {
/* The L2CAP frame is complete. mark it with the END flag */
td->flags |= BSMHCP_ACL_L2CAP_FLAG_END;
/* Set the remaining length to zero */
bt_service.connection_handle_list[td->hci_connection_handle].length = 0;
} else if (bt_service.connection_handle_list[td->hci_connection_handle].length < payload_len) {
/* Mark it with the END flag if packet length is greater than the L2CAP missing payload length
and generate a warning notifying that this is incorrect according to the specification.
This is allowed to support the BITE tester. */
SCSC_TAG_WARNING(BT_H4, "ACL_DATA_PKT - H4 ACL payload length > L2CAP Missing Length (payload_len=%zu, missing=%u)\n",
payload_len, bt_service.connection_handle_list[td->hci_connection_handle].length);
td->flags |= BSMHCP_ACL_L2CAP_FLAG_END;
/* Set the remaining length to zero */
bt_service.connection_handle_list[td->hci_connection_handle].length = 0;
} else if (bt_service.connection_handle_list[td->hci_connection_handle].length > payload_len)
/* This is another fragment of the packet. Save the remaining number of octets required
* to complete the packet */
bt_service.connection_handle_list[td->hci_connection_handle].length -= (u16)payload_len;
else if (bt_service.connection_handle_list[td->hci_connection_handle].length < payload_len) {
/* The packet is larger than the L2CAP payload length - protocol error */
SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - L2CAP Length Error (missing=%u, payload_len=%zu)\n",
bt_service.connection_handle_list[td->hci_connection_handle].length, payload_len);
atomic_inc(&bt_service.error_count);
return -EIO;
}
} else {
/* Reserved flags set - report it as an error */
SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - Flag set to reserved\n");
atomic_inc(&bt_service.error_count);
return -EIO;
}
SCSC_TAG_DEBUG(BT_H4, "ACL_DATA_PKT (len=%zu, read=%u, write=%u, slot=%u, flags=0x%04x, handle=0x%03x, l2cap_cid=0x%04x, missing=%u)\n",
payload_len, tr_read, tr_write, acldata_buf_index, td->flags >> 4, td->hci_connection_handle, td->l2cap_cid,
bt_service.connection_handle_list[td->hci_connection_handle].length);
/* Ensure the wake lock is acquired */
if (!wake_lock_active(&bt_service.write_wake_lock)) {
bt_service.write_wake_lock_count++;
wake_lock(&bt_service.write_wake_lock);
}
/* Copy the ACL packet into the targer buffer */
memcpy(&bt_service.bsmhcp_protocol->acl_tx_buffer[acldata_buf_index][0], &data[ACLDATA_HEADER_SIZE], payload_len);
/* Increate the write pointer */
BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_ACL_SIZE);
bt_service.bsmhcp_protocol->header.mailbox_acl_tx_write = tr_write;
/* Memory barrier to ensure out-of-order execution is completed */
wmb();
if (bt_service.bsmhcp_protocol->header.firmware_features & BSMHCP_FEATURE_M4_INTERRUPTS)
/* Trigger the interrupt in the mailbox */
scsc_service_mifintrbit_bit_set(bt_service.service,
bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, SCSC_MIFINTR_TARGET_M4);
else
/* Trigger the interrupt in the mailbox */
scsc_service_mifintrbit_bit_set(bt_service.service,
bt_service.bsmhcp_protocol->header.ap_to_fg_int_src, SCSC_MIFINTR_TARGET_R4);
} else {
/* Transfer ring full. Only happens if the user attempt to send more ACL data packets than
* available credits */
SCSC_TAG_ERR(BT_H4, "ACL_DATA_PKT - No room in transfer ring (tr_write=%u, tr_read=%u)\n",
tr_write, tr_read);
atomic_inc(&bt_service.error_count);
count = -EIO;
}
return count;
}
#ifdef CONFIG_SCSC_PRINTK
static const char *scsc_hci_evt_decode_event_code(u8 hci_event_code, u8 hci_ulp_sub_code)
{
const char *ret = "NA";
switch (hci_event_code) {
HCI_EV_DECODE(HCI_EV_INQUIRY_COMPLETE);
HCI_EV_DECODE(HCI_EV_INQUIRY_RESULT);
HCI_EV_DECODE(HCI_EV_CONN_COMPLETE);
HCI_EV_DECODE(HCI_EV_CONN_REQUEST);
HCI_EV_DECODE(HCI_EV_DISCONNECT_COMPLETE);
HCI_EV_DECODE(HCI_EV_AUTH_COMPLETE);
HCI_EV_DECODE(HCI_EV_REMOTE_NAME_REQ_COMPLETE);
HCI_EV_DECODE(HCI_EV_ENCRYPTION_CHANGE);
HCI_EV_DECODE(HCI_EV_CHANGE_CONN_LINK_KEY_COMPLETE);
HCI_EV_DECODE(HCI_EV_MASTER_LINK_KEY_COMPLETE);
HCI_EV_DECODE(HCI_EV_READ_REM_SUPP_FEATURES_COMPLETE);
HCI_EV_DECODE(HCI_EV_READ_REMOTE_VER_INFO_COMPLETE);
HCI_EV_DECODE(HCI_EV_QOS_SETUP_COMPLETE);
HCI_EV_DECODE(HCI_EV_COMMAND_COMPLETE);
HCI_EV_DECODE(HCI_EV_COMMAND_STATUS);
HCI_EV_DECODE(HCI_EV_HARDWARE_ERROR);
HCI_EV_DECODE(HCI_EV_FLUSH_OCCURRED);
HCI_EV_DECODE(HCI_EV_ROLE_CHANGE);
HCI_EV_DECODE(HCI_EV_NUMBER_COMPLETED_PKTS);
HCI_EV_DECODE(HCI_EV_MODE_CHANGE);
HCI_EV_DECODE(HCI_EV_RETURN_LINK_KEYS);
HCI_EV_DECODE(HCI_EV_PIN_CODE_REQ);
HCI_EV_DECODE(HCI_EV_LINK_KEY_REQ);
HCI_EV_DECODE(HCI_EV_LINK_KEY_NOTIFICATION);
HCI_EV_DECODE(HCI_EV_LOOPBACK_COMMAND);
HCI_EV_DECODE(HCI_EV_DATA_BUFFER_OVERFLOW);
HCI_EV_DECODE(HCI_EV_MAX_SLOTS_CHANGE);
HCI_EV_DECODE(HCI_EV_READ_CLOCK_OFFSET_COMPLETE);
HCI_EV_DECODE(HCI_EV_CONN_PACKET_TYPE_CHANGED);
HCI_EV_DECODE(HCI_EV_QOS_VIOLATION);
HCI_EV_DECODE(HCI_EV_PAGE_SCAN_MODE_CHANGE);
HCI_EV_DECODE(HCI_EV_PAGE_SCAN_REP_MODE_CHANGE);
HCI_EV_DECODE(HCI_EV_FLOW_SPEC_COMPLETE);
HCI_EV_DECODE(HCI_EV_INQUIRY_RESULT_WITH_RSSI);
HCI_EV_DECODE(HCI_EV_READ_REM_EXT_FEATURES_COMPLETE);
HCI_EV_DECODE(HCI_EV_FIXED_ADDRESS);
HCI_EV_DECODE(HCI_EV_ALIAS_ADDRESS);
HCI_EV_DECODE(HCI_EV_GENERATE_ALIAS_REQ);
HCI_EV_DECODE(HCI_EV_ACTIVE_ADDRESS);
HCI_EV_DECODE(HCI_EV_ALLOW_PRIVATE_PAIRING);
HCI_EV_DECODE(HCI_EV_ALIAS_ADDRESS_REQ);
HCI_EV_DECODE(HCI_EV_ALIAS_NOT_RECOGNISED);
HCI_EV_DECODE(HCI_EV_FIXED_ADDRESS_ATTEMPT);
HCI_EV_DECODE(HCI_EV_SYNC_CONN_COMPLETE);
HCI_EV_DECODE(HCI_EV_SYNC_CONN_CHANGED);
HCI_EV_DECODE(HCI_EV_SNIFF_SUB_RATE);
HCI_EV_DECODE(HCI_EV_EXTENDED_INQUIRY_RESULT);
HCI_EV_DECODE(HCI_EV_ENCRYPTION_KEY_REFRESH_COMPLETE);
HCI_EV_DECODE(HCI_EV_IO_CAPABILITY_REQUEST);
HCI_EV_DECODE(HCI_EV_IO_CAPABILITY_RESPONSE);
HCI_EV_DECODE(HCI_EV_USER_CONFIRMATION_REQUEST);
HCI_EV_DECODE(HCI_EV_USER_PASSKEY_REQUEST);
HCI_EV_DECODE(HCI_EV_REMOTE_OOB_DATA_REQUEST);
HCI_EV_DECODE(HCI_EV_SIMPLE_PAIRING_COMPLETE);
HCI_EV_DECODE(HCI_EV_LST_CHANGE);
HCI_EV_DECODE(HCI_EV_ENHANCED_FLUSH_COMPLETE);
HCI_EV_DECODE(HCI_EV_USER_PASSKEY_NOTIFICATION);
HCI_EV_DECODE(HCI_EV_KEYPRESS_NOTIFICATION);
HCI_EV_DECODE(HCI_EV_REM_HOST_SUPPORTED_FEATURES);
HCI_EV_DECODE(HCI_EV_TRIGGERED_CLOCK_CAPTURE);
HCI_EV_DECODE(HCI_EV_SYNCHRONIZATION_TRAIN_COMPLETE);
HCI_EV_DECODE(HCI_EV_SYNCHRONIZATION_TRAIN_RECEIVED);
HCI_EV_DECODE(HCI_EV_CSB_RECEIVE);
HCI_EV_DECODE(HCI_EV_CSB_TIMEOUT);
HCI_EV_DECODE(HCI_EV_TRUNCATED_PAGE_COMPLETE);
HCI_EV_DECODE(HCI_EV_SLAVE_PAGE_RESPONSE_TIMEOUT);
HCI_EV_DECODE(HCI_EV_CSB_CHANNEL_MAP_CHANGE);
HCI_EV_DECODE(HCI_EV_INQUIRY_RESPONSE_NOTIFICATION);
HCI_EV_DECODE(HCI_EV_AUTHENTICATED_PAYLOAD_TIMEOUT_EXPIRED);
case HCI_EV_ULP:
{
switch (hci_ulp_sub_code) {
HCI_EV_DECODE(HCI_EV_ULP_CONNECTION_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_ADVERTISING_REPORT);
HCI_EV_DECODE(HCI_EV_ULP_CONNECTION_UPDATE_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_READ_REMOTE_USED_FEATURES_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_LONG_TERM_KEY_REQUEST);
HCI_EV_DECODE(HCI_EV_ULP_REMOTE_CONNECTION_PARAMETER_REQUEST);
HCI_EV_DECODE(HCI_EV_ULP_DATA_LENGTH_CHANGE);
HCI_EV_DECODE(HCI_EV_ULP_READ_LOCAL_P256_PUB_KEY_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_GENERATE_DHKEY_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_ENHANCED_CONNECTION_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_DIRECT_ADVERTISING_REPORT);
HCI_EV_DECODE(HCI_EV_ULP_PHY_UPDATE_COMPLETE);
HCI_EV_DECODE(HCI_EV_ULP_USED_CHANNEL_SELECTION);
}
break;
}
}
return ret;
}
#endif
static ssize_t scsc_iq_report_evt_read(char __user *buf, size_t len)
{
ssize_t consumed = 0;
ssize_t ret = 0;
/* Calculate the amount of data that can be transferred */
len = min(h4_iq_report_evt_len - bt_service.read_offset, len);
SCSC_TAG_DEBUG(BT_H4, "SCSC_IQ_REPORT_EVT_READ: td(h4_iq_len=%u offset=%u)\n",
h4_iq_report_evt_len,
bt_service.read_offset);
/* Copy the data to the user buffer */
ret = copy_to_user(buf, &h4_iq_report_evt[bt_service.read_offset], len);
if (ret == 0) {
/* All good - Update our consumed information */
bt_service.read_offset += len;
consumed = len;
SCSC_TAG_DEBUG(BT_H4, "SCSC_IQ_REPORT_EVT_READ: (offset=%u consumed: %u)\n",
bt_service.read_offset,
consumed);
/* Have all data been copied to the userspace buffer */
if (bt_service.read_offset == h4_iq_report_evt_len) {
/* All good - read operation is completed */
bt_service.read_offset = 0;
bt_service.read_operation = BT_READ_OP_NONE;
}
} else {
SCSC_TAG_ERR(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
return ret == 0 ? consumed : ret;
}
static ssize_t scsc_hci_evt_read(char __user *buf, size_t len)
{
struct BSMHCP_TD_HCI_EVT *td = &bt_service.bsmhcp_protocol->hci_evt_transfer_ring[bt_service.read_index];
u8 h4_hci_event_header = HCI_EVENT_PKT;
ssize_t consumed = 0;
ssize_t ret = 0;
SCSC_TAG_DEBUG(BT_H4, "td (length=%u, hci_connection_handle=0x%03x, event_type=%u), len=%zu, read_offset=%zu\n",
td->length, td->hci_connection_handle, td->event_type, len, bt_service.read_offset);
/* Is this the start of the copy operation */
if (0 == bt_service.read_offset) {
SCSC_TAG_DEBUG(BT_RX, "HCI Event [type=%s (0x%02x), length=%u]\n",
scsc_hci_evt_decode_event_code(td->data[0], td->data[2]), td->data[0], td->data[1]);
if (td->data[1] + HCI_EVENT_HEADER_LENGTH != td->length) {
SCSC_TAG_ERR(BT_H4, "Firmware sent invalid HCI event\n");
atomic_inc(&bt_service.error_count);
ret = -EFAULT;
}
/* Store the H4 header in the user buffer */
ret = copy_to_user(buf, &h4_hci_event_header, sizeof(h4_hci_event_header));
if (0 == ret) {
/* All good - Update our consumed information */
consumed = sizeof(h4_hci_event_header);
bt_service.read_offset = sizeof(h4_hci_event_header);
} else {
SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
}
/* Can more data be put into the userspace buffer */
if (0 == ret && (len - consumed)) {
/* Calculate the amount of data that can be transferred */
len = min((td->length - (bt_service.read_offset - sizeof(h4_hci_event_header))), (len - consumed));
/* Copy the data to the user buffer */
ret = copy_to_user(&buf[consumed], &td->data[bt_service.read_offset - sizeof(h4_hci_event_header)], len);
if (0 == ret) {
/* All good - Update our consumed information */
bt_service.read_offset += len;
consumed += len;
/* Have all data been copied to the userspace buffer */
if (bt_service.read_offset == (sizeof(h4_hci_event_header) + td->length)) {
/* All good - read operation is completed */
bt_service.read_offset = 0;
bt_service.read_operation = BT_READ_OP_NONE;
}
} else {
SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
}
return 0 == ret ? consumed : ret;
}
static ssize_t scsc_hci_evt_error_read(char __user *buf, size_t len)
{
ssize_t ret;
ssize_t consumed = 0;
/* Calculate the amount of data that can be transferred */
len = min(sizeof(h4_hci_event_hardware_error) - bt_service.read_offset, len);
/* Copy the data to the user buffer */
ret = copy_to_user(buf, &h4_hci_event_hardware_error[bt_service.read_offset], len);
if (0 == ret) {
/* All good - Update our consumed information */
bt_service.read_offset += len;
consumed = len;
/* Have all data been copied to the userspace buffer */
if (bt_service.read_offset == sizeof(h4_hci_event_hardware_error)) {
/* All good - read operation is completed */
bt_service.read_offset = 0;
bt_service.read_operation = BT_READ_OP_NONE;
#ifdef CONFIG_SCSC_LOG_COLLECTION
if (bt_service.recovery_level == 0)
scsc_log_collector_schedule_collection(
SCSC_LOG_HOST_BT,
SCSC_LOG_HOST_BT_REASON_HCI_ERROR);
#endif
}
} else {
SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
return 0 == ret ? consumed : ret;
}
static ssize_t scsc_acl_read(char __user *buf, size_t len)
{
struct BSMHCP_TD_ACL_RX *td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.read_index];
ssize_t consumed = 0;
size_t copy_len = 0;
ssize_t ret = 0;
SCSC_TAG_DEBUG(BT_H4, "td (length=%u, hci_connection_handle=0x%03x, packet_boundary=%u, broadcast_flag=%u), len=%zu, read_offset=%zu\n",
td->length, td->hci_connection_handle, td->packet_boundary, td->broadcast_flag, len, bt_service.read_offset);
/* Has the header been copied to userspace */
if (bt_service.read_offset < sizeof(h4_acl_header)) {
/* Calculate the amount of data that can be transferred */
copy_len = min(sizeof(h4_acl_header) - bt_service.read_offset, len);
/* Fully generate the H4 header + ACL data header regardless of the available amount of user memory */
h4_acl_header[0] = HCI_ACLDATA_PKT;
h4_acl_header[1] = td->hci_connection_handle & 0x00ff;
h4_acl_header[2] = ((td->hci_connection_handle & 0x0f00) >> 8) | ((td->packet_boundary & 0x03) << 4) | ((td->broadcast_flag & 0x03) << 6);
h4_acl_header[3] = td->length & 0x00ff;
h4_acl_header[4] = (td->length & 0xff00) >> 8;
/* Copy the H4 header + ACL data header to the userspace buffer */
ret = copy_to_user(buf, &h4_acl_header[bt_service.read_offset], copy_len);
if (0 == ret) {
/* All good - Update our consumed information */
consumed = copy_len;
bt_service.read_offset += copy_len;
} else {
SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
}
/* Can more data be put into the userspace buffer */
if (0 == ret && bt_service.read_offset >= sizeof(h4_acl_header) && (len - consumed)) {
/* Calculate the amount of data that can be transferred */
copy_len = min((td->length - (bt_service.read_offset - sizeof(h4_acl_header))), (len - consumed));
/* Copy the data to the user buffer */
ret = copy_to_user(&buf[consumed], &td->data[bt_service.read_offset - sizeof(h4_acl_header)], copy_len);
if (0 == ret) {
/* All good - Update our consumed information */
bt_service.read_offset += copy_len;
consumed += copy_len;
/* Have all data been copied to the userspace buffer */
if (bt_service.read_offset == (sizeof(h4_acl_header) + td->length)) {
/* All good - read operation is completed */
bt_service.read_offset = 0;
bt_service.read_operation = BT_READ_OP_NONE;
/* Only supported on start packet*/
if (td->packet_boundary == HCI_ACL_PACKET_BOUNDARY_START_FLUSH)
/* The "false" argument is to tell the detection that this is RX */
scsc_avdtp_detect_rxtx(td->hci_connection_handle, td->data, td->length, false);
}
} else {
SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
}
SCSC_TAG_DEBUG(BT_H4, "read_offset=%zu, consumed=%zu, ret=%zd, len=%zu, copy_len=%zu\n",
bt_service.read_offset, consumed, ret, len, copy_len);
return 0 == ret ? consumed : ret;
}
static ssize_t scsc_acl_credit(char __user *buf, size_t len)
{
ssize_t consumed = 0;
ssize_t ret = 0;
SCSC_TAG_DEBUG(BT_H4, "len=%zu, read_offset=%zu\n", len, bt_service.read_offset);
/* Calculate the amount of data that can be transferred */
len = min(h4_hci_event_ncp_header_len - bt_service.read_offset, len);
/* Copy the data to the user buffer */
ret = copy_to_user(buf, &h4_hci_event_ncp_header[bt_service.read_offset], len);
if (0 == ret) {
/* All good - Update our consumed information */
bt_service.read_offset += len;
consumed = len;
/* Have all data been copied to the userspace buffer */
if (bt_service.read_offset == h4_hci_event_ncp_header_len) {
/* All good - read operation is completed */
bt_service.read_offset = 0;
bt_service.read_operation = BT_READ_OP_NONE;
}
} else {
SCSC_TAG_WARNING(BT_H4, "copy_to_user returned: %zu\n", ret);
ret = -EACCES;
}
return 0 == ret ? consumed : ret;
}
static ssize_t scsc_bt_shm_h4_read_continue(char __user *buf, size_t len)
{
ssize_t ret = 0;
/* Is a HCI event read operation ongoing */
if (BT_READ_OP_HCI_EVT == bt_service.read_operation) {
SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_HCI_EVT\n");
/* Copy data into the userspace buffer */
ret = scsc_hci_evt_read(buf, len);
if (BT_READ_OP_NONE == bt_service.read_operation)
/* All done - increase the read pointer and continue
* unless this was an out-of-order read for the queue
* sync helper */
if (bt_service.read_index == bt_service.mailbox_hci_evt_read)
BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE);
/* Is a ACL data read operation ongoing */
} else if (BT_READ_OP_ACL_DATA == bt_service.read_operation) {
SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_ACL_DATA\n");
/* Copy data into the userspace buffer */
ret = scsc_acl_read(buf, len);
if (BT_READ_OP_NONE == bt_service.read_operation)
/* All done - increase the read pointer and continue */
BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE);
/* Is a ACL credit update operation ongoing */
} else if (BT_READ_OP_ACL_CREDIT == bt_service.read_operation) {
SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_ACL_CREDIT\n");
/* Copy data into the userspace buffer */
ret = scsc_acl_credit(buf, len);
} else if (bt_service.read_operation == BT_READ_OP_IQ_REPORT) {
SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_IQ_REPORT\n");
/* Copy data into the userspace buffer */
ret = scsc_iq_report_evt_read(buf, len);
if (bt_service.read_operation == BT_READ_OP_NONE)
/* All done - increase the read pointer and continue */
BSMHCP_INCREASE_INDEX(bt_service.mailbox_iq_report_read, BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE);
} else if (BT_READ_OP_HCI_EVT_ERROR == bt_service.read_operation) {
SCSC_TAG_ERR(BT_H4, "BT_READ_OP_HCI_EVT_ERROR\n");
/* Copy data into the userspace buffer */
ret = scsc_hci_evt_error_read(buf, len);
if (BT_READ_OP_NONE == bt_service.read_operation)
/* All done - set the stop condition */
bt_service.read_operation = BT_READ_OP_STOP;
} else if (BT_READ_OP_STOP == bt_service.read_operation)
ret = -EIO;
return ret;
}
static ssize_t scsc_bt_shm_h4_read_iq_report_evt(char __user *buf, size_t len)
{
ssize_t ret = 0;
ssize_t consumed = 0;
if (bt_service.read_operation == BT_READ_OP_NONE &&
bt_service.mailbox_iq_report_read != bt_service.mailbox_iq_report_write) {
struct BSMHCP_TD_IQ_REPORTING_EVT *td =
&bt_service.bsmhcp_protocol->iq_reporting_transfer_ring[bt_service.mailbox_iq_report_read];
u32 index = 0;
u32 j = 0;
u32 i;
if (!bt_service.iq_reports_enabled)
{
BSMHCP_INCREASE_INDEX(bt_service.mailbox_iq_report_read,
BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE);
}
else
{
memset(h4_iq_report_evt, 0, sizeof(h4_iq_report_evt));
h4_iq_report_evt_len = 0;
h4_iq_report_evt[index++] = HCI_EVENT_PKT;
h4_iq_report_evt[index++] = 0x3E;
index++; /* Leaving room for total length of params */
h4_iq_report_evt[index++] = td->subevent_code;
if (td->subevent_code == HCI_LE_CONNECTIONLESS_IQ_REPORT_EVENT_SUB_CODE) {
/* LE Connectionless IQ Report Event*/
h4_iq_report_evt[index++] = td->sync_handle & 0xFF;
h4_iq_report_evt[index++] = (td->sync_handle >> 8) & 0xFF;
} else if (td->subevent_code == HCI_LE_CONNECTION_IQ_REPORT_EVENT_SUB_CODE) {
/* LE connection IQ Report Event */
h4_iq_report_evt[index++] = td->connection_handle & 0xFF;
h4_iq_report_evt[index++] = (td->connection_handle >> 8) & 0xFF;
h4_iq_report_evt[index++] = td->rx_phy;
}
h4_iq_report_evt[index++] = td->channel_index;
h4_iq_report_evt[index++] = td->rssi & 0xFF;
h4_iq_report_evt[index++] = (td->rssi >> 8) & 0xFF;
h4_iq_report_evt[index++] = td->rssi_antenna_id;
h4_iq_report_evt[index++] = td->cte_type;
h4_iq_report_evt[index++] = td->slot_durations;
h4_iq_report_evt[index++] = td->packet_status;
h4_iq_report_evt[index++] = td->event_count & 0xFF;
h4_iq_report_evt[index++] = (td->event_count >> 8) & 0xFF;
h4_iq_report_evt[index++] = td->sample_count;
/* Total length of hci event */
h4_iq_report_evt_len = index + (2 * td->sample_count);
/* Total length of hci event parameters */
h4_iq_report_evt[2] = h4_iq_report_evt_len - 3;
for (i = 0; i < td->sample_count; i++) {
h4_iq_report_evt[index + i] = td->data[j++];
h4_iq_report_evt[(index + td->sample_count) + i] = td->data[j++];
}
bt_service.read_operation = BT_READ_OP_IQ_REPORT;
bt_service.read_index = bt_service.mailbox_iq_report_read;
ret = scsc_iq_report_evt_read(&buf[consumed], len - consumed);
if (ret > 0) {
/* All good - Update our consumed information */
consumed += ret;
ret = 0;
/**
* Update the index if all the data could be copied to the userspace
* buffer otherwise stop processing the HCI events
*/
if (bt_service.read_operation == BT_READ_OP_NONE)
BSMHCP_INCREASE_INDEX(bt_service.mailbox_iq_report_read,
BSMHCP_TRANSFER_RING_IQ_REPORT_SIZE);
}
}
}
return ret == 0 ? consumed : ret;
}
static ssize_t scsc_bt_shm_h4_read_hci_evt(char __user *buf, size_t len)
{
ssize_t ret = 0;
ssize_t consumed = 0;
while (BT_READ_OP_NONE == bt_service.read_operation && 0 == ret && !bt_service.hci_event_paused && bt_service.mailbox_hci_evt_read != bt_service.mailbox_hci_evt_write) {
struct BSMHCP_TD_HCI_EVT *td = &bt_service.bsmhcp_protocol->hci_evt_transfer_ring[bt_service.mailbox_hci_evt_read];
/* This event has already been processed - skip it */
if (bt_service.processed[bt_service.mailbox_hci_evt_read]) {
bt_service.processed[bt_service.mailbox_hci_evt_read] = false;
BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE);
continue;
}
/* A connection event has been detected by the firmware */
if (td->event_type == BSMHCP_EVENT_TYPE_CONNECTED) {
/* Sanity check of the HCI connection handle */
if (td->hci_connection_handle >= SCSC_BT_CONNECTION_INFO_MAX) {
SCSC_TAG_ERR(BT_H4, "connection handle is beyond max (hci_connection_handle=0x%03x)\n",
td->hci_connection_handle);
atomic_inc(&bt_service.error_count);
break;
}
SCSC_TAG_DEBUG(BT_H4, "connected (hci_connection_handle=0x%03x, state=%u)\n",
td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state);
/* Update the connection table to mark it as active */
bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_ACTIVE;
bt_service.connection_handle_list[td->hci_connection_handle].length = 0;
/* ACL data processing can now continue */
bt_service.acldata_paused = false;
/* A disconnection event has been detected by the firmware */
} else if (td->event_type == BSMHCP_EVENT_TYPE_DISCONNECTED) {
SCSC_TAG_DEBUG(BT_H4, "disconnected (hci_connection_handle=0x%03x, state=%u)\n",
td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state);
/* If this ACL connection had an avdtp stream, mark it gone and interrupt the bg */
if (scsc_avdtp_detect_reset_connection_handle(td->hci_connection_handle))
wmb();
/* If the connection is marked as active the ACL disconnect packet hasn't yet arrived */
if (CONNECTION_ACTIVE == bt_service.connection_handle_list[td->hci_connection_handle].state) {
/* Pause the HCI event procssing until the ACL disconnect packet arrives */
bt_service.hci_event_paused = true;
break;
}
/* Firmware does not have more ACL data - Mark the connection as inactive */
bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_NONE;
} else if (td->event_type == BSMHCP_EVENT_TYPE_IQ_REPORT_ENABLED) {
bt_service.iq_reports_enabled = true;
} else if (td->event_type == BSMHCP_EVENT_TYPE_IQ_REPORT_DISABLED) {
bt_service.iq_reports_enabled = false;
}
/* Start a HCI event copy to userspace */
bt_service.read_operation = BT_READ_OP_HCI_EVT;
bt_service.read_index = bt_service.mailbox_hci_evt_read;
ret = scsc_hci_evt_read(&buf[consumed], len - consumed);
if (ret > 0) {
/* All good - Update our consumed information */
consumed += ret;
ret = 0;
/* Update the index if all the data could be copied to the userspace buffer otherwise stop processing the HCI events */
if (BT_READ_OP_NONE == bt_service.read_operation)
BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE);
else
break;
}
}
return 0 == ret ? consumed : ret;
}
/**
* Start the acl data to userspace copy
*
* Acl processing should be stopped if either unable to read a complete packet
* or a complete packet is read and BlueZ is enabled
*
* @param[out] ret result of read operations written to here
* @param[in,out] consumed read bytes added to this
*
* @return true if ACL data processing should stop
*/
static bool scsc_bt_shm_h4_acl_start_copy(char __user *buf,
size_t len,
ssize_t *ret,
ssize_t *consumed)
{
bt_service.read_operation = BT_READ_OP_ACL_DATA;
bt_service.read_index = bt_service.mailbox_acl_rx_read;
*ret = scsc_acl_read(&buf[*consumed], len - *consumed);
if (*ret <= 0)
return *ret < 0; /* Break the loop for errors */
/* Update our consumed information */
*consumed += *ret;
*ret = 0;
/* Stop processing if all the data could not be copied to userspace */
if (bt_service.read_operation != BT_READ_OP_NONE)
return true;
BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE);
return false;
}
static ssize_t scsc_bt_shm_h4_read_acl_data(char __user *buf, size_t len)
{
ssize_t ret = 0;
ssize_t consumed = 0;
while (bt_service.read_operation == BT_READ_OP_NONE &&
!bt_service.acldata_paused &&
bt_service.mailbox_acl_rx_read != bt_service.mailbox_acl_rx_write) {
struct BSMHCP_TD_ACL_RX *td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.mailbox_acl_rx_read];
/* Bypass packet inspection and connection handling for data dump */
if (SCSC_BT_ACL_RAW == (td->hci_connection_handle & SCSC_BT_ACL_RAW_MASK)) {
if (scsc_bt_shm_h4_acl_start_copy(buf, len, &ret, &consumed))
break;
}
/* Sanity check of the HCI connection handle */
if (td->hci_connection_handle >= SCSC_BT_CONNECTION_INFO_MAX) {
SCSC_TAG_ERR(BT_H4, "connection handle is beyond max (hci_connection_handle=0x%03x)\n",
td->hci_connection_handle);
atomic_inc(&bt_service.error_count);
break;
}
/* Only process ACL data if the connection is marked active aka a HCI connection complete event has arrived */
if (CONNECTION_ACTIVE == bt_service.connection_handle_list[td->hci_connection_handle].state) {
/* Is this the final packet for the indicated ACL connection */
if (td->disconnected) {
SCSC_TAG_DEBUG(BT_H4, "ACL disconnected (hci_connection_handle=0x%03x, state=%u)\n",
td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state);
/* Update the connection table to mark it as disconnected */
bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_DISCONNECTED;
/* Clear the HCI event processing to allow for the HCI disconnect event to be transferred to userspace */
bt_service.hci_event_paused = false;
/* Update the read pointer */
BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE);
} else {
if (scsc_bt_shm_h4_acl_start_copy(buf, len, &ret, &consumed))
break;
}
/* If the connection state is inactive the HCI connection complete information hasn't yet arrived. Stop processing ACL data */
} else if (CONNECTION_NONE == bt_service.connection_handle_list[td->hci_connection_handle].state) {
SCSC_TAG_DEBUG(BT_H4, "ACL empty (hci_connection_handle=0x%03x, state=%u)\n",
td->hci_connection_handle, bt_service.connection_handle_list[td->hci_connection_handle].state);
bt_service.acldata_paused = true;
/* If the connection state is disconnection the firmware sent ACL after the ACL disconnect packet which is an FW error */
} else {
SCSC_TAG_ERR(BT_H4, "ACL data received after disconnected indication\n");
atomic_inc(&bt_service.error_count);
break;
}
}
return 0 == ret ? consumed : ret;
}
static ssize_t scsc_bt_shm_h4_read_acl_credit(char __user *buf, size_t len)
{
ssize_t ret = 0;
ssize_t consumed = 0;
if (BT_READ_OP_NONE == bt_service.read_operation && 0 == ret &&
bt_service.mailbox_acl_free_read != bt_service.mailbox_acl_free_write) {
u32 entries = 0;
u32 index;
memset(h4_hci_event_ncp_header, 0, sizeof(h4_hci_event_ncp_header));
while (bt_service.mailbox_acl_free_read != bt_service.mailbox_acl_free_write) {
struct BSMHCP_TD_ACL_TX_FREE *td =
&bt_service.bsmhcp_protocol->acl_tx_free_transfer_ring[bt_service.mailbox_acl_free_read];
uint16_t sanitized_conn_handle = td->hci_connection_handle & SCSC_BT_ACL_HANDLE_MASK;
if (bt_service.connection_handle_list[sanitized_conn_handle].state == CONNECTION_ACTIVE) {
SCSC_TAG_DEBUG(BT_H4, "td (hci_connection_handle=0x%03x, buffer_index=%u)\n",
td->hci_connection_handle & SCSC_BT_ACL_HANDLE_MASK, td->buffer_index);
for (index = 0; index < BSMHCP_TRANSFER_RING_ACL_COUNT; index++) {
if (0 == h4_hci_credit_entries[index].hci_connection_handle) {
h4_hci_credit_entries[index].hci_connection_handle =
td->hci_connection_handle;
h4_hci_credit_entries[index].credits = 1;
entries++;
break;
} else if ((h4_hci_credit_entries[index].hci_connection_handle &
SCSC_BT_ACL_HANDLE_MASK) == sanitized_conn_handle) {
h4_hci_credit_entries[index].hci_connection_handle =
td->hci_connection_handle;
h4_hci_credit_entries[index].credits++;
break;
}
}
} else
SCSC_TAG_WARNING(BT_H4,
"No active connection ((hci_connection_handle=0x%03x)\n",
sanitized_conn_handle);
BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_free_read, BSMHCP_TRANSFER_RING_ACL_SIZE);
}
if (entries) {
/* Fill the number of completed packets data into the temp buffer */
h4_hci_event_ncp_header[0] = HCI_EVENT_PKT;
h4_hci_event_ncp_header[1] = HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS_EVENT;
h4_hci_event_ncp_header[2] = 1 + (4 * entries); /* Parameter length */
h4_hci_event_ncp_header[3] = entries; /* Number_of_Handles */
h4_hci_event_ncp_header_len = 4 + (4 * entries);
/* Start a ACL credit copy to userspace */
bt_service.read_operation = BT_READ_OP_ACL_CREDIT;
bt_service.read_index = bt_service.mailbox_acl_free_read;
ret = scsc_acl_credit(&buf[consumed], len - consumed);
if (ret > 0) {
/* Update our consumed information */
consumed += ret;
ret = 0;
}
}
}
return 0 == ret ? consumed : ret;
}
ssize_t scsc_bt_shm_h4_queue_sync_helper(char __user *buf, size_t len)
{
ssize_t ret = 0;
bool found = false;
u32 mailbox_hci_evt_read = bt_service.mailbox_hci_evt_read;
/* If both the HCI event transfer ring and ACL data transfer ring has been
* paused the entire HCI event transfer ring is scanned for the presence
* of the connected indication. Once present this is transferred to the host
* stack and marked as processed. This will unlock the hci event processing */
while (bt_service.hci_event_paused && bt_service.acldata_paused) {
struct BSMHCP_TD_ACL_RX *acl_td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.mailbox_acl_rx_read];
while (mailbox_hci_evt_read != bt_service.mailbox_hci_evt_write) {
struct BSMHCP_TD_HCI_EVT *td = &bt_service.bsmhcp_protocol->hci_evt_transfer_ring[mailbox_hci_evt_read];
if (td->event_type & BSMHCP_EVENT_TYPE_CONNECTED && acl_td->hci_connection_handle == td->hci_connection_handle) {
/* Update the connection table to mark it as active */
bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_ACTIVE;
bt_service.connection_handle_list[td->hci_connection_handle].length = 0;
/* ACL data processing can now continue */
bt_service.acldata_paused = false;
/* Mark the event as processed */
bt_service.processed[mailbox_hci_evt_read] = true;
/* Indicate the event have been found */
found = true;
/* Start a HCI event copy to userspace */
bt_service.read_operation = BT_READ_OP_HCI_EVT;
bt_service.read_index = mailbox_hci_evt_read;
ret = scsc_hci_evt_read(buf, len);
break;
}
BSMHCP_INCREASE_INDEX(mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE);
}
if (!found) {
ret = wait_event_interruptible_timeout(bt_service.read_wait,
((mailbox_hci_evt_read != bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write ||
0 != atomic_read(&bt_service.error_count) ||
bt_service.bsmhcp_protocol->header.panic_deathbed_confession)), HZ);
if (0 == ret) {
SCSC_TAG_ERR(BT_H4, "firmware didn't send the connected event within the given timeframe\n");
atomic_inc(&bt_service.error_count);
break;
} else if (1 != ret) {
SCSC_TAG_INFO(BT_H4, "user interrupt\n");
break;
}
}
}
return ret;
}
ssize_t scsc_bt_shm_h4_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
ssize_t consumed = 0;
ssize_t ret = 0;
ssize_t res;
bool gen_bg_int = false;
bool gen_fg_int = false;
if (len == 0)
return 0;
/* Special handling in case read is called after service has closed */
if (!bt_service.service_started)
return -EIO;
/* Only 1 reader is allowed */
if (1 != atomic_inc_return(&bt_service.h4_readers)) {
atomic_dec(&bt_service.h4_readers);
return -EIO;
}
/* Update the cached variables with the non-cached variables */
bt_service.mailbox_hci_evt_write = bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write;
bt_service.mailbox_acl_rx_write = bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write;
bt_service.mailbox_acl_free_write = bt_service.bsmhcp_protocol->header.mailbox_acl_free_write;
bt_service.mailbox_iq_report_write = bt_service.bsmhcp_protocol->header.mailbox_iq_report_write;
/* Only generate the HCI hardware error event if any pending operation has been completed
* and the event hasn't already neen sent. This check assume the main while loop will exit
* on a completed operation in the next section */
if (0 != atomic_read(&bt_service.error_count) && BT_READ_OP_NONE == bt_service.read_operation)
bt_service.read_operation = BT_READ_OP_HCI_EVT_ERROR;
/* put the remaining data from the transfer ring into the available userspace buffer */
if (BT_READ_OP_NONE != bt_service.read_operation) {
ret = scsc_bt_shm_h4_read_continue(buf, len);
/* Update the consumed variable in case a operation was ongoing */
if (0 < ret) {
consumed = ret;
ret = 0;
}
}
/* Main loop - Can only be entered when no operation is present on entering this function
* or no hardware error has been detected. It loops until data has been placed in the
* userspace buffer or an error has been detected */
while (0 == atomic_read(&bt_service.error_count) && 0 == consumed) {
/* If both the HCI event processing and ACL data processing has been disabled this function
* helps exit this condition by scanning the HCI event queue for the connection established
* event and return it to userspace */
ret = scsc_bt_shm_h4_queue_sync_helper(buf, len);
if (ret > 0) {
consumed = ret;
break;
}
/* Does any of the read/write pairs differs */
if ((bt_service.mailbox_hci_evt_read == bt_service.mailbox_hci_evt_write || bt_service.hci_event_paused) &&
(bt_service.mailbox_acl_rx_read == bt_service.mailbox_acl_rx_write || bt_service.acldata_paused) &&
bt_service.mailbox_acl_free_read == bt_service.mailbox_acl_free_write &&
bt_service.mailbox_iq_report_read == bt_service.mailbox_iq_report_write &&
0 == atomic_read(&bt_service.error_count) &&
0 == bt_service.bsmhcp_protocol->header.panic_deathbed_confession) {
/* Don't wait if in NONBLOCK mode */
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
break;
}
/* All read/write pairs are identical - wait for the firmware. The conditional
* check is used to verify that a read/write pair has actually changed */
ret = wait_event_interruptible(
bt_service.read_wait,
((bt_service.mailbox_hci_evt_read !=
bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write &&
!bt_service.hci_event_paused) ||
(bt_service.mailbox_acl_rx_read !=
bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write &&
!bt_service.acldata_paused) ||
(bt_service.mailbox_acl_free_read !=
bt_service.bsmhcp_protocol->header.mailbox_acl_free_write) ||
(bt_service.mailbox_iq_report_read !=
bt_service.bsmhcp_protocol->header.mailbox_iq_report_write) ||
atomic_read(&bt_service.error_count) != 0 ||
bt_service.bsmhcp_protocol->header.panic_deathbed_confession));
/* Has an error been detected elsewhere in the driver then just return from this function */
if (0 != atomic_read(&bt_service.error_count))
break;
/* Any failures is handled by the userspace application */
if (ret)
break;
/* Refresh our write indexes before starting to process the protocol */
bt_service.mailbox_hci_evt_write = bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write;
bt_service.mailbox_acl_rx_write = bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write;
bt_service.mailbox_acl_free_write = bt_service.bsmhcp_protocol->header.mailbox_acl_free_write;
bt_service.mailbox_iq_report_write = bt_service.bsmhcp_protocol->header.mailbox_iq_report_write;
}
SCSC_TAG_DEBUG(BT_H4, "hci_evt_read=%u, hci_evt_write=%u, acl_rx_read=%u,acl_rx_write=%u\n",
bt_service.mailbox_hci_evt_read,
bt_service.mailbox_hci_evt_write,
bt_service.mailbox_acl_rx_read,
bt_service.mailbox_acl_rx_write);
SCSC_TAG_DEBUG(BT_H4, "acl_free_read=%u, acl_free_write=%u, iq_report_read=%u iq_report_write=%u\n",
bt_service.mailbox_acl_free_read,
bt_service.mailbox_acl_free_write,
bt_service.mailbox_iq_report_read,
bt_service.mailbox_iq_report_write);
SCSC_TAG_DEBUG(BT_H4, "read_operation=%u, hci_event_paused=%u, acldata_paused=%u\n",
bt_service.read_operation, bt_service.hci_event_paused,
bt_service.acldata_paused);
while (bt_service.mailbox_acl_free_read_scan != bt_service.mailbox_acl_free_write) {
struct BSMHCP_TD_ACL_TX_FREE *td = &bt_service.bsmhcp_protocol->acl_tx_free_transfer_ring[bt_service.mailbox_acl_free_read_scan];
/* Free the buffer in the allocation table */
if (td->buffer_index < BSMHCP_DATA_BUFFER_TX_ACL_SIZE) {
bt_service.allocated[td->buffer_index] = 0;
bt_service.freed_count++;
SCSC_TAG_DEBUG(BT_TX, "ACL[CREDIT] (index=%u, buffer=%u, credits=%u)\n",
bt_service.mailbox_acl_free_read_scan,
td->buffer_index,
BSMHCP_DATA_BUFFER_TX_ACL_SIZE - (bt_service.allocated_count - bt_service.freed_count));
}
BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_free_read_scan, BSMHCP_TRANSFER_RING_ACL_SIZE);
}
/* Update the quality of service module with the number of used entries */
scsc_bt_qos_update(BSMHCP_USED_ENTRIES(bt_service.mailbox_hci_evt_write,
bt_service.mailbox_hci_evt_read,
BSMHCP_TRANSFER_RING_EVT_SIZE),
BSMHCP_USED_ENTRIES(bt_service.mailbox_acl_rx_write,
bt_service.mailbox_acl_rx_read,
BSMHCP_TRANSFER_RING_ACL_SIZE));
/* First: process any pending HCI event that needs to be sent to userspace */
res = scsc_bt_shm_h4_read_hci_evt(&buf[consumed], len - consumed);
if (res < 0) {
ret = res;
break;
}
consumed += res;
/* Second: process any pending ACL data that needs to be sent to userspace */
res = scsc_bt_shm_h4_read_acl_data(&buf[consumed], len - consumed);
if (res < 0) {
ret = res;
break;
}
consumed += res;
/* Third: process any pending ACL data that needs to be sent to userspace */
res = scsc_bt_shm_h4_read_acl_credit(&buf[consumed], len - consumed);
if (res < 0) {
ret = res;
break;
}
consumed += res;
res = scsc_bt_shm_h4_read_iq_report_evt(&buf[consumed], len - consumed);
if (res < 0) {
ret = res;
break;
}
consumed += res;
}
if (0 == ret && 0 == consumed) {
if (0 != atomic_read(&bt_service.error_count) && BT_READ_OP_NONE == bt_service.read_operation)
bt_service.read_operation = BT_READ_OP_HCI_EVT_ERROR;
if (BT_READ_OP_HCI_EVT_ERROR == bt_service.read_operation) {
SCSC_TAG_ERR(BT_H4, "BT_READ_OP_HCI_EVT_ERROR\n");
/* Copy data into the userspace buffer */
ret = scsc_hci_evt_error_read(buf, len);
if (ret > 0) {
consumed += ret;
ret = 0;
}
if (BT_READ_OP_NONE == bt_service.read_operation)
/* All done - set the stop condition */
bt_service.read_operation = BT_READ_OP_STOP;
}
}
/* If anything was read, generate the appropriate interrupt(s) */
if (bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read !=
bt_service.mailbox_hci_evt_read)
gen_bg_int = true;
if (bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read !=
bt_service.mailbox_acl_rx_read ||
bt_service.bsmhcp_protocol->header.mailbox_acl_free_read !=
bt_service.mailbox_acl_free_read)
gen_fg_int = true;
if (bt_service.bsmhcp_protocol->header.mailbox_iq_report_read !=
bt_service.mailbox_iq_report_read)
gen_fg_int = true;
/* Update the read index for all transfer rings */
bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read = bt_service.mailbox_hci_evt_read;
bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read = bt_service.mailbox_acl_rx_read;
bt_service.bsmhcp_protocol->header.mailbox_acl_free_read = bt_service.mailbox_acl_free_read;
bt_service.bsmhcp_protocol->header.mailbox_iq_report_read = bt_service.mailbox_iq_report_read;
/* Ensure the data is updating correctly in memory */
wmb();
if (gen_bg_int)
scsc_service_mifintrbit_bit_set(bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, SCSC_MIFINTR_TARGET_R4);
if (gen_fg_int) {
if (bt_service.bsmhcp_protocol->header.firmware_features & BSMHCP_FEATURE_M4_INTERRUPTS)
/* Trigger the interrupt in the mailbox */
scsc_service_mifintrbit_bit_set(bt_service.service,
bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, SCSC_MIFINTR_TARGET_M4);
else
/* Trigger the interrupt in the mailbox */
scsc_service_mifintrbit_bit_set(bt_service.service,
bt_service.bsmhcp_protocol->header.ap_to_fg_int_src, SCSC_MIFINTR_TARGET_R4);
}
if (BT_READ_OP_STOP != bt_service.read_operation)
SCSC_TAG_DEBUG(BT_H4, "hci_evt_read=%u, acl_rx_read=%u, acl_free_read=%u, read_operation=%u, consumed=%zd, ret=%zd\n",
bt_service.mailbox_hci_evt_read, bt_service.mailbox_acl_rx_read, bt_service.mailbox_acl_free_read, bt_service.read_operation, consumed, ret);
/* Decrease the H4 readers counter */
atomic_dec(&bt_service.h4_readers);
return 0 == ret ? consumed : ret;
}
ssize_t scsc_bt_shm_h4_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
size_t length;
size_t hci_pkt_len;
ssize_t written = 0;
ssize_t ret = 0;
SCSC_TAG_DEBUG(BT_H4, "enter\n");
UNUSED(file);
UNUSED(offset);
/* Don't allow any writes after service has been closed */
if (!bt_service.service_started)
return -EIO;
/* Only 1 writer is allowed */
if (1 != atomic_inc_return(&bt_service.h4_writers)) {
atomic_dec(&bt_service.h4_writers);
return -EIO;
}
/* Has en error been detect then just return with an error */
if (0 != atomic_read(&bt_service.error_count)) {
/* SCSC_TAG_WARNING(BT_H4, "firmware panicked or protocol error (error_count=%u)\n", atomic_read(&bt_service.error_count));*/
atomic_dec(&bt_service.h4_writers);
return -EIO;
}
while (written != count && 0 == ret) {
length = min(count - written, sizeof(h4_write_buffer) - bt_service.h4_write_offset);
SCSC_TAG_DEBUG(BT_H4, "count: %zu, length: %zu, h4_write_offset: %zu, written:%zu, size:%zu\n",
count, length, bt_service.h4_write_offset, written, sizeof(h4_write_buffer));
/* Is there room in the temp buffer */
if (0 == length) {
SCSC_TAG_ERR(BT_H4, "no room in the buffer\n");
atomic_inc(&bt_service.error_count);
ret = -EIO;
break;
}
/* Copy the userspace data to the target buffer */
ret = copy_from_user(&h4_write_buffer[bt_service.h4_write_offset], &buf[written], length);
if (0 == ret) {
/* Is there enough data to include a HCI command header and is the type a HCI_COMMAND_PKT */
if ((length + bt_service.h4_write_offset) >= H4DMUX_HEADER_HCI && HCI_COMMAND_PKT == h4_write_buffer[0]) {
/* Extract the HCI command packet length */
hci_pkt_len = h4_write_buffer[3] + 3;
/* Is it a complete packet available */
if ((hci_pkt_len + 1) <= (length + bt_service.h4_write_offset)) {
/* Transfer the packet to the HCI command transfer ring */
ret = scsc_bt_shm_h4_hci_cmd_write(&h4_write_buffer[1], hci_pkt_len);
if (ret >= 0) {
written += ((hci_pkt_len + 1) - bt_service.h4_write_offset);
bt_service.h4_write_offset = 0;
ret = 0;
}
} else {
/* Still needing data to have the complete packet */
SCSC_TAG_WARNING(BT_H4, "missing data (need=%zu, got=%zu)\n", (hci_pkt_len + 1), (length + bt_service.h4_write_offset));
written += length;
bt_service.h4_write_offset += (u32) length;
}
/* Is there enough data to include a ACL data header and is the type a HCI_ACLDATA_PKT */
} else if ((length + bt_service.h4_write_offset) >= H4DMUX_HEADER_ACL && HCI_ACLDATA_PKT == h4_write_buffer[0]) {
/* Extract the ACL data packet length */
hci_pkt_len = (h4_write_buffer[3] | (h4_write_buffer[4] << 8));
/* Sanity check on the packet length */
if (hci_pkt_len > BSMHCP_ACL_PACKET_SIZE) {
SCSC_TAG_ERR(BT_H4, "ACL packet length is larger than read buffer size specifies (%zu > %u)\n", hci_pkt_len, BSMHCP_ACL_PACKET_SIZE);
atomic_inc(&bt_service.error_count);
ret = -EIO;
break;
}
/* Is it a complete packet available */
if ((hci_pkt_len + 5) <= (length + bt_service.h4_write_offset)) {
/* Transfer the packet to the ACL data transfer ring */
ret = scsc_bt_shm_h4_acl_write(&h4_write_buffer[1], hci_pkt_len + 4);
if (ret >= 0) {
written += ((hci_pkt_len + 5) - bt_service.h4_write_offset);
bt_service.h4_write_offset = 0;
ret = 0;
}
} else {
/* Still needing data to have the complete packet */
SCSC_TAG_WARNING(BT_H4, "missing data (need=%zu, got=%zu)\n", (hci_pkt_len + 5), (length - bt_service.h4_write_offset));
written += length;
bt_service.h4_write_offset += (u32) length;
}
/* Is there less data than a header then just wait for more */
} else if (length <= 5) {
bt_service.h4_write_offset += length;
written += length;
/* Header is unknown - unable to proceed */
} else {
atomic_inc(&bt_service.error_count);
ret = -EIO;
}
} else {
SCSC_TAG_WARNING(BT_H4, "copy_from_user returned: %zu\n", ret);
ret = -EACCES;
}
}
SCSC_TAG_DEBUG(BT_H4, "h4_write_offset=%zu, ret=%zu, written=%zu\n",
bt_service.h4_write_offset, ret, written);
/* Decrease the H4 readers counter */
atomic_dec(&bt_service.h4_writers);
return 0 == ret ? written : ret;
}
unsigned scsc_bt_shm_h4_poll(struct file *file, poll_table *wait)
{
/* Add the wait queue to the polling queue */
poll_wait(file, &bt_service.read_wait, wait);
/* Return immediately if service has been closed */
if (!bt_service.service_started)
return POLLOUT;
/* Has en error been detect then just return with an error */
if (((bt_service.bsmhcp_protocol->header.mailbox_hci_evt_write !=
bt_service.bsmhcp_protocol->header.mailbox_hci_evt_read ||
bt_service.bsmhcp_protocol->header.mailbox_acl_rx_write !=
bt_service.bsmhcp_protocol->header.mailbox_acl_rx_read ||
bt_service.bsmhcp_protocol->header.mailbox_acl_free_write !=
bt_service.bsmhcp_protocol->header.mailbox_acl_free_read ||
bt_service.bsmhcp_protocol->header.mailbox_iq_report_write !=
bt_service.bsmhcp_protocol->header.mailbox_iq_report_read) &&
bt_service.read_operation != BT_READ_OP_STOP) ||
(bt_service.read_operation != BT_READ_OP_NONE &&
bt_service.read_operation != BT_READ_OP_STOP) ||
((BT_READ_OP_STOP != bt_service.read_operation) &&
(0 != atomic_read(&bt_service.error_count) ||
bt_service.bsmhcp_protocol->header.panic_deathbed_confession))) {
SCSC_TAG_DEBUG(BT_H4, "queue(s) changed\n");
return POLLIN | POLLRDNORM; /* readeable */
}
SCSC_TAG_DEBUG(BT_H4, "no change\n");
return POLLOUT; /* writeable */
}
/* Initialise the shared memory interface */
int scsc_bt_shm_init(void)
{
/* Get kmem pointer to the shared memory ref */
bt_service.bsmhcp_protocol = scsc_mx_service_mif_addr_to_ptr(bt_service.service, bt_service.bsmhcp_ref);
if (bt_service.bsmhcp_protocol == NULL) {
SCSC_TAG_ERR(BT_COMMON, "couldn't map kmem to shm_ref 0x%08x\n", (u32)bt_service.bsmhcp_ref);
return -ENOMEM;
}
/* Clear the protocol shared memory area */
memset(bt_service.bsmhcp_protocol, 0, sizeof(*bt_service.bsmhcp_protocol));
bt_service.bsmhcp_protocol->header.magic_value = BSMHCP_PROTOCOL_MAGICVALUE;
bt_service.mailbox_hci_evt_read = 0;
bt_service.mailbox_acl_rx_read = 0;
bt_service.mailbox_acl_free_read = 0;
bt_service.mailbox_acl_free_read_scan = 0;
bt_service.mailbox_iq_report_read = 0;
bt_service.read_index = 0;
bt_service.allocated_count = 0;
bt_service.iq_reports_enabled = false;
h4_irq_mask = 0;
/* Initialise the interrupt handlers */
if (scsc_bt_shm_init_interrupt() < 0) {
SCSC_TAG_ERR(BT_COMMON, "Failed to register IRQ bits\n");
return -EIO;
}
return 0;
}
/* Terminate the shared memory interface, stopping its thread.
*
* Note: The service must be stopped prior to calling this function.
* The shared memory can only be released after calling this function.
*/
void scsc_bt_shm_exit(void)
{
u16 irq_num = 0;
/* Release IRQs */
if (bt_service.bsmhcp_protocol != NULL) {
if (h4_irq_mask & 1 << irq_num++)
scsc_service_mifintrbit_unregister_tohost(
bt_service.service, bt_service.bsmhcp_protocol->header.bg_to_ap_int_src);
if (h4_irq_mask & 1 << irq_num++)
scsc_service_mifintrbit_unregister_tohost(
bt_service.service, bt_service.bsmhcp_protocol->header.fg_to_ap_int_src);
if (h4_irq_mask & 1 << irq_num++)
scsc_service_mifintrbit_free_fromhost(
bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, SCSC_MIFINTR_TARGET_R4);
if (h4_irq_mask & 1 << irq_num++)
scsc_service_mifintrbit_free_fromhost(
bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_fg_int_src, SCSC_MIFINTR_TARGET_R4);
if (h4_irq_mask & 1 << irq_num++)
scsc_service_mifintrbit_free_fromhost(
bt_service.service, bt_service.bsmhcp_protocol->header.ap_to_fg_m4_int_src, SCSC_MIFINTR_TARGET_M4);
}
/* Clear all control structures */
bt_service.last_alloc = 0;
bt_service.hci_event_paused = false;
bt_service.acldata_paused = false;
bt_service.bsmhcp_protocol = NULL;
memset(bt_service.allocated, 0, sizeof(bt_service.allocated));
memset(bt_service.connection_handle_list, 0, sizeof(bt_service.connection_handle_list));
}