dect
/
linux-2.6
Archived
13
0
Fork 0

Bluetooth: Handle physical link completion

Several different actions may be taken when an AMP physical link
becomes available.  A channel being created on an AMP controller must
continue the connection process.  A channel being moved needs to
either send a move request or a move response.  A failed physical link
will revert to using a BR/EDR controller if possible.

Signed-off-by: Mat Martineau <mathewm@codeaurora.org>
Acked-by: Marcel Holtmann <marcel@holtmann.org>
Acked-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
Signed-off-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk>
This commit is contained in:
Mat Martineau 2012-10-23 15:24:17 -07:00 committed by Gustavo Padovan
parent 3fd71a0a43
commit 8eb200bd2f
1 changed files with 164 additions and 0 deletions

View File

@ -1016,6 +1016,19 @@ void l2cap_send_conn_req(struct l2cap_chan *chan)
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, sizeof(req), &req);
}
static void l2cap_send_create_chan_req(struct l2cap_chan *chan, u8 amp_id)
{
struct l2cap_create_chan_req req;
req.scid = cpu_to_le16(chan->scid);
req.psm = chan->psm;
req.amp_id = amp_id;
chan->ident = l2cap_get_ident(chan->conn);
l2cap_send_cmd(chan->conn, chan->ident, L2CAP_CREATE_CHAN_REQ,
sizeof(req), &req);
}
static void l2cap_move_setup(struct l2cap_chan *chan)
{
struct sk_buff *skb;
@ -4187,6 +4200,25 @@ static int l2cap_create_channel_req(struct l2cap_conn *conn,
return 0;
}
static void l2cap_send_move_chan_req(struct l2cap_chan *chan, u8 dest_amp_id)
{
struct l2cap_move_chan_req req;
u8 ident;
BT_DBG("chan %p, dest_amp_id %d", chan, dest_amp_id);
ident = l2cap_get_ident(chan->conn);
chan->ident = ident;
req.icid = cpu_to_le16(chan->scid);
req.dest_amp_id = dest_amp_id;
l2cap_send_cmd(chan->conn, ident, L2CAP_MOVE_CHAN_REQ, sizeof(req),
&req);
__set_chan_timer(chan, L2CAP_MOVE_TIMEOUT);
}
static void l2cap_send_move_chan_rsp(struct l2cap_chan *chan, u16 result)
{
struct l2cap_move_chan_rsp rsp;
@ -4363,6 +4395,138 @@ static void l2cap_logical_cfm(struct l2cap_chan *chan, struct hci_chan *hchan,
}
}
static void l2cap_do_create(struct l2cap_chan *chan, int result,
u8 local_amp_id, u8 remote_amp_id)
{
if (!test_bit(CONF_CONNECT_PEND, &chan->conf_state)) {
struct l2cap_conn_rsp rsp;
char buf[128];
rsp.scid = cpu_to_le16(chan->dcid);
rsp.dcid = cpu_to_le16(chan->scid);
/* Incoming channel on AMP */
if (result == L2CAP_CR_SUCCESS) {
/* Send successful response */
rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS);
rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
} else {
/* Send negative response */
rsp.result = cpu_to_le16(L2CAP_CR_NO_MEM);
rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
}
l2cap_send_cmd(chan->conn, chan->ident, L2CAP_CREATE_CHAN_RSP,
sizeof(rsp), &rsp);
if (result == L2CAP_CR_SUCCESS) {
__l2cap_state_change(chan, BT_CONFIG);
set_bit(CONF_REQ_SENT, &chan->conf_state);
l2cap_send_cmd(chan->conn, l2cap_get_ident(chan->conn),
L2CAP_CONF_REQ,
l2cap_build_conf_req(chan, buf), buf);
chan->num_conf_req++;
}
} else {
/* Outgoing channel on AMP */
if (result == L2CAP_CR_SUCCESS) {
chan->local_amp_id = local_amp_id;
l2cap_send_create_chan_req(chan, remote_amp_id);
} else {
/* Revert to BR/EDR connect */
l2cap_send_conn_req(chan);
}
}
}
static void l2cap_do_move_initiate(struct l2cap_chan *chan, u8 local_amp_id,
u8 remote_amp_id)
{
l2cap_move_setup(chan);
chan->move_id = local_amp_id;
chan->move_state = L2CAP_MOVE_WAIT_RSP;
l2cap_send_move_chan_req(chan, remote_amp_id);
}
static void l2cap_do_move_respond(struct l2cap_chan *chan, int result)
{
struct hci_chan *hchan = NULL;
/* Placeholder - get hci_chan for logical link */
if (hchan) {
if (hchan->state == BT_CONNECTED) {
/* Logical link is ready to go */
chan->hs_hcon = hchan->conn;
chan->hs_hcon->l2cap_data = chan->conn;
chan->move_state = L2CAP_MOVE_WAIT_CONFIRM;
l2cap_send_move_chan_rsp(chan, L2CAP_MR_SUCCESS);
l2cap_logical_cfm(chan, hchan, L2CAP_MR_SUCCESS);
} else {
/* Wait for logical link to be ready */
chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM;
}
} else {
/* Logical link not available */
l2cap_send_move_chan_rsp(chan, L2CAP_MR_NOT_ALLOWED);
}
}
static void l2cap_do_move_cancel(struct l2cap_chan *chan, int result)
{
if (chan->move_role == L2CAP_MOVE_ROLE_RESPONDER) {
u8 rsp_result;
if (result == -EINVAL)
rsp_result = L2CAP_MR_BAD_ID;
else
rsp_result = L2CAP_MR_NOT_ALLOWED;
l2cap_send_move_chan_rsp(chan, rsp_result);
}
chan->move_role = L2CAP_MOVE_ROLE_NONE;
chan->move_state = L2CAP_MOVE_STABLE;
/* Restart data transmission */
l2cap_ertm_send(chan);
}
void l2cap_physical_cfm(struct l2cap_chan *chan, int result, u8 local_amp_id,
u8 remote_amp_id)
{
BT_DBG("chan %p, result %d, local_amp_id %d, remote_amp_id %d",
chan, result, local_amp_id, remote_amp_id);
l2cap_chan_lock(chan);
if (chan->state == BT_DISCONN || chan->state == BT_CLOSED) {
l2cap_chan_unlock(chan);
return;
}
if (chan->state != BT_CONNECTED) {
l2cap_do_create(chan, result, local_amp_id, remote_amp_id);
} else if (result != L2CAP_MR_SUCCESS) {
l2cap_do_move_cancel(chan, result);
} else {
switch (chan->move_role) {
case L2CAP_MOVE_ROLE_INITIATOR:
l2cap_do_move_initiate(chan, local_amp_id,
remote_amp_id);
break;
case L2CAP_MOVE_ROLE_RESPONDER:
l2cap_do_move_respond(chan, result);
break;
default:
l2cap_do_move_cancel(chan, result);
break;
}
}
l2cap_chan_unlock(chan);
}
static inline int l2cap_move_channel_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd,
u16 cmd_len, void *data)