You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
980 lines
30 KiB
980 lines
30 KiB
/****************************************************************************** |
|
* |
|
* Copyright (C) 2016 The Android Open Source Project |
|
* Copyright (C) 2002-2012 Broadcom Corporation |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at: |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
* |
|
******************************************************************************/ |
|
|
|
/****************************************************************************** |
|
* |
|
* this file contains the connection interface functions |
|
* |
|
******************************************************************************/ |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#include "bt_types.h" |
|
|
|
#include "l2c_api.h" |
|
#include "l2cdefs.h" |
|
|
|
#include "btm_api.h" |
|
#include "btm_int.h" |
|
#include "btu.h" |
|
|
|
#include "hiddefs.h" |
|
|
|
#include "bt_utils.h" |
|
#include "hidd_api.h" |
|
#include "hidd_int.h" |
|
|
|
#include "osi/include/osi.h" |
|
|
|
static void hidd_l2cif_connect_ind(const RawAddress& bd_addr, uint16_t cid, |
|
uint16_t psm, uint8_t id); |
|
static void hidd_l2cif_connect_cfm(uint16_t cid, uint16_t result); |
|
static void hidd_l2cif_config_ind(uint16_t cid, tL2CAP_CFG_INFO* p_cfg); |
|
static void hidd_l2cif_config_cfm(uint16_t cid, tL2CAP_CFG_INFO* p_cfg); |
|
static void hidd_l2cif_disconnect_ind(uint16_t cid, bool ack_needed); |
|
static void hidd_l2cif_disconnect_cfm(uint16_t cid, uint16_t result); |
|
static void hidd_l2cif_data_ind(uint16_t cid, BT_HDR* p_msg); |
|
static void hidd_l2cif_cong_ind(uint16_t cid, bool congested); |
|
|
|
static const tL2CAP_APPL_INFO dev_reg_info = {hidd_l2cif_connect_ind, |
|
hidd_l2cif_connect_cfm, |
|
NULL, |
|
hidd_l2cif_config_ind, |
|
hidd_l2cif_config_cfm, |
|
hidd_l2cif_disconnect_ind, |
|
hidd_l2cif_disconnect_cfm, |
|
NULL, |
|
hidd_l2cif_data_ind, |
|
hidd_l2cif_cong_ind, |
|
NULL}; |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_check_config_done |
|
* |
|
* Description Checks if connection is configured and callback can be fired |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_check_config_done() { |
|
tHID_CONN* p_hcon; |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (((p_hcon->conn_flags & HID_CONN_FLAGS_ALL_CONFIGURED) == |
|
HID_CONN_FLAGS_ALL_CONFIGURED) && |
|
(p_hcon->conn_state == HID_CONN_STATE_CONFIG)) { |
|
p_hcon->conn_state = HID_CONN_STATE_CONNECTED; |
|
|
|
hd_cb.device.state = HIDD_DEV_CONNECTED; |
|
|
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_OPEN, 0, NULL); |
|
|
|
// send outstanding data on intr |
|
if (hd_cb.pending_data) { |
|
L2CA_DataWrite(p_hcon->intr_cid, hd_cb.pending_data); |
|
hd_cb.pending_data = NULL; |
|
} |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidh_sec_check_complete_term |
|
* |
|
* Description HID security check complete callback function. |
|
* |
|
* Returns Send L2CA_ConnectRsp OK if secutiry check succeed; otherwise |
|
* send security block L2C connection response. |
|
* |
|
******************************************************************************/ |
|
static void hidd_sec_check_complete(UNUSED_ATTR const RawAddress* bd_addr, |
|
UNUSED_ATTR tBT_TRANSPORT transport, |
|
void* p_ref_data, uint8_t res) { |
|
tHID_DEV_DEV_CTB* p_dev = (tHID_DEV_DEV_CTB*)p_ref_data; |
|
|
|
if (res == BTM_SUCCESS && p_dev->conn.conn_state == HID_CONN_STATE_SECURITY) { |
|
p_dev->conn.disc_reason = HID_SUCCESS; |
|
p_dev->conn.conn_state = HID_CONN_STATE_CONNECTING_INTR; |
|
|
|
L2CA_ConnectRsp(p_dev->addr, p_dev->conn.ctrl_id, p_dev->conn.ctrl_cid, |
|
L2CAP_CONN_OK, L2CAP_CONN_OK); |
|
L2CA_ConfigReq(p_dev->conn.ctrl_cid, &hd_cb.l2cap_cfg); |
|
} else if (res != BTM_SUCCESS) { |
|
HIDD_TRACE_WARNING("%s: connection rejected by security", __func__); |
|
|
|
p_dev->conn.disc_reason = HID_ERR_AUTH_FAILED; |
|
p_dev->conn.conn_state = HID_CONN_STATE_UNUSED; |
|
L2CA_ConnectRsp(p_dev->addr, p_dev->conn.ctrl_id, p_dev->conn.ctrl_cid, |
|
L2CAP_CONN_SECURITY_BLOCK, L2CAP_CONN_OK); |
|
return; |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_sec_check_complete_orig |
|
* |
|
* Description HID security check complete callback function (device |
|
*originated) |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
void hidd_sec_check_complete_orig(UNUSED_ATTR const RawAddress* bd_addr, |
|
UNUSED_ATTR tBT_TRANSPORT transport, |
|
void* p_ref_data, uint8_t res) { |
|
tHID_DEV_DEV_CTB* p_dev = (tHID_DEV_DEV_CTB*)p_ref_data; |
|
|
|
if (p_dev->conn.conn_state != HID_CONN_STATE_SECURITY) { |
|
HIDD_TRACE_WARNING("%s: invalid state (%02x)", __func__, |
|
p_dev->conn.conn_state); |
|
return; |
|
} |
|
|
|
if (res == BTM_SUCCESS) { |
|
HIDD_TRACE_EVENT("%s: security ok", __func__); |
|
p_dev->conn.disc_reason = HID_SUCCESS; |
|
|
|
p_dev->conn.conn_state = HID_CONN_STATE_CONFIG; |
|
L2CA_ConfigReq(p_dev->conn.ctrl_cid, &hd_cb.l2cap_cfg); |
|
} else { |
|
HIDD_TRACE_WARNING("%s: security check failed (%02x)", __func__, res); |
|
p_dev->conn.disc_reason = HID_ERR_AUTH_FAILED; |
|
hidd_conn_disconnect(); |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_connect_ind |
|
* |
|
* Description Handles incoming L2CAP connection (we act as server) |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_connect_ind(const RawAddress& bd_addr, uint16_t cid, |
|
uint16_t psm, uint8_t id) { |
|
tHID_CONN* p_hcon; |
|
tHID_DEV_DEV_CTB* p_dev; |
|
bool accept = TRUE; // accept by default |
|
|
|
HIDD_TRACE_EVENT("%s: psm=%04x cid=%04x id=%02x", __func__, psm, cid, id); |
|
|
|
p_dev = &hd_cb.device; |
|
|
|
if (!hd_cb.allow_incoming) { |
|
HIDD_TRACE_WARNING("%s: incoming connections not allowed, rejecting", |
|
__func__); |
|
L2CA_ConnectRsp(bd_addr, id, cid, L2CAP_CONN_NO_RESOURCES, 0); |
|
return; |
|
} |
|
|
|
if (p_dev->in_use && bd_addr != p_dev->addr) { |
|
HIDD_TRACE_WARNING( |
|
"%s: incoming connections from different device, rejecting", __func__); |
|
L2CA_ConnectRsp(bd_addr, id, cid, L2CAP_CONN_NO_RESOURCES, 0); |
|
return; |
|
} else if (!p_dev->in_use) { |
|
p_dev->in_use = TRUE; |
|
p_dev->addr = bd_addr; |
|
p_dev->state = HIDD_DEV_NO_CONN; |
|
} |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
switch (psm) { |
|
case HID_PSM_INTERRUPT: |
|
if (p_hcon->ctrl_cid == 0) { |
|
accept = FALSE; |
|
HIDD_TRACE_WARNING("%s: incoming INTR without CTRL, rejecting", |
|
__func__); |
|
} |
|
|
|
if (p_hcon->conn_state != HID_CONN_STATE_CONNECTING_INTR) { |
|
accept = FALSE; |
|
HIDD_TRACE_WARNING("%s: incoming INTR in invalid state (%d), rejecting", |
|
__func__, p_hcon->conn_state); |
|
} |
|
|
|
break; |
|
|
|
case HID_PSM_CONTROL: |
|
if (p_hcon->conn_state != HID_CONN_STATE_UNUSED) { |
|
accept = FALSE; |
|
HIDD_TRACE_WARNING("%s: incoming CTRL in invalid state (%d), rejecting", |
|
__func__, p_hcon->conn_state); |
|
} |
|
|
|
break; |
|
|
|
default: |
|
accept = FALSE; |
|
HIDD_TRACE_ERROR("%s: received invalid PSM, rejecting", __func__); |
|
break; |
|
} |
|
|
|
if (!accept) { |
|
L2CA_ConnectRsp(bd_addr, id, cid, L2CAP_CONN_NO_RESOURCES, 0); |
|
return; |
|
} |
|
|
|
// for CTRL we need to go through security and we reply in callback from there |
|
if (psm == HID_PSM_CONTROL) { |
|
p_hcon->conn_flags = 0; |
|
p_hcon->ctrl_cid = cid; |
|
p_hcon->ctrl_id = id; |
|
p_hcon->disc_reason = HID_L2CAP_CONN_FAIL; |
|
|
|
p_hcon->conn_state = HID_CONN_STATE_SECURITY; |
|
if (btm_sec_mx_access_request(p_dev->addr, HID_PSM_CONTROL, FALSE, |
|
BTM_SEC_PROTO_HID, HIDD_NOSEC_CHN, |
|
&hidd_sec_check_complete, |
|
p_dev) == BTM_CMD_STARTED) { |
|
L2CA_ConnectRsp(bd_addr, id, cid, L2CAP_CONN_PENDING, L2CAP_CONN_OK); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// for INTR we go directly to config state |
|
p_hcon->conn_state = HID_CONN_STATE_CONFIG; |
|
p_hcon->intr_cid = cid; |
|
|
|
L2CA_ConnectRsp(bd_addr, id, cid, L2CAP_CONN_OK, L2CAP_CONN_OK); |
|
L2CA_ConfigReq(cid, &hd_cb.l2cap_intr_cfg); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_connect_cfm |
|
* |
|
* Description Handles L2CAP connection response (we act as client) |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_connect_cfm(uint16_t cid, uint16_t result) { |
|
tHID_DEV_DEV_CTB* p_dev = &hd_cb.device; |
|
tHID_CONN* p_hcon = &hd_cb.device.conn; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x result=%d", __func__, cid, result); |
|
|
|
if (p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
return; |
|
} |
|
|
|
if (!(p_hcon->conn_flags & HID_CONN_FLAGS_IS_ORIG) || |
|
((cid == p_hcon->ctrl_cid) && |
|
(p_hcon->conn_state != HID_CONN_STATE_CONNECTING_CTRL)) || |
|
((cid == p_hcon->intr_cid) && |
|
(p_hcon->conn_state != HID_CONN_STATE_CONNECTING_INTR))) { |
|
HIDD_TRACE_WARNING("%s: unexpected", __func__); |
|
return; |
|
} |
|
|
|
if (result != L2CAP_CONN_OK) { |
|
HIDD_TRACE_WARNING("%s: connection failed, now disconnect", __func__); |
|
|
|
if (cid == p_hcon->ctrl_cid) |
|
p_hcon->ctrl_cid = 0; |
|
else |
|
p_hcon->intr_cid = 0; |
|
|
|
hidd_conn_disconnect(); |
|
|
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, |
|
HID_L2CAP_CONN_FAIL | (uint32_t)result, NULL); |
|
return; |
|
} |
|
|
|
/* CTRL connect conf */ |
|
if (cid == p_hcon->ctrl_cid) { |
|
p_hcon->conn_state = HID_CONN_STATE_SECURITY; |
|
p_hcon->disc_reason = |
|
HID_L2CAP_CONN_FAIL; /* in case disconnected before sec completed */ |
|
|
|
btm_sec_mx_access_request(p_dev->addr, HID_PSM_CONTROL, TRUE, |
|
BTM_SEC_PROTO_HID, HIDD_SEC_CHN, |
|
&hidd_sec_check_complete_orig, p_dev); |
|
} else { |
|
p_hcon->conn_state = HID_CONN_STATE_CONFIG; |
|
L2CA_ConfigReq(cid, &hd_cb.l2cap_intr_cfg); |
|
} |
|
|
|
return; |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_config_ind |
|
* |
|
* Description Handles incoming L2CAP configuration request |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_config_ind(uint16_t cid, tL2CAP_CFG_INFO* p_cfg) { |
|
tHID_CONN* p_hcon; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x", __func__, cid); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
return; |
|
} |
|
|
|
if ((!p_cfg->mtu_present) || (p_cfg->mtu > HID_DEV_MTU_SIZE)) |
|
p_hcon->rem_mtu_size = HID_DEV_MTU_SIZE; |
|
else |
|
p_hcon->rem_mtu_size = p_cfg->mtu; |
|
|
|
// accept without changes |
|
p_cfg->flush_to_present = FALSE; |
|
p_cfg->mtu_present = FALSE; |
|
p_cfg->result = L2CAP_CFG_OK; |
|
|
|
if (cid == p_hcon->intr_cid && hd_cb.use_in_qos && !p_cfg->qos_present) { |
|
p_cfg->qos_present = TRUE; |
|
memcpy(&p_cfg->qos, &hd_cb.in_qos, sizeof(FLOW_SPEC)); |
|
} |
|
|
|
L2CA_ConfigRsp(cid, p_cfg); |
|
|
|
// update flags |
|
if (cid == p_hcon->ctrl_cid) { |
|
p_hcon->conn_flags |= HID_CONN_FLAGS_HIS_CTRL_CFG_DONE; |
|
|
|
if ((p_hcon->conn_flags & HID_CONN_FLAGS_IS_ORIG) && |
|
(p_hcon->conn_flags & HID_CONN_FLAGS_MY_CTRL_CFG_DONE)) { |
|
p_hcon->disc_reason = HID_L2CAP_CONN_FAIL; |
|
if ((p_hcon->intr_cid = |
|
L2CA_ConnectReq(HID_PSM_INTERRUPT, hd_cb.device.addr)) == 0) { |
|
hidd_conn_disconnect(); |
|
p_hcon->conn_state = HID_CONN_STATE_UNUSED; |
|
|
|
HIDD_TRACE_WARNING("%s: could not start L2CAP connection for INTR", |
|
__func__); |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, |
|
HID_ERR_L2CAP_FAILED, NULL); |
|
return; |
|
} else { |
|
p_hcon->conn_state = HID_CONN_STATE_CONNECTING_INTR; |
|
} |
|
} |
|
} else { |
|
p_hcon->conn_flags |= HID_CONN_FLAGS_HIS_INTR_CFG_DONE; |
|
} |
|
|
|
hidd_check_config_done(); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_config_cfm |
|
* |
|
* Description Handles incoming L2CAP configuration response |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_config_cfm(uint16_t cid, tL2CAP_CFG_INFO* p_cfg) { |
|
tHID_CONN* p_hcon; |
|
uint32_t reason; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x pcfg->result=%d", __func__, cid, |
|
p_cfg->result); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
return; |
|
} |
|
|
|
if (p_hcon->intr_cid == cid && |
|
p_cfg->result == L2CAP_CFG_UNACCEPTABLE_PARAMS && p_cfg->qos_present) { |
|
tL2CAP_CFG_INFO new_qos; |
|
|
|
// QoS parameters not accepted for intr, try again with host proposal |
|
|
|
memcpy(&new_qos, &hd_cb.l2cap_intr_cfg, sizeof(new_qos)); |
|
memcpy(&new_qos.qos, &p_cfg->qos, sizeof(FLOW_SPEC)); |
|
new_qos.qos_present = TRUE; |
|
|
|
HIDD_TRACE_WARNING("%s: config failed, retry", __func__); |
|
|
|
L2CA_ConfigReq(cid, &new_qos); |
|
return; |
|
} else if (p_hcon->intr_cid == cid && |
|
p_cfg->result == L2CAP_CFG_UNKNOWN_OPTIONS) { |
|
// QoS not understood by remote device, try configuring without QoS |
|
|
|
HIDD_TRACE_WARNING("%s: config failed, retry without QoS", __func__); |
|
|
|
L2CA_ConfigReq(cid, &hd_cb.l2cap_cfg); |
|
return; |
|
} else if (p_cfg->result != L2CAP_CFG_OK) { |
|
HIDD_TRACE_WARNING("%s: config failed, disconnecting", __func__); |
|
|
|
hidd_conn_disconnect(); |
|
reason = HID_L2CAP_CFG_FAIL | (uint32_t)p_cfg->result; |
|
|
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, reason, NULL); |
|
return; |
|
} |
|
|
|
// update flags |
|
if (cid == p_hcon->ctrl_cid) { |
|
p_hcon->conn_flags |= HID_CONN_FLAGS_MY_CTRL_CFG_DONE; |
|
|
|
if ((p_hcon->conn_flags & HID_CONN_FLAGS_IS_ORIG) && |
|
(p_hcon->conn_flags & HID_CONN_FLAGS_HIS_CTRL_CFG_DONE)) { |
|
p_hcon->disc_reason = HID_L2CAP_CONN_FAIL; |
|
if ((p_hcon->intr_cid = |
|
L2CA_ConnectReq(HID_PSM_INTERRUPT, hd_cb.device.addr)) == 0) { |
|
hidd_conn_disconnect(); |
|
p_hcon->conn_state = HID_CONN_STATE_UNUSED; |
|
|
|
HIDD_TRACE_WARNING("%s: could not start L2CAP connection for INTR", |
|
__func__); |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, |
|
HID_ERR_L2CAP_FAILED, NULL); |
|
return; |
|
} else { |
|
p_hcon->conn_state = HID_CONN_STATE_CONNECTING_INTR; |
|
} |
|
} |
|
} else { |
|
p_hcon->conn_flags |= HID_CONN_FLAGS_MY_INTR_CFG_DONE; |
|
} |
|
|
|
hidd_check_config_done(); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_disconnect_ind |
|
* |
|
* Description Handler incoming L2CAP disconnection request |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_disconnect_ind(uint16_t cid, bool ack_needed) { |
|
tHID_CONN* p_hcon; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x ack_needed=%d", __func__, cid, ack_needed); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->conn_state == HID_CONN_STATE_UNUSED || |
|
(p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid)) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
return; |
|
} |
|
|
|
if (ack_needed) L2CA_DisconnectRsp(cid); |
|
|
|
p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING; |
|
|
|
if (cid == p_hcon->ctrl_cid) |
|
p_hcon->ctrl_cid = 0; |
|
else |
|
p_hcon->intr_cid = 0; |
|
|
|
if ((p_hcon->ctrl_cid == 0) && (p_hcon->intr_cid == 0)) { |
|
HIDD_TRACE_EVENT("%s: INTR and CTRL disconnected", __func__); |
|
|
|
// clean any outstanding data on intr |
|
if (hd_cb.pending_data) { |
|
osi_free(hd_cb.pending_data); |
|
hd_cb.pending_data = NULL; |
|
} |
|
|
|
hd_cb.device.state = HIDD_DEV_NO_CONN; |
|
p_hcon->conn_state = HID_CONN_STATE_UNUSED; |
|
|
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, p_hcon->disc_reason, |
|
NULL); |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_disconnect_cfm |
|
* |
|
* Description Handles L2CAP disconection response |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_disconnect_cfm(uint16_t cid, uint16_t result) { |
|
tHID_CONN* p_hcon; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x result=%d", __func__, cid, result); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->conn_state == HID_CONN_STATE_UNUSED || |
|
(p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid)) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
return; |
|
} |
|
|
|
if (cid == p_hcon->ctrl_cid) { |
|
p_hcon->ctrl_cid = 0; |
|
} else { |
|
p_hcon->intr_cid = 0; |
|
|
|
// now disconnect CTRL |
|
L2CA_DisconnectReq(p_hcon->ctrl_cid); |
|
} |
|
|
|
if ((p_hcon->ctrl_cid == 0) && (p_hcon->intr_cid == 0)) { |
|
HIDD_TRACE_EVENT("%s: INTR and CTRL disconnected", __func__); |
|
|
|
hd_cb.device.state = HIDD_DEV_NO_CONN; |
|
p_hcon->conn_state = HID_CONN_STATE_UNUSED; |
|
|
|
if (hd_cb.pending_vc_unplug) { |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_VC_UNPLUG, |
|
p_hcon->disc_reason, NULL); |
|
hd_cb.pending_vc_unplug = FALSE; |
|
} else { |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, |
|
p_hcon->disc_reason, NULL); |
|
} |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_cong_ind |
|
* |
|
* Description Handles L2CAP congestion status event |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_cong_ind(uint16_t cid, bool congested) { |
|
tHID_CONN* p_hcon; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x congested=%d", __func__, cid, congested); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->conn_state == HID_CONN_STATE_UNUSED || |
|
(p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid)) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
return; |
|
} |
|
|
|
if (congested) { |
|
p_hcon->conn_flags |= HID_CONN_FLAGS_CONGESTED; |
|
} else { |
|
p_hcon->conn_flags &= ~HID_CONN_FLAGS_CONGESTED; |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_l2cif_data_ind |
|
* |
|
* Description Handler incoming data on L2CAP channel |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
static void hidd_l2cif_data_ind(uint16_t cid, BT_HDR* p_msg) { |
|
tHID_CONN* p_hcon; |
|
uint8_t* p_data = (uint8_t*)(p_msg + 1) + p_msg->offset; |
|
uint8_t msg_type, param; |
|
bool err = FALSE; |
|
|
|
HIDD_TRACE_EVENT("%s: cid=%04x", __func__, cid); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->conn_state == HID_CONN_STATE_UNUSED || |
|
(p_hcon->ctrl_cid != cid && p_hcon->intr_cid != cid)) { |
|
HIDD_TRACE_WARNING("%s: unknown cid", __func__); |
|
osi_free(p_msg); |
|
return; |
|
} |
|
|
|
msg_type = HID_GET_TRANS_FROM_HDR(*p_data); |
|
param = HID_GET_PARAM_FROM_HDR(*p_data); |
|
|
|
if (msg_type == HID_TRANS_DATA && cid == p_hcon->intr_cid) { |
|
// skip HID header |
|
p_msg->offset++; |
|
p_msg->len--; |
|
|
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_INTR_DATA, 0, p_msg); |
|
return; |
|
} |
|
|
|
switch (msg_type) { |
|
case HID_TRANS_GET_REPORT: |
|
// at this stage we don't know if Report Id shall be included in request |
|
// so we pass complete packet in callback and let other code analyze this |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_GET_REPORT, |
|
!!(param & HID_PAR_GET_REP_BUFSIZE_FOLLOWS), p_msg); |
|
break; |
|
|
|
case HID_TRANS_SET_REPORT: |
|
// as above |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_SET_REPORT, 0, p_msg); |
|
break; |
|
|
|
case HID_TRANS_GET_IDLE: |
|
hidd_conn_send_data(HID_CHANNEL_CTRL, HID_TRANS_DATA, |
|
HID_PAR_REP_TYPE_OTHER, hd_cb.device.idle_time, 0, |
|
NULL); |
|
osi_free(p_msg); |
|
break; |
|
|
|
case HID_TRANS_SET_IDLE: |
|
if (p_msg->len != 2) { |
|
HIDD_TRACE_ERROR("%s: invalid len (%d) set idle request received", |
|
__func__, p_msg->len); |
|
err = TRUE; |
|
} else { |
|
hd_cb.device.idle_time = p_data[1]; |
|
HIDD_TRACE_DEBUG("%s: idle_time = %d", __func__, |
|
hd_cb.device.idle_time); |
|
if (hd_cb.device.idle_time) { |
|
HIDD_TRACE_WARNING( |
|
"%s: idle_time of %d ms not supported by HID Device", __func__, |
|
(hd_cb.device.idle_time * 4)); |
|
err = TRUE; |
|
} |
|
} |
|
if (!err) { |
|
hidd_conn_send_data(0, HID_TRANS_HANDSHAKE, |
|
HID_PAR_HANDSHAKE_RSP_SUCCESS, 0, 0, NULL); |
|
} else { |
|
hidd_conn_send_data(0, HID_TRANS_HANDSHAKE, |
|
HID_PAR_HANDSHAKE_RSP_ERR_INVALID_PARAM, 0, 0, |
|
NULL); |
|
} |
|
osi_free(p_msg); |
|
break; |
|
|
|
case HID_TRANS_GET_PROTOCOL: |
|
hidd_conn_send_data(HID_CHANNEL_CTRL, HID_TRANS_DATA, |
|
HID_PAR_REP_TYPE_OTHER, !hd_cb.device.boot_mode, 0, |
|
NULL); |
|
osi_free(p_msg); |
|
break; |
|
|
|
case HID_TRANS_SET_PROTOCOL: |
|
hd_cb.device.boot_mode = !(param & HID_PAR_PROTOCOL_MASK); |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_SET_PROTOCOL, |
|
param & HID_PAR_PROTOCOL_MASK, NULL); |
|
hidd_conn_send_data(0, HID_TRANS_HANDSHAKE, HID_PAR_HANDSHAKE_RSP_SUCCESS, |
|
0, 0, NULL); |
|
osi_free(p_msg); |
|
break; |
|
|
|
case HID_TRANS_CONTROL: |
|
switch (param) { |
|
case HID_PAR_CONTROL_SUSPEND: |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_SUSPEND, 0, NULL); |
|
break; |
|
|
|
case HID_PAR_CONTROL_EXIT_SUSPEND: |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_EXIT_SUSPEND, 0, |
|
NULL); |
|
break; |
|
|
|
case HID_PAR_CONTROL_VIRTUAL_CABLE_UNPLUG: |
|
hidd_conn_disconnect(); |
|
|
|
// set flag so we can notify properly when disconnected |
|
hd_cb.pending_vc_unplug = TRUE; |
|
break; |
|
} |
|
|
|
osi_free(p_msg); |
|
break; |
|
|
|
case HID_TRANS_DATA: |
|
default: |
|
HIDD_TRACE_WARNING("%s: got unsupported msg (%d)", __func__, msg_type); |
|
hidd_conn_send_data(0, HID_TRANS_HANDSHAKE, |
|
HID_PAR_HANDSHAKE_RSP_ERR_UNSUPPORTED_REQ, 0, 0, |
|
NULL); |
|
osi_free(p_msg); |
|
break; |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_conn_reg |
|
* |
|
* Description Registers L2CAP channels |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
tHID_STATUS hidd_conn_reg(void) { |
|
HIDD_TRACE_API("%s", __func__); |
|
|
|
memset(&hd_cb.l2cap_cfg, 0, sizeof(tL2CAP_CFG_INFO)); |
|
|
|
hd_cb.l2cap_cfg.mtu_present = TRUE; |
|
hd_cb.l2cap_cfg.mtu = HID_DEV_MTU_SIZE; |
|
hd_cb.l2cap_cfg.flush_to_present = TRUE; |
|
hd_cb.l2cap_cfg.flush_to = HID_DEV_FLUSH_TO; |
|
|
|
memset(&hd_cb.l2cap_intr_cfg, 0, sizeof(tL2CAP_CFG_INFO)); |
|
hd_cb.l2cap_intr_cfg.mtu_present = TRUE; |
|
hd_cb.l2cap_intr_cfg.mtu = HID_DEV_MTU_SIZE; |
|
hd_cb.l2cap_intr_cfg.flush_to_present = TRUE; |
|
hd_cb.l2cap_intr_cfg.flush_to = HID_DEV_FLUSH_TO; |
|
|
|
if (!L2CA_Register(HID_PSM_CONTROL, (tL2CAP_APPL_INFO*)&dev_reg_info)) { |
|
HIDD_TRACE_ERROR("HID Control (device) registration failed"); |
|
return (HID_ERR_L2CAP_FAILED); |
|
} |
|
|
|
if (!L2CA_Register(HID_PSM_INTERRUPT, (tL2CAP_APPL_INFO*)&dev_reg_info)) { |
|
L2CA_Deregister(HID_PSM_CONTROL); |
|
HIDD_TRACE_ERROR("HID Interrupt (device) registration failed"); |
|
return (HID_ERR_L2CAP_FAILED); |
|
} |
|
|
|
return (HID_SUCCESS); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_conn_dereg |
|
* |
|
* Description Deregisters L2CAP channels |
|
* |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
void hidd_conn_dereg(void) { |
|
HIDD_TRACE_API("%s", __func__); |
|
|
|
L2CA_Deregister(HID_PSM_CONTROL); |
|
L2CA_Deregister(HID_PSM_INTERRUPT); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_conn_initiate |
|
* |
|
* Description Initiates HID connection to plugged device |
|
* |
|
* Returns HID_SUCCESS |
|
* |
|
******************************************************************************/ |
|
tHID_STATUS hidd_conn_initiate(void) { |
|
tHID_DEV_DEV_CTB* p_dev = &hd_cb.device; |
|
|
|
HIDD_TRACE_API("%s", __func__); |
|
|
|
if (!p_dev->in_use) { |
|
HIDD_TRACE_WARNING("%s: no virtual cable established", __func__); |
|
return (HID_ERR_NOT_REGISTERED); |
|
} |
|
|
|
if (p_dev->conn.conn_state != HID_CONN_STATE_UNUSED) { |
|
HIDD_TRACE_WARNING("%s: connection already in progress", __func__); |
|
return (HID_ERR_CONN_IN_PROCESS); |
|
} |
|
|
|
p_dev->conn.ctrl_cid = 0; |
|
p_dev->conn.intr_cid = 0; |
|
p_dev->conn.disc_reason = HID_L2CAP_CONN_FAIL; |
|
|
|
p_dev->conn.conn_flags = HID_CONN_FLAGS_IS_ORIG; |
|
|
|
BTM_SetOutService(p_dev->addr, BTM_SEC_SERVICE_HIDD_SEC_CTRL, HIDD_SEC_CHN); |
|
|
|
/* Check if L2CAP started the connection process */ |
|
if ((p_dev->conn.ctrl_cid = L2CA_ConnectReq(HID_PSM_CONTROL, p_dev->addr)) == |
|
0) { |
|
HIDD_TRACE_WARNING("%s: could not start L2CAP connection", __func__); |
|
hd_cb.callback(hd_cb.device.addr, HID_DHOST_EVT_CLOSE, HID_ERR_L2CAP_FAILED, |
|
NULL); |
|
} else { |
|
p_dev->conn.conn_state = HID_CONN_STATE_CONNECTING_CTRL; |
|
} |
|
|
|
return (HID_SUCCESS); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_conn_disconnect |
|
* |
|
* Description Disconnects existing HID connection |
|
* |
|
* Returns HID_SUCCESS |
|
* |
|
******************************************************************************/ |
|
tHID_STATUS hidd_conn_disconnect(void) { |
|
tHID_CONN* p_hcon; |
|
|
|
HIDD_TRACE_API("%s", __func__); |
|
|
|
// clean any outstanding data on intr |
|
if (hd_cb.pending_data) { |
|
osi_free(hd_cb.pending_data); |
|
hd_cb.pending_data = NULL; |
|
} |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if ((p_hcon->ctrl_cid != 0) || (p_hcon->intr_cid != 0)) { |
|
p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING; |
|
|
|
/* Set l2cap idle timeout to 0 (so ACL link is disconnected |
|
* immediately after last channel is closed) */ |
|
L2CA_SetIdleTimeoutByBdAddr(hd_cb.device.addr, 0, BT_TRANSPORT_BR_EDR); |
|
|
|
if (p_hcon->intr_cid) { |
|
L2CA_DisconnectReq(p_hcon->intr_cid); |
|
} else if (p_hcon->ctrl_cid) { |
|
L2CA_DisconnectReq(p_hcon->ctrl_cid); |
|
} |
|
} else { |
|
HIDD_TRACE_WARNING("%s: already disconnected", __func__); |
|
p_hcon->conn_state = HID_CONN_STATE_UNUSED; |
|
} |
|
|
|
return (HID_SUCCESS); |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function hidd_conn_send_data |
|
* |
|
* Description Sends data to host |
|
* |
|
* Returns tHID_STATUS |
|
* |
|
******************************************************************************/ |
|
tHID_STATUS hidd_conn_send_data(uint8_t channel, uint8_t msg_type, |
|
uint8_t param, uint8_t data, uint16_t len, |
|
uint8_t* p_data) { |
|
tHID_CONN* p_hcon; |
|
BT_HDR* p_buf; |
|
uint8_t* p_out; |
|
uint16_t cid; |
|
uint16_t buf_size; |
|
|
|
HIDD_TRACE_VERBOSE("%s: channel(%d), msg_type(%d), len(%d)", __func__, |
|
channel, msg_type, len); |
|
|
|
p_hcon = &hd_cb.device.conn; |
|
|
|
if (p_hcon->conn_flags & HID_CONN_FLAGS_CONGESTED) { |
|
return HID_ERR_CONGESTED; |
|
} |
|
|
|
switch (msg_type) { |
|
case HID_TRANS_HANDSHAKE: |
|
case HID_TRANS_CONTROL: |
|
cid = p_hcon->ctrl_cid; |
|
buf_size = HID_CONTROL_BUF_SIZE; |
|
break; |
|
case HID_TRANS_DATA: |
|
if (channel == HID_CHANNEL_CTRL) { |
|
cid = p_hcon->ctrl_cid; |
|
buf_size = HID_CONTROL_BUF_SIZE; |
|
} else { |
|
cid = p_hcon->intr_cid; |
|
buf_size = HID_INTERRUPT_BUF_SIZE; |
|
} |
|
break; |
|
default: |
|
return (HID_ERR_INVALID_PARAM); |
|
} |
|
|
|
p_buf = (BT_HDR*)osi_malloc(buf_size); |
|
if (p_buf == NULL) return (HID_ERR_NO_RESOURCES); |
|
|
|
p_buf->offset = L2CAP_MIN_OFFSET; |
|
|
|
p_out = (uint8_t*)(p_buf + 1) + p_buf->offset; |
|
|
|
*p_out = HID_BUILD_HDR(msg_type, param); |
|
p_out++; |
|
|
|
p_buf->len = 1; // start with header only |
|
|
|
// add report id prefix only if non-zero (which is reserved) |
|
if (msg_type == HID_TRANS_DATA && (data || param == HID_PAR_REP_TYPE_OTHER)) { |
|
*p_out = data; // report_id |
|
p_out++; |
|
p_buf->len++; |
|
} |
|
|
|
if (len > 0 && p_data != NULL) { |
|
memcpy(p_out, p_data, len); |
|
p_buf->len += len; |
|
} |
|
|
|
// check if connected |
|
if (hd_cb.device.state != HIDD_DEV_CONNECTED) { |
|
// for DATA on intr we hold transfer and try to reconnect |
|
if (msg_type == HID_TRANS_DATA && cid == p_hcon->intr_cid) { |
|
// drop previous data, we do not queue it for now |
|
if (hd_cb.pending_data) { |
|
osi_free(hd_cb.pending_data); |
|
} |
|
|
|
hd_cb.pending_data = p_buf; |
|
|
|
if (hd_cb.device.conn.conn_state == HID_CONN_STATE_UNUSED) { |
|
hidd_conn_initiate(); |
|
} |
|
|
|
return HID_SUCCESS; |
|
} |
|
|
|
return HID_ERR_NO_CONNECTION; |
|
} |
|
|
|
#ifdef REPORT_TRANSFER_TIMESTAMP |
|
if (report_transfer) { |
|
HIDD_TRACE_ERROR("%s: report sent", __func__); |
|
} |
|
#endif |
|
HIDD_TRACE_VERBOSE("%s: report sent", __func__); |
|
|
|
if (!L2CA_DataWrite(cid, p_buf)) return (HID_ERR_CONGESTED); |
|
|
|
return (HID_SUCCESS); |
|
}
|
|
|