/**************************************************************************** * * Copyright (c) 2015 Samsung Electronics Co., Ltd * ****************************************************************************/ /* MX BT shared memory interface */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) #include #else #include #endif #include #include #include #include #ifdef CONFIG_SCSC_LOG_COLLECTION #include #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[DATA_PKTS_MAX_LEN]; static u8 h4_read_data_header_len; static u8 h4_read_data_header[H4DMUX_HEADER_MAX_SIZE + ISO_DATA_LOAD_HEADER_SIZE]; /* Worst case */ static u8 h4_hci_event_ncp_header[HCI_EVENT_NCP_HEADER_LEN + MAX_NCP_ENTRIES * sizeof(struct hci_credit_entry)]; static u32 h4_hci_event_ncp_header_len = HCI_EVENT_NCP_HEADER_LEN + sizeof(struct hci_credit_entry); static struct hci_credit_entry *h4_hci_credit_entries = (struct hci_credit_entry *)&h4_hci_event_ncp_header[HCI_EVENT_NCP_HEADER_LEN]; 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 bool scsc_read_data_available(void) { 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.bsmhcp_protocol->header_2.mailbox_iso_rx_write != bt_service.bsmhcp_protocol->header_2.mailbox_iso_rx_read || bt_service.bsmhcp_protocol->header_2.mailbox_iso_free_write != bt_service.bsmhcp_protocol->header_2.mailbox_iso_free_read) return true; return false; } 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 (scsc_read_data_available() || atomic_read(&bt_service.error_count) != 0 || 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.bsmhcp_protocol->header_2.mailbox_iso_tx_write == bt_service.bsmhcp_protocol->header_2.mailbox_iso_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); } } } static void scsc_bt_clear_paused_acl_rx(u16 conn_hdl) { /* Adjust the index for reverse searching of acl_rx_transfer_ring */ u32 search = 0; u32 dst = BSMHCP_PREV_INDEX(bt_service.mailbox_acl_rx_write, BSMHCP_TRANSFER_RING_ACL_SIZE); u32 stop = BSMHCP_PREV_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); /* Clear all ACL data having data_paused_conn_hdl from acl_rx_transfer_ring */ for (search = dst; search != stop; search = BSMHCP_PREV_INDEX(search, BSMHCP_TRANSFER_RING_ACL_SIZE)) { if (conn_hdl != bt_service.bsmhcp_protocol->acl_rx_transfer_ring[search].header.hci_connection_handle) { if (search != dst) memcpy(&bt_service.bsmhcp_protocol->acl_rx_transfer_ring[dst], &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[search], sizeof(struct BSMHCP_TD_ACL_RX)); dst = BSMHCP_PREV_INDEX(dst, BSMHCP_TRANSFER_RING_ACL_SIZE); } else BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); } } /* 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, SCSC_MIFINTR_TARGET_WPAN); 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++; /* From-host f/w IRQ allocations */ irq_ret = scsc_service_mifintrbit_alloc_fromhost( bt_service.service, SCSC_MIFINTR_TARGET_WPAN); 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_WPAN); 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++; SCSC_TAG_DEBUG(BT_COMMON, "Registered to-host IRQ bits %d, from-host IRQ bits %d:%d\n", bt_service.bsmhcp_protocol->header.bg_to_ap_int_src, bt_service.bsmhcp_protocol->header.ap_to_bg_int_src, bt_service.bsmhcp_protocol->header.ap_to_fg_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_WPAN); } 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_WPAN); } 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 */ uint8_t pb_flag; uint16_t conn_hdl; td->header.buffer_index = (uint8_t)acldata_buf_index; td->header.flags = HCI_ACL_DATA_GET_FLAGS(data); td->header.hci_connection_handle = HCI_ACL_DATA_GET_CON_HDL(data); td->header.length = (uint16_t)payload_len; pb_flag = td->header.flags & BSMHCP_ACL_PB_FLAG_MASK; conn_hdl = td->header.hci_connection_handle; if ((td->header.flags & BSMHCP_ACL_BC_FLAG_BCAST_ACTIVE) || (td->header.flags & BSMHCP_ACL_BC_FLAG_BCAST_ALL)) { SCSC_TAG_DEBUG(BT_H4, "Setting broadcast handle (hci_connection_handle=0x%03x)\n", conn_hdl); bt_service.connection_handle_list[conn_hdl].state = CONNECTION_ACTIVE; } /* Is this a packet marked with the start flag */ if (pb_flag == BSMHCP_ACL_PB_FLAG_START_NONFLUSH || pb_flag == BSMHCP_ACL_PB_FLAG_START_FLUSH) { /* Extract the L2CAP payload length and connection identifier */ td->header.l2cap_cid = HCI_L2CAP_GET_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(conn_hdl, data + 4, td->header.length, true); l2cap_length = HCI_L2CAP_GET_LENGTH(data); SCSC_TAG_DEBUG(BT_TX, "ACL[START] (len=%u, buffer=%u, credits=%u, l2cap_cid=0x%04x, l2cap_length=%u)\n", td->header.length, acldata_buf_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ACL_SIZE, bt_service.allocated_count, bt_service.freed_count), td->header.l2cap_cid, 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->header.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 %zu > L2CAP Length %u\n", payload_len - L2CAP_HEADER_SIZE, l2cap_length); td->header.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[conn_hdl].remaining_length = (u16)(l2cap_length - payload_len + L2CAP_HEADER_SIZE); bt_service.connection_handle_list[conn_hdl].l2cap_cid = HCI_L2CAP_GET_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 (pb_flag == BSMHCP_ACL_PB_FLAG_CONT) { /* Set the L2CAP connection identifer set by the start packet */ u16 remaining_length = bt_service.connection_handle_list[conn_hdl].remaining_length; td->header.l2cap_cid = bt_service.connection_handle_list[conn_hdl].l2cap_cid; SCSC_TAG_DEBUG(BT_TX, "ACL[CONT] (len=%u, buffer=%u, credits=%u, l2cap_cid=0x%04x, length=%u)\n", td->header.length, acldata_buf_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ACL_SIZE, bt_service.allocated_count, bt_service.freed_count), bt_service.connection_handle_list[conn_hdl].l2cap_cid, remaining_length); /* Does this packet complete the L2CAP frame */ if (remaining_length == payload_len) { /* The L2CAP frame is complete. mark it with the END flag */ td->header.flags |= BSMHCP_ACL_L2CAP_FLAG_END; /* Set the remaining length to zero */ bt_service.connection_handle_list[conn_hdl].remaining_length = 0; } else if (remaining_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 %zu > L2CAP Missing Length %u\n", payload_len, remaining_length); td->header.flags |= BSMHCP_ACL_L2CAP_FLAG_END; /* Set the remaining length to zero */ bt_service.connection_handle_list[conn_hdl].remaining_length = 0; } else if (remaining_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[conn_hdl].remaining_length -= (u16)payload_len; else if (remaining_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", remaining_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->header.flags >> 4, conn_hdl, td->header.l2cap_cid, bt_service.connection_handle_list[conn_hdl].remaining_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(); /* 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_WPAN); } 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; } static ssize_t scsc_bt_shm_h4_iso_write(const unsigned char *data, size_t count) { uint8_t pb_flag; uint16_t conn_hdl; /* 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_2.mailbox_iso_tx_read; uint32_t tr_write = bt_service.bsmhcp_protocol->header_2.mailbox_iso_tx_write; struct BSMHCP_TD_ISO_TX_DATA *td = NULL; int isodata_buf_index = -1; uint16_t data_load_header_len = 0; int i; /* Index out of bounds check */ if (tr_read >= BSMHCP_TRANSFER_RING_ISO_TX_SIZE || tr_write >= BSMHCP_TRANSFER_RING_ISO_TX_SIZE) { SCSC_TAG_ERR(BT_H4, "ISO_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_ISO_SIZE; i++) { /* Wrap the offset index around the buffer max */ if (++bt_service.iso_last_alloc == BSMHCP_DATA_BUFFER_TX_ISO_SIZE) bt_service.iso_last_alloc = 0; /* Claim a free slot */ if (bt_service.iso_allocated[bt_service.iso_last_alloc] == 0) { bt_service.iso_allocated[bt_service.iso_last_alloc] = 1; isodata_buf_index = bt_service.iso_last_alloc; bt_service.iso_allocated_count++; break; } } /* Is a buffer available to hold the data */ if (isodata_buf_index < 0) { SCSC_TAG_ERR(BT_H4, "ISO_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_ISO_TX_SIZE)) { /* Transfer ring full. Only happens if the user attempt to send more * ISO data packets than available credits */ SCSC_TAG_ERR(BT_H4, "ISO_DATA_PKT - No room in transfer ring (tr_write=%u, tr_read=%u)\n", tr_write, tr_read); atomic_inc(&bt_service.error_count); return -EIO; } /* The transfer ring has room for a entry. Extract the ISO data header and * fill it into the transfer descriptor */ td = &bt_service.bsmhcp_protocol->iso_tx_data_transfer_ring[tr_write]; td->header.length = (uint16_t)(count - ISODATA_HEADER_SIZE); td->header.buffer_index = (uint8_t)isodata_buf_index; td->header.flags = HCI_ISO_DATA_GET_FLAGS(data); td->header.hci_connection_handle = HCI_ISO_DATA_GET_CON_HDL(data); conn_hdl = td->header.hci_connection_handle; pb_flag = td->header.flags & BSMHCP_ISO_PB_FLAG_MASK; /* The Time_Stamp, Packet_Sequence_Number and ISO_SDU_Length fields * are only included in the HCI ISO data packet when the PB_Flag equals * BSMHCP_ISO_PB_FLAG_FIRST (0b00) or BSMHCP_ISO_PB_FLAG_COMPLETE (0b10) */ if (pb_flag == BSMHCP_ISO_PB_FLAG_FIRST || pb_flag == BSMHCP_ISO_PB_FLAG_COMPLETE) { u8 offset = 0; data_load_header_len = ISO_PKT_SEQ_NUM_SIZE + ISO_PKT_SDU_LENGTH_SIZE; /* Is the Time stamp include */ if ((td->header.flags & BSMHCP_ISO_TS_FLAG_MASK) == BSMHCP_ISO_TS_FLAG_TS_INCL) { data_load_header_len += ISO_TIMESTAMP_SIZE; offset = ISO_TIMESTAMP_SIZE; } if (td->header.length >= data_load_header_len) { /* Subtract the length of the additional headers in the ISO Data load field * to get the 'real' payload_len */ td->header.length -= data_load_header_len; if ((td->header.flags & BSMHCP_ISO_TS_FLAG_MASK) == BSMHCP_ISO_TS_FLAG_TS_INCL) td->time_stamp = HCI_ISO_DATA_GET_TIMESTAMP(data); } else { /* The ISO_Data_Load field shall contain a header - protocol error */ SCSC_TAG_ERR(BT_H4, "ISO[FIRST] - Length Error (data_load_header_len=%u, packet_len=%u)\n", data_load_header_len, td->header.length); atomic_inc(&bt_service.error_count); return -EIO; } /* Extract Packet Sequence Number and SDU length */ td->packet_sequence_number = HCI_ISO_DATA_GET_PKT_SEQ_NUM(data, offset); td->sdu_length = HCI_ISO_DATA_GET_SDU_LENGTH(data, offset); if (pb_flag == BSMHCP_ISO_PB_FLAG_FIRST) { /* The ISO Data Load field contains the first fragment of a fragmented SDU */ SCSC_TAG_DEBUG(BT_TX, "ISO[FIRST] (payload_len=%u, buffer=%u, credits=%u, sdu_length=%u, header_length=%u)\n", td->header.length, td->header.buffer_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ISO_SIZE, bt_service.iso_allocated_count, bt_service.iso_freed_count), td->sdu_length, data_load_header_len); if (td->sdu_length > td->header.length) { bt_service.connection_handle_list[conn_hdl].remaining_length = td->sdu_length - td->header.length; } else { /* The SDU length field shall be greater than the payload len - protocol error */ SCSC_TAG_ERR(BT_H4, "ISO[FIRST] - SDU Length Error (sdu_length=%u, payload_len=%u)\n", td->sdu_length, td->header.length); atomic_inc(&bt_service.error_count); return -EIO; } } else { /* The ISO Data Load field contains a complete SDU */ SCSC_TAG_DEBUG(BT_TX, "ISO[COMP] (payload_len=%u, buffer=%u, credits=%u, sdu_length=%u, header_length=%u)\n", td->header.length, td->header.buffer_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ISO_SIZE, bt_service.iso_allocated_count, bt_service.iso_freed_count), td->sdu_length, data_load_header_len); if (td->sdu_length == td->header.length) { /* Mark it with the END flag as the payload length matches the ISO SDU length */ td->header.flags |= BSMHCP_ACL_L2CAP_FLAG_END; bt_service.connection_handle_list[conn_hdl].remaining_length = 0; } else { /* The SDU length field not equals the payload length - protocol error */ SCSC_TAG_ERR(BT_H4, "ISO[COMP] - SDU Length Error (sdu_length=%u, payload_len=%u)\n", td->sdu_length, td->header.length); atomic_inc(&bt_service.error_count); return -EIO; } } } else if (pb_flag == BSMHCP_ISO_PB_FLAG_CONTINUE) { /* The ISO_Data_Load field contains a continuation fragment of an SDU */ SCSC_TAG_DEBUG(BT_TX, "ISO[CONT] (payload_len=%u, buffer=%u, credits=%u, length=%u)\n", td->header.length, td->header.buffer_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ISO_SIZE, bt_service.iso_allocated_count, bt_service.iso_freed_count), bt_service.connection_handle_list[conn_hdl].remaining_length); if (bt_service.connection_handle_list[conn_hdl].remaining_length >= td->header.length) { /* This is another fragment of the packet. Save the remaining number of octets required * to complete the packet. */ bt_service.connection_handle_list[conn_hdl].remaining_length -= td->header.length; } else { SCSC_TAG_ERR(BT_H4, "ISO[CONT] - SDU Length Error (missing=%u, payload_len=%u)\n", bt_service.connection_handle_list[conn_hdl].remaining_length, td->header.length); atomic_inc(&bt_service.error_count); return -EIO; } } else if (pb_flag == BSMHCP_ISO_PB_FLAG_LAST) { /* The ISO_Data_Load field contains the last fragment of an SDU */ SCSC_TAG_DEBUG(BT_TX, "ISO[LAST] (payload_len=%u, buffer=%u, credits=%u, length=%u)\n", td->header.length, td->header.buffer_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ISO_SIZE, bt_service.iso_allocated_count, bt_service.iso_freed_count), bt_service.connection_handle_list[conn_hdl].remaining_length); if (bt_service.connection_handle_list[conn_hdl].remaining_length == td->header.length) { /* Mark it with the END flag as the length matches the ISO SDU length */ td->header.flags |= BSMHCP_ACL_L2CAP_FLAG_END; bt_service.connection_handle_list[conn_hdl].remaining_length = 0; } else { SCSC_TAG_ERR(BT_H4, "ISO[LAST] - SDU Length Error (missing=%u, payload_len=%u)\n", bt_service.connection_handle_list[conn_hdl].remaining_length, td->header.length); atomic_inc(&bt_service.error_count); return -EIO; } } else { /* Reserved flags set - report it as an error */ SCSC_TAG_ERR(BT_H4, "ISO_DATA_PKT - PB Flag set to reserved value\n"); atomic_inc(&bt_service.error_count); return -EIO; } SCSC_TAG_DEBUG(BT_H4, "ISO_DATA_PKT (payload_len=%u, read=%u, write=%u, slot=%u, flags=0x%04x, handle=0x%03x, missing=%u)\n", td->header.length, tr_read, tr_write, td->header.buffer_index, td->header.flags >> 4, conn_hdl, bt_service.connection_handle_list[conn_hdl].remaining_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 ISO packet into the target buffer */ memcpy(&bt_service.bsmhcp_protocol->iso_tx_buffer[td->header.buffer_index][0], &data[ISODATA_HEADER_SIZE + data_load_header_len], td->header.length); /* Increate the write pointer */ BSMHCP_INCREASE_INDEX(tr_write, BSMHCP_TRANSFER_RING_ISO_TX_SIZE); bt_service.bsmhcp_protocol->header_2.mailbox_iso_tx_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_fg_int_src, SCSC_MIFINTR_TARGET_WPAN); 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 (bt_service.read_offset == 0) { 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 (ret == 0) { /* 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 (ret == 0 && (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 (ret == 0) { /* 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 ret == 0 ? 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 (ret == 0) { /* 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 ret == 0 ? consumed : ret; } static ssize_t scsc_rx_read_data(char __user *buf, size_t len, uint16_t data_length, uint8_t *data) { ssize_t ret = 0; ssize_t consumed = 0; size_t copy_len = 0; /* Has the header been copied to userspace */ if (bt_service.read_offset < h4_read_data_header_len) { copy_len = min(h4_read_data_header_len - bt_service.read_offset, len); /* Copy the header to the userspace buffer */ ret = copy_to_user(buf, &h4_read_data_header[bt_service.read_offset], copy_len); if (ret == 0) { /* 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 (ret == 0 && bt_service.read_offset >= h4_read_data_header_len && (len - consumed)) { /* Calculate the amount of data that can be transferred */ copy_len = min((data_length - (bt_service.read_offset - h4_read_data_header_len)), (len - consumed)); /* Copy the data to the user buffer */ ret = copy_to_user(&buf[consumed], &data[bt_service.read_offset - h4_read_data_header_len], copy_len); if (ret == 0) { /* 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 == (h4_read_data_header_len + data_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; } } 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 ret == 0 ? consumed : ret; } static ssize_t scsc_rx_read_iso_data(char __user *buf, size_t len) { struct BSMHCP_TD_ISO_RX *td = &bt_service.bsmhcp_protocol->iso_rx_transfer_ring[bt_service.read_index]; SCSC_TAG_DEBUG(BT_H4, "td (length=%u, hci_connection_handle=0x%03x, pb_flag=%u, ts_flag=%u), len=%zu, read_offset=%zu\n", td->header.length, td->header.hci_connection_handle, td->header.pb_flag, td->header.flag, len, bt_service.read_offset); return scsc_rx_read_data(buf, len, td->header.length, td->data); } static ssize_t scsc_rx_read_acl_data(char __user *buf, size_t len) { ssize_t ret; struct BSMHCP_TD_ACL_RX *td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.read_index]; SCSC_TAG_DEBUG(BT_H4, "td (length=%u, hci_connection_handle=0x%03x, pb_flag=%u, bc_flag=%u), len=%zu, read_offset=%zu\n", td->header.length, td->header.hci_connection_handle, td->header.pb_flag, td->header.flag, len, bt_service.read_offset); ret = scsc_rx_read_data(buf, len, td->header.length, td->data); if (bt_service.read_operation == BT_READ_OP_NONE && td->header.pb_flag == HCI_ACL_PACKET_BOUNDARY_START_FLUSH) /* The "false" argument is to tell the detection that this is RX */ scsc_avdtp_detect_rxtx(td->header.hci_connection_handle, td->data, td->header.length, false); return ret; } static ssize_t scsc_read_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 (ret == 0) { /* 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 ret == 0 ? 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_service.read_operation == BT_READ_OP_HCI_EVT) { 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_service.read_operation == BT_READ_OP_NONE) /* 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_service.read_operation == BT_READ_OP_ACL_DATA) { SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_ACL_DATA\n"); /* Copy data into the userspace buffer */ ret = scsc_rx_read_acl_data(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_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); /* Is a ISO data read operation ongoing */ } else if (bt_service.read_operation == BT_READ_OP_ISO_DATA) { SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_ISO_DATA\n"); /* Copy data into the userspace buffer */ ret = scsc_rx_read_iso_data(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_iso_rx_read, BSMHCP_TRANSFER_RING_ISO_RX_SIZE); /* Is a ACL credit update operation ongoing */ } else if (bt_service.read_operation == BT_READ_OP_CREDIT) { SCSC_TAG_DEBUG(BT_H4, "BT_READ_OP_CREDIT\n"); /* Copy data into the userspace buffer */ ret = scsc_read_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_service.read_operation == BT_READ_OP_HCI_EVT_ERROR) { 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_service.read_operation == BT_READ_OP_NONE) /* All done - set the stop condition */ bt_service.read_operation = BT_READ_OP_STOP; } else if (bt_service.read_operation == BT_READ_OP_STOP) { 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++] = HCI_EV_ULP; 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_service.read_operation == BT_READ_OP_NONE && ret == 0 && !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].remaining_length = 0; /* ACL and ISO data processing can now continue */ bt_service.data_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 (bt_service.connection_handle_list[td->hci_connection_handle].state == CONNECTION_ACTIVE) { /* Pause the HCI event procssing until the ACL or ISO 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; /* Clear the ACL and ISO data processing to allow for the ACL and ISO * disconnect event to be transferred to userspace */ if (bt_service.data_paused && bt_service.data_paused_conn_hdl == td->hci_connection_handle) { /* ACL and ISO data processing can now continue */ bt_service.data_paused = false; /* Clear all ACL data having data_paused_conn_hdl from acl_rx_transfer_ring */ scsc_bt_clear_paused_acl_rx(bt_service.data_paused_conn_hdl); /* Initialize the data_paused_conn_hdl for next data_paused */ bt_service.data_paused_conn_hdl = 0; } } 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_service.read_operation == BT_READ_OP_NONE) BSMHCP_INCREASE_INDEX(bt_service.mailbox_hci_evt_read, BSMHCP_TRANSFER_RING_EVT_SIZE); else break; } } return ret == 0 ? consumed : ret; } static bool scsc_rx_read_iso_data_start_copy(char __user *buf, size_t len, ssize_t *ret, ssize_t *consumed) { uint16_t data_load_len; struct BSMHCP_TD_ISO_RX *td = &bt_service.bsmhcp_protocol->iso_rx_transfer_ring[bt_service.mailbox_iso_rx_read]; u8 ts_flag = td->header.flag & BSMHCP_ISO_TS_FLAG_MASK; bt_service.read_operation = BT_READ_OP_ISO_DATA; bt_service.read_index = bt_service.mailbox_iso_rx_read; /* Fully generate the H4 header + ISO data header regardless of the available amount of user memory */ h4_read_data_header_len = H4DMUX_HEADER_ISO; h4_read_data_header[H4_HEADER_INDEX] = HCI_ISODATA_PKT; HCI_ISO_DATA_SET_CON_HDL(h4_read_data_header, td->header.hci_connection_handle, td->header.pb_flag, ts_flag); /* The fields Time_Stamp, Packet_Sequence_Number, Packet_Status_Flag and ISO_SDU_Length * shall only be included in the HCI ISO Data packet if the PB_Flag equals 0b00 or 0b10 */ if (td->header.pb_flag == BSMHCP_ISO_PB_FLAG_FIRST || td->header.pb_flag == BSMHCP_ISO_PB_FLAG_COMPLETE) { /* The Time_Stamp field shall only be included in the HCI ISO Data packet if the TS_Flag is set */ if (ts_flag == BSMHCP_ISO_TS_FLAG_TS_INCL) { HCI_ISO_DATA_SET_TIMESTAMP(h4_read_data_header, h4_read_data_header_len, td->time_stamp); h4_read_data_header_len += ISO_TIMESTAMP_SIZE; } HCI_ISO_DATA_SET_PKT_SEQ_NUM(h4_read_data_header, h4_read_data_header_len, td->packet_sequence_number); h4_read_data_header_len += ISO_PKT_SEQ_NUM_SIZE; HCI_ISO_DATA_SET_SDU_LENGTH_AND_STATUS_FLAG(h4_read_data_header, h4_read_data_header_len, td->sdu_length, td->packet_status_flag); h4_read_data_header_len += ISO_PKT_SDU_LENGTH_SIZE; } /* Add the length of the additional headers in the ISO Data load field to get the 'real' payload_len */ data_load_len = td->header.length + (h4_read_data_header_len - H4DMUX_HEADER_ISO); if (data_load_len > ISO_DATA_LOAD_LENGTH_MAX_LEN) { SCSC_TAG_ERR(BT_H4, "Error - Received ISO Packet to large (data length=%u, max size=%u)\n", data_load_len, ISO_DATA_LOAD_LENGTH_MAX_LEN); atomic_inc(&bt_service.error_count); return -EIO; } HCI_ISO_DATA_SET_LENGTH(h4_read_data_header, data_load_len); *ret = scsc_rx_read_iso_data(&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_iso_rx_read, BSMHCP_TRANSFER_RING_ISO_RX_SIZE); return false; } /** * 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_rx_read_acl_data_start_copy(char __user *buf, size_t len, ssize_t *ret, ssize_t *consumed) { struct BSMHCP_TD_ACL_RX *td = &bt_service.bsmhcp_protocol->acl_rx_transfer_ring[bt_service.mailbox_acl_rx_read]; bt_service.read_operation = BT_READ_OP_ACL_DATA; bt_service.read_index = bt_service.mailbox_acl_rx_read; /* Fully generate the H4 header + ACL data header regardless of the available amount of user memory */ h4_read_data_header_len = H4DMUX_HEADER_ACL; h4_read_data_header[H4_HEADER_INDEX] = HCI_ACLDATA_PKT; HCI_ACL_DATA_SET_CON_HDL(h4_read_data_header, td->header.hci_connection_handle, td->header.pb_flag, td->header.flag); HCI_ACL_DATA_SET_LENGTH(h4_read_data_header, td->header.length); *ret = scsc_rx_read_acl_data(&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 bool scsc_rx_read_connection_active(uint16_t hci_connection_handle, uint8_t disconnected) { /* Sanity check of the HCI connection handle */ if (hci_connection_handle >= SCSC_BT_CONNECTION_INFO_MAX) { SCSC_TAG_ERR(BT_H4, "connection handle is beyond max (hci_connection_handle=0x%03x)\n", hci_connection_handle); atomic_inc(&bt_service.error_count); return false; } /* Only process ACL or ISO data if the connection is marked active aka a * HCI connection complete event has arrived */ if (bt_service.connection_handle_list[hci_connection_handle].state == CONNECTION_ACTIVE) { /* Is this the final packet for the indicated ACL or ISO connection */ if (disconnected) { SCSC_TAG_DEBUG(BT_H4, "CONN disconnected (hci_connection_handle=0x%03x, state=%u)\n", hci_connection_handle, bt_service.connection_handle_list[hci_connection_handle].state); /* Update the connection table to mark it as disconnected */ bt_service.connection_handle_list[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; } return true; } /* If the connection state is inactive the HCI connection complete information hasn't yet arrived. * Stop processing ACL or ISO data */ if (bt_service.connection_handle_list[hci_connection_handle].state == CONNECTION_NONE) { SCSC_TAG_DEBUG(BT_H4, "DATA empty (hci_connection_handle=0x%03x, state=%u)\n", hci_connection_handle, bt_service.connection_handle_list[hci_connection_handle].state); bt_service.data_paused = true; bt_service.data_paused_conn_hdl = hci_connection_handle; /* If the connection state is disconnection the firmware sent ACL or ISO after the ACL or ISO * disconnect packet which is an FW error */ } else { SCSC_TAG_ERR(BT_H4, "DATA received after disconnected indication\n"); atomic_inc(&bt_service.error_count); } return false; } static ssize_t scsc_bt_shm_h4_read_iso_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.data_paused && bt_service.mailbox_iso_rx_read != bt_service.mailbox_iso_rx_write) { struct BSMHCP_TD_ISO_RX *td = &bt_service.bsmhcp_protocol->iso_rx_transfer_ring[bt_service.mailbox_iso_rx_read]; /* Only process ISO data if the connection is marked active aka a * HCI connection complete event has arrived */ if (scsc_rx_read_connection_active(td->header.hci_connection_handle, td->header.disconnected)) { /* Is this the final packet for the indicated ISO connection */ if (td->header.disconnected) { /* Update the read pointer */ BSMHCP_INCREASE_INDEX(bt_service.mailbox_iso_rx_read, BSMHCP_TRANSFER_RING_ISO_RX_SIZE); } else { if (scsc_rx_read_iso_data_start_copy(buf, len, &ret, &consumed)) break; } } else { break; } } return ret == 0 ? consumed : ret; } 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.data_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 ((td->header.hci_connection_handle & SCSC_BT_ACL_RAW_MASK) == SCSC_BT_ACL_RAW) { if (scsc_rx_read_acl_data_start_copy(buf, len, &ret, &consumed)) break; } /* Only process ACL data if the connection is marked active aka a * HCI connection complete event has arrived */ if (scsc_rx_read_connection_active(td->header.hci_connection_handle, td->header.disconnected)) { /* Is this the final packet for the indicated ACL connection */ if (td->header.disconnected) { /* Update the read pointer */ BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_rx_read, BSMHCP_TRANSFER_RING_ACL_SIZE); } else { if (scsc_rx_read_acl_data_start_copy(buf, len, &ret, &consumed)) break; } } else { break; } } return ret == 0 ? consumed : ret; } static void scsc_bt_shm_h4_insert_credit_entries(uint16_t conn_hdl, u32 start_index, u16 tr_size, u32 *entries) { uint16_t sanitized_conn_handle = conn_hdl & SCSC_BT_ACL_HANDLE_MASK; u32 i; if (bt_service.connection_handle_list[sanitized_conn_handle].state == CONNECTION_ACTIVE) { for (i = 0; i < tr_size; i++) { if (h4_hci_credit_entries[start_index + i].hci_connection_handle == 0) { h4_hci_credit_entries[start_index + i].hci_connection_handle = conn_hdl; h4_hci_credit_entries[start_index + i].credits = 1; *entries += 1; break; } else if ((h4_hci_credit_entries[start_index + i].hci_connection_handle & SCSC_BT_ACL_HANDLE_MASK) == sanitized_conn_handle) { h4_hci_credit_entries[start_index + i].hci_connection_handle = conn_hdl; h4_hci_credit_entries[start_index + i].credits++; break; } } } else { SCSC_TAG_WARNING(BT_H4, "No active connection ((hci_connection_handle=0x%03x)\n", sanitized_conn_handle); } } static void scsc_bt_shm_h4_read_acl_credit(u32 *entries) { u32 start_index = *entries; 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]; scsc_bt_shm_h4_insert_credit_entries(td->hci_connection_handle, start_index, BSMHCP_TRANSFER_RING_ACL_SIZE, entries); BSMHCP_INCREASE_INDEX(bt_service.mailbox_acl_free_read, BSMHCP_TRANSFER_RING_ACL_SIZE); } } static void scsc_bt_shm_h4_read_iso_credit(u32 *entries) { u32 start_index = *entries; while (bt_service.mailbox_iso_free_read != bt_service.mailbox_iso_free_write) { struct BSMHCP_TD_ISO_TX_FREE *td = &bt_service.bsmhcp_protocol->iso_tx_free_transfer_ring[bt_service.mailbox_iso_free_read]; scsc_bt_shm_h4_insert_credit_entries(td->hci_connection_handle, start_index, BSMHCP_TRANSFER_RING_ISO_TX_SIZE, entries); BSMHCP_INCREASE_INDEX(bt_service.mailbox_iso_free_read, BSMHCP_TRANSFER_RING_ISO_TX_SIZE); } } static ssize_t scsc_bt_shm_h4_read_credit(char __user *buf, size_t len) { ssize_t ret = 0; if (bt_service.read_operation == BT_READ_OP_NONE) { u32 entries = 0; memset(h4_hci_event_ncp_header, 0, sizeof(h4_hci_event_ncp_header)); /* Find how many ACL data packet have been completed */ scsc_bt_shm_h4_read_acl_credit(&entries); /* Find how many ISO data packet have been completed */ scsc_bt_shm_h4_read_iso_credit(&entries); if (entries > 0) { /* 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; /* Event Code */ h4_hci_event_ncp_header[2] = 1 + (sizeof(struct hci_credit_entry) * entries); /* Param length */ h4_hci_event_ncp_header[3] = entries; /* Number_of_Handles */ h4_hci_event_ncp_header_len = HCI_EVENT_NCP_HEADER_LEN + (sizeof(struct hci_credit_entry) * entries); /* Start copy the Number of Completed Packet Event to userspace */ bt_service.read_operation = BT_READ_OP_CREDIT; ret = scsc_read_credit(buf, len); } } return ret; } ssize_t scsc_bt_shm_h4_queue_sync_helper(char __user *buf, size_t len) { uint16_t conn_hdl_acl; uint16_t conn_hdl_iso; ssize_t ret = 0; bool found = false; u32 mailbox_hci_evt_read = bt_service.mailbox_hci_evt_read; if (!bt_service.hci_event_paused) return ret; conn_hdl_acl = bt_service.bsmhcp_protocol->acl_rx_transfer_ring[ bt_service.mailbox_acl_rx_read].header.hci_connection_handle; conn_hdl_iso = bt_service.bsmhcp_protocol->iso_rx_transfer_ring[ bt_service.mailbox_iso_rx_read].header.hci_connection_handle; /* If both the HCI event transfer ring and data transfer rings, ACL or ISO, 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.data_paused) { 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 && (conn_hdl_acl == td->hci_connection_handle || conn_hdl_iso == 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].remaining_length = 0; /* ACL and ISO data processing can now continue */ bt_service.data_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; } if (td->event_type == BSMHCP_EVENT_TYPE_DISCONNECTED && bt_service.data_paused_conn_hdl == td->hci_connection_handle) { 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(); /* Firmware does not have more ACL data - Mark the connection as inactive */ bt_service.connection_handle_list[td->hci_connection_handle].state = CONNECTION_NONE; /* ACL and ISO data processing can now continue */ bt_service.data_paused = false; /* Clear all ACL data having data_paused_conn_hdl from acl_rx_transfer_ring */ scsc_bt_clear_paused_acl_rx(bt_service.data_paused_conn_hdl); /* Initialize the data_paused_conn_hdl for next data_paused */ bt_service.data_paused_conn_hdl = 0; /* Mark the event as processed */ bt_service.processed[mailbox_hci_evt_read] = true; /* Indicate the event have been found */ found = true; 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 || atomic_read(&bt_service.error_count) != 0 || bt_service.bsmhcp_protocol->header.panic_deathbed_confession)), HZ); if (ret == 0) { 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 (ret != 1) { SCSC_TAG_INFO(BT_H4, "user interrupt\n"); break; } } } return ret; } static void scsc_bt_free_acl_credits(void) { 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, GET_AVAILABLE_TX_CREDITS(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); } } static void scsc_bt_free_iso_credits(void) { while (bt_service.mailbox_iso_free_read_scan != bt_service.mailbox_iso_free_write) { struct BSMHCP_TD_ISO_TX_FREE *td = &bt_service.bsmhcp_protocol->iso_tx_free_transfer_ring[bt_service.mailbox_iso_free_read_scan]; /* Free the buffer in the ISO allocation table */ if (td->buffer_index < BSMHCP_DATA_BUFFER_TX_ISO_SIZE) { bt_service.iso_allocated[td->buffer_index] = 0; bt_service.iso_freed_count++; SCSC_TAG_DEBUG(BT_TX, "ISO[CREDIT] (index=%u, buffer=%u, credits=%u)\n", bt_service.mailbox_iso_free_read_scan, td->buffer_index, GET_AVAILABLE_TX_CREDITS(BSMHCP_DATA_BUFFER_TX_ISO_SIZE, bt_service.iso_allocated_count, bt_service.iso_freed_count)); } BSMHCP_INCREASE_INDEX(bt_service.mailbox_iso_free_read_scan, BSMHCP_TRANSFER_RING_ISO_TX_SIZE); } } static void scsc_bt_free_credits(void) { scsc_bt_free_acl_credits(); scsc_bt_free_iso_credits(); } static void scsc_update_cached_write(void) { /* Update the write cached indexes for all transfer rings */ 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; bt_service.mailbox_iso_rx_write = bt_service.bsmhcp_protocol->header_2.mailbox_iso_rx_write; bt_service.mailbox_iso_free_write = bt_service.bsmhcp_protocol->header_2.mailbox_iso_free_write; } static void scsc_update_read_indexes(void) { /* Update the read indexes 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; bt_service.bsmhcp_protocol->header_2.mailbox_iso_rx_read = bt_service.mailbox_iso_rx_read; bt_service.bsmhcp_protocol->header_2.mailbox_iso_free_read = bt_service.mailbox_iso_free_read; } 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 (atomic_inc_return(&bt_service.h4_readers) != 1) { atomic_dec(&bt_service.h4_readers); return -EIO; } /* Update the cached variables with the non-cached variables */ scsc_update_cached_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 (atomic_read(&bt_service.error_count) != 0 && bt_service.read_operation == BT_READ_OP_NONE) 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_service.read_operation != BT_READ_OP_NONE) { ret = scsc_bt_shm_h4_read_continue(buf, len); /* Update the consumed variable in case a operation was ongoing */ if (ret > 0) { 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 data (ACL or ISO) 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.data_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 && bt_service.mailbox_iso_free_read == bt_service.mailbox_iso_free_write && (bt_service.mailbox_iso_rx_read == bt_service.mailbox_iso_rx_write || bt_service.data_paused) && atomic_read(&bt_service.error_count) == 0 && bt_service.bsmhcp_protocol->header.panic_deathbed_confession == 0) { /* 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.data_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) || (bt_service.mailbox_iso_free_read != bt_service.bsmhcp_protocol->header_2.mailbox_iso_free_write) || (bt_service.mailbox_iso_rx_read != bt_service.bsmhcp_protocol->header_2.mailbox_iso_rx_write && !bt_service.data_paused) || 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 (atomic_read(&bt_service.error_count) != 0) break; /* Any failures is handled by the userspace application */ if (ret) break; /* Refresh our write indexes before starting to process the protocol */ scsc_update_cached_write(); } SCSC_TAG_DEBUG(BT_H4, "hci_evt_read=%u, hci_evt_write=%u, acl_rx_read=%u, acl_rx_write=%u, iso_rx_read=%u, iso_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, bt_service.mailbox_iso_rx_read, bt_service.mailbox_iso_rx_write); SCSC_TAG_DEBUG(BT_H4, "acl_free_read=%u, acl_free_write=%u, iso_free_read=%u, iso_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_iso_free_read, bt_service.mailbox_iso_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, data_paused=%u\n", bt_service.read_operation, bt_service.hci_event_paused, bt_service.data_paused); /* First: process TX buffer that needs to marked free */ scsc_bt_free_credits(); #ifdef CONFIG_SCSC_QOS /* Second: 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)); #endif /* Third: 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; /* Fourth: process any pending ISO data that needs to be sent to userspace */ res = scsc_bt_shm_h4_read_iso_data(&buf[consumed], len - consumed); if (res < 0) { ret = res; break; } consumed += res; /* Fifth: 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; /* Sixth: process any number of complete packet that needs to be sent to userspace */ res = scsc_bt_shm_h4_read_credit(&buf[consumed], len - consumed); if (res < 0) { ret = res; break; } consumed += res; /* Seventh: process any pending IQ report that needs to be sent to userspace */ res = scsc_bt_shm_h4_read_iq_report_evt(&buf[consumed], len - consumed); if (res < 0) { ret = res; break; } consumed += res; } if (ret == 0 && consumed == 0) { if (atomic_read(&bt_service.error_count) != 0 && bt_service.read_operation == BT_READ_OP_NONE) bt_service.read_operation = BT_READ_OP_HCI_EVT_ERROR; if (bt_service.read_operation == BT_READ_OP_HCI_EVT_ERROR) { 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_service.read_operation == BT_READ_OP_NONE) /* 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 || bt_service.bsmhcp_protocol->header_2.mailbox_iso_rx_read != bt_service.mailbox_iso_rx_read || bt_service.bsmhcp_protocol->header_2.mailbox_iso_free_read != bt_service.mailbox_iso_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 */ scsc_update_read_indexes(); /* 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_WPAN); if (gen_fg_int) /* 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_WPAN); 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 ret == 0 ? 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 (atomic_inc_return(&bt_service.h4_writers) != 1) { atomic_dec(&bt_service.h4_writers); return -EIO; } /* Has en error been detect then just return with an error */ if (atomic_read(&bt_service.error_count) != 0) { atomic_dec(&bt_service.h4_writers); return -EIO; } while (written != count && ret == 0) { 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 (length == 0) { 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 (ret == 0) { /* 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 && h4_write_buffer[H4_HEADER_INDEX] == HCI_COMMAND_PKT) { /* 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 && h4_write_buffer[H4_HEADER_INDEX] == HCI_ACLDATA_PKT) { /* Extract the ACL data packet length */ hci_pkt_len = HCI_ACL_DATA_GET_LENGTH(&h4_write_buffer[H4_HEADER_SIZE]); /* 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 + H4DMUX_HEADER_ACL) <= (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 enough data to include a ISO data header and is the * type a HCI_ISODATA_PKT */ } else if ((length + bt_service.h4_write_offset) >= H4DMUX_HEADER_ISO && h4_write_buffer[H4_HEADER_INDEX] == HCI_ISODATA_PKT) { /* Extract the ISO Data packet length */ hci_pkt_len = HCI_ISO_DATA_GET_LENGTH(&h4_write_buffer[H4_HEADER_SIZE]); /* Sanity check on the packet length */ if (hci_pkt_len > BSMHCP_ISO_PACKET_SIZE) { SCSC_TAG_ERR(BT_H4, "ISO Data length is larger than read buffer size specifies (%zu > %u)\n", hci_pkt_len, BSMHCP_ISO_PACKET_SIZE); atomic_inc(&bt_service.error_count); ret = -EIO; break; } /* Is a complete packet available */ if ((hci_pkt_len + H4DMUX_HEADER_ISO) <= (length + bt_service.h4_write_offset)) { /* Transfer the packet to the ISO data transfer ring */ ret = scsc_bt_shm_h4_iso_write(&h4_write_buffer[H4_HEADER_SIZE], hci_pkt_len + ISODATA_HEADER_SIZE); if (ret >= 0) { written += ((hci_pkt_len + H4DMUX_HEADER_ISO) - 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 ISO data (need=%zu, got=%zu)\n", (hci_pkt_len + H4DMUX_HEADER_ISO), (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 <= H4DMUX_HEADER_MAX_SIZE) { 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 ret == 0 ? written : ret; } unsigned int 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 ((scsc_read_data_available() && 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_service.read_operation != BT_READ_OP_STOP && (atomic_read(&bt_service.error_count) != 0 || 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.bsmhcp_protocol->header_2.magic_value = BSMHCP_PROTOCOL_V2_MAGICVALUE; bt_service.bsmhcp_protocol->header.bsmhcp_version = BSMHCP_VERSION; 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.mailbox_iso_rx_read = 0; bt_service.mailbox_iso_free_read = 0; bt_service.mailbox_iso_free_read_scan = 0; bt_service.read_index = 0; bt_service.allocated_count = 0; bt_service.freed_count = 0; bt_service.iq_reports_enabled = false; bt_service.iso_allocated_count = 0; bt_service.iso_freed_count = 0; 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, SCSC_MIFINTR_TARGET_WPAN); 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_WPAN); 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_WPAN); } /* Clear all control structures */ bt_service.last_alloc = 0; bt_service.iso_last_alloc = 0; bt_service.hci_event_paused = false; bt_service.data_paused = false; bt_service.data_paused_conn_hdl = 0; bt_service.bsmhcp_protocol = NULL; memset(bt_service.allocated, 0, sizeof(bt_service.allocated)); memset(bt_service.iso_allocated, 0, sizeof(bt_service.iso_allocated)); memset(bt_service.connection_handle_list, 0, sizeof(bt_service.connection_handle_list)); }