freebsd-dev/sys/netgraph/bluetooth/l2cap/ng_l2cap_cmds.c
Maksim Yevmenkin 82e1becc5f Fix dangling callout problem in the Bluetooth L2CAP code that leads to
panic. The panic happens when outgoing L2CAP connection descriptor is
deleted with the L2CAP command(s) pending in the queue. In this case when
the last L2CAP command is deleted (due to cleanup) and reference counter
for the L2CAP connection goes down to zero the auto disconnect timeout
is incorrectly set. pjd gets credit for tracking this down and committing
bandaid.

Reported by:	Jonatan B <onatan at gmail dot com>
MFC after:	3 days
2005-08-31 18:13:23 +00:00

389 lines
10 KiB
C

/*
* ng_l2cap_cmds.c
*/
/*-
* Copyright (c) Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: ng_l2cap_cmds.c,v 1.2 2003/09/08 19:11:45 max Exp $
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/endian.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/queue.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/bluetooth/include/ng_bluetooth.h>
#include <netgraph/bluetooth/include/ng_hci.h>
#include <netgraph/bluetooth/include/ng_l2cap.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_var.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_cmds.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_evnt.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_llpi.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_ulpi.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_misc.h>
/******************************************************************************
******************************************************************************
** L2CAP commands processing module
******************************************************************************
******************************************************************************/
/*
* Process L2CAP command queue on connection
*/
void
ng_l2cap_con_wakeup(ng_l2cap_con_p con)
{
ng_l2cap_cmd_p cmd = NULL;
struct mbuf *m = NULL;
int error = 0;
/* Find first non-pending command in the queue */
TAILQ_FOREACH(cmd, &con->cmd_list, next) {
KASSERT((cmd->con == con),
("%s: %s - invalid connection pointer!\n",
__func__, NG_NODE_NAME(con->l2cap->node)));
if (!(cmd->flags & NG_L2CAP_CMD_PENDING))
break;
}
if (cmd == NULL)
return;
/* Detach command packet */
m = cmd->aux;
cmd->aux = NULL;
/* Process command */
switch (cmd->code) {
case NG_L2CAP_CMD_REJ:
case NG_L2CAP_DISCON_RSP:
case NG_L2CAP_ECHO_RSP:
case NG_L2CAP_INFO_RSP:
ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
ng_l2cap_unlink_cmd(cmd);
ng_l2cap_free_cmd(cmd);
break;
case NG_L2CAP_CON_REQ:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
if (error != 0) {
ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token,
NG_L2CAP_NO_RESOURCES, 0);
ng_l2cap_free_chan(cmd->ch); /* will free commands */
} else
ng_l2cap_command_timeout(cmd,
bluetooth_l2cap_rtx_timeout());
break;
case NG_L2CAP_CON_RSP:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
ng_l2cap_unlink_cmd(cmd);
if (cmd->ch != NULL) {
ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token,
(error == 0)? NG_L2CAP_SUCCESS :
NG_L2CAP_NO_RESOURCES);
if (error != 0)
ng_l2cap_free_chan(cmd->ch);
}
ng_l2cap_free_cmd(cmd);
break;
case NG_L2CAP_CFG_REQ:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
if (error != 0) {
ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token,
NG_L2CAP_NO_RESOURCES);
ng_l2cap_unlink_cmd(cmd);
ng_l2cap_free_cmd(cmd);
} else
ng_l2cap_command_timeout(cmd,
bluetooth_l2cap_rtx_timeout());
break;
case NG_L2CAP_CFG_RSP:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
ng_l2cap_unlink_cmd(cmd);
if (cmd->ch != NULL)
ng_l2cap_l2ca_cfg_rsp_rsp(cmd->ch, cmd->token,
(error == 0)? NG_L2CAP_SUCCESS :
NG_L2CAP_NO_RESOURCES);
ng_l2cap_free_cmd(cmd);
break;
case NG_L2CAP_DISCON_REQ:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token,
(error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES);
if (error != 0)
ng_l2cap_free_chan(cmd->ch); /* XXX free channel */
else
ng_l2cap_command_timeout(cmd,
bluetooth_l2cap_rtx_timeout());
break;
case NG_L2CAP_ECHO_REQ:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
if (error != 0) {
ng_l2cap_l2ca_ping_rsp(con, cmd->token,
NG_L2CAP_NO_RESOURCES, NULL);
ng_l2cap_unlink_cmd(cmd);
ng_l2cap_free_cmd(cmd);
} else
ng_l2cap_command_timeout(cmd,
bluetooth_l2cap_rtx_timeout());
break;
case NG_L2CAP_INFO_REQ:
error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
if (error != 0) {
ng_l2cap_l2ca_get_info_rsp(con, cmd->token,
NG_L2CAP_NO_RESOURCES, NULL);
ng_l2cap_unlink_cmd(cmd);
ng_l2cap_free_cmd(cmd);
} else
ng_l2cap_command_timeout(cmd,
bluetooth_l2cap_rtx_timeout());
break;
case NGM_L2CAP_L2CA_WRITE: {
int length = m->m_pkthdr.len;
if (cmd->ch->dcid == NG_L2CAP_CLT_CID) {
m = ng_l2cap_prepend(m, sizeof(ng_l2cap_clt_hdr_t));
if (m == NULL)
error = ENOBUFS;
else
mtod(m, ng_l2cap_clt_hdr_t *)->psm =
htole16(cmd->ch->psm);
}
if (error == 0)
error = ng_l2cap_lp_send(con, cmd->ch->dcid, m);
ng_l2cap_l2ca_write_rsp(cmd->ch, cmd->token,
(error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES,
length);
ng_l2cap_unlink_cmd(cmd);
ng_l2cap_free_cmd(cmd);
} break;
/* XXX FIXME add other commands */
default:
panic(
"%s: %s - unknown command code=%d\n",
__func__, NG_NODE_NAME(con->l2cap->node), cmd->code);
break;
}
} /* ng_l2cap_con_wakeup */
/*
* We have failed to open ACL connection to the remote unit. Could be negative
* confirmation or timeout. So fail any "delayed" commands, notify upper layer,
* remove all channels and remove connection descriptor.
*/
void
ng_l2cap_con_fail(ng_l2cap_con_p con, u_int16_t result)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_p cmd = NULL;
ng_l2cap_chan_p ch = NULL;
NG_L2CAP_INFO(
"%s: %s - ACL connection failed, result=%d\n",
__func__, NG_NODE_NAME(l2cap->node), result);
/* Connection is dying */
con->flags |= NG_L2CAP_CON_DYING;
/* Clean command queue */
while (!TAILQ_EMPTY(&con->cmd_list)) {
cmd = TAILQ_FIRST(&con->cmd_list);
ng_l2cap_unlink_cmd(cmd);
if(cmd->flags & NG_L2CAP_CMD_PENDING)
ng_l2cap_command_untimeout(cmd);
KASSERT((cmd->con == con),
("%s: %s - invalid connection pointer!\n",
__func__, NG_NODE_NAME(l2cap->node)));
switch (cmd->code) {
case NG_L2CAP_CMD_REJ:
case NG_L2CAP_DISCON_RSP:
case NG_L2CAP_ECHO_RSP:
case NG_L2CAP_INFO_RSP:
break;
case NG_L2CAP_CON_REQ:
ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, result, 0);
break;
case NG_L2CAP_CON_RSP:
if (cmd->ch != NULL)
ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token,
result);
break;
case NG_L2CAP_CFG_REQ:
case NG_L2CAP_CFG_RSP:
case NGM_L2CAP_L2CA_WRITE:
ng_l2cap_l2ca_discon_ind(cmd->ch);
break;
case NG_L2CAP_DISCON_REQ:
ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token,
NG_L2CAP_SUCCESS);
break;
case NG_L2CAP_ECHO_REQ:
ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token,
result, NULL);
break;
case NG_L2CAP_INFO_REQ:
ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token,
result, NULL);
break;
/* XXX FIXME add other commands */
default:
panic(
"%s: %s - unexpected command code=%d\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->code);
break;
}
if (cmd->ch != NULL)
ng_l2cap_free_chan(cmd->ch);
ng_l2cap_free_cmd(cmd);
}
/*
* There still might be channels (in OPEN state?) that
* did not submit any commands, so diconnect them
*/
LIST_FOREACH(ch, &l2cap->chan_list, next)
if (ch->con == con)
ng_l2cap_l2ca_discon_ind(ch);
/* Free connection descriptor */
ng_l2cap_free_con(con);
} /* ng_l2cap_con_fail */
/*
* Process L2CAP command timeout. In general - notify upper layer and destroy
* channel. Do not pay much attension to return code, just do our best.
*/
void
ng_l2cap_process_command_timeout(node_p node, hook_p hook, void *arg1, int arg2)
{
ng_l2cap_p l2cap = NULL;
ng_l2cap_con_p con = NULL;
ng_l2cap_cmd_p cmd = NULL;
u_int16_t con_handle = (arg2 & 0x0ffff);
u_int8_t ident = ((arg2 >> 16) & 0xff);
if (NG_NODE_NOT_VALID(node)) {
printf("%s: Netgraph node is not valid\n", __func__);
return;
}
l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node);
con = ng_l2cap_con_by_handle(l2cap, con_handle);
if (con == NULL) {
NG_L2CAP_ALERT(
"%s: %s - could not find connection, con_handle=%d\n",
__func__, NG_NODE_NAME(node), con_handle);
return;
}
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd == NULL) {
NG_L2CAP_ALERT(
"%s: %s - could not find command, con_handle=%d, ident=%d\n",
__func__, NG_NODE_NAME(node), con_handle, ident);
return;
}
cmd->flags &= ~NG_L2CAP_CMD_PENDING;
ng_l2cap_unlink_cmd(cmd);
switch (cmd->code) {
case NG_L2CAP_CON_REQ:
ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT, 0);
ng_l2cap_free_chan(cmd->ch);
break;
case NG_L2CAP_CFG_REQ:
ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT);
break;
case NG_L2CAP_DISCON_REQ:
ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT);
ng_l2cap_free_chan(cmd->ch); /* XXX free channel */
break;
case NG_L2CAP_ECHO_REQ:
/* Echo request timed out. Let the upper layer know */
ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token,
NG_L2CAP_TIMEOUT, NULL);
break;
case NG_L2CAP_INFO_REQ:
/* Info request timed out. Let the upper layer know */
ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token,
NG_L2CAP_TIMEOUT, NULL);
break;
/* XXX FIXME add other commands */
default:
panic(
"%s: %s - unexpected command code=%d\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->code);
break;
}
ng_l2cap_free_cmd(cmd);
} /* ng_l2cap_process_command_timeout */