esp-idf/components/bt/host/bluedroid/stack/hid/hidd_conn.c
liqigan bc1b01a966 change(bt/bluedroid): Optimize HIDD sending interrupt report behavior
HID device is allowed to trigger reconnection by sending an interrupt
report. When reconnection is in progress, no more interrupt report can
be sent until the procedure is accomplished.
2023-09-08 03:35:05 +00:00

785 lines
31 KiB
C

/******************************************************************************
*
* 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 "btm_int.h"
#include "hid_conn.h"
#include "hid_int.h"
#include "osi/allocator.h"
#include "osi/osi.h"
#include "stack/btm_api.h"
#include "stack/btu.h"
#include "stack/hidd_api.h"
#include "stack/hiddefs.h"
#include "stack/l2c_api.h"
#include "stack/l2cdefs.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if (HID_DEV_INCLUDED == TRUE)
static void hidd_l2cif_connect_ind(BD_ADDR 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(void)
{
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 BD_ADDR 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 BD_ADDR 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(BD_ADDR 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 && memcmp(bd_addr, p_dev->addr, sizeof(BD_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;
memcpy(p_dev->addr, bd_addr, sizeof(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, conn_state=%d", __func__, cid, result, p_hcon->conn_state);
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 && (p_hcon->conn_state != HID_CONN_STATE_DISCONNECTING_INTR))) ||
((cid == p_hcon->intr_cid) && (p_hcon->conn_state != HID_CONN_STATE_CONNECTING_INTR) && (p_hcon->conn_state != HID_CONN_STATE_DISCONNECTING_CTRL))) {
HIDD_TRACE_WARNING("%s: unexpected, cid:0x%04x, ctrl_cid:0x%04x, intr_cid:0x%04x, conn_state:%d", __func__, cid,
p_hcon->ctrl_cid, p_hcon->intr_cid, p_hcon->conn_state);
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) {
p_hcon->conn_state = HID_CONN_STATE_UNUSED;
hidd_conn_disconnect();
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) {
p_hcon->conn_state = HID_CONN_STATE_UNUSED;
hidd_conn_disconnect();
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);
if (cid == p_hcon->ctrl_cid) {
p_hcon->ctrl_cid = 0;
p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING_CTRL;
} else {
p_hcon->intr_cid = 0;
p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING_INTR;
}
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)) {
/* 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) {
p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING_INTR;
L2CA_DisconnectReq(p_hcon->intr_cid);
} else if (p_hcon->ctrl_cid) {
p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING_CTRL;
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_TRACE_WARNING("%s: try to reconnect!", __func__);
return 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 (p_hcon->conn_flags & HID_CONN_FLAGS_CONGESTED || !L2CA_DataWrite(cid, p_buf))
return (HID_ERR_CONGESTED);
return (HID_SUCCESS);
}
#endif