Commit 2950f21a authored by Marcel Holtmann's avatar Marcel Holtmann

Bluetooth: Ask upper layers for HCI disconnect reason

Some of the qualification tests demand that in case of failures in L2CAP
the HCI disconnect should indicate a reason why L2CAP fails. This is a
bluntly layer violation since multiple L2CAP connections could be using
the same ACL and thus forcing a disconnect reason is not a good idea.

To comply with the Bluetooth test specification, the disconnect reason
is now stored in the L2CAP connection structure and every time a new
L2CAP channel is added it will set back to its default. So only in the
case where the L2CAP channel with the disconnect reason is really the
last one, it will propagated to the HCI layer.

The HCI layer has been extended with a disconnect indication that allows
it to ask upper layers for a disconnect reason. The upper layer must not
support this callback and in that case it will nicely default to the
existing behavior. If an upper layer like L2CAP can provide a disconnect
reason that one will be used to disconnect the ACL or SCO link.

No modification to the ACL disconnect timeout have been made. So in case
of Linux to Linux connection the initiator will disconnect the ACL link
before the acceptor side can signal the specific disconnect reason. That
is perfectly fine since Linux doesn't make use of this value anyway. The
L2CAP layer has a perfect valid error code for rejecting connection due
to a security violation. It is unclear why the Bluetooth specification
insists on having specific HCI disconnect reason.
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent f29972de
...@@ -478,7 +478,8 @@ struct hci_proto { ...@@ -478,7 +478,8 @@ struct hci_proto {
int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type); int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type);
int (*connect_cfm) (struct hci_conn *conn, __u8 status); int (*connect_cfm) (struct hci_conn *conn, __u8 status);
int (*disconn_ind) (struct hci_conn *conn, __u8 reason); int (*disconn_ind) (struct hci_conn *conn);
int (*disconn_cfm) (struct hci_conn *conn, __u8 reason);
int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, __u16 flags); int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, __u16 flags);
int (*recv_scodata) (struct hci_conn *conn, struct sk_buff *skb); int (*recv_scodata) (struct hci_conn *conn, struct sk_buff *skb);
int (*security_cfm) (struct hci_conn *conn, __u8 status, __u8 encrypt); int (*security_cfm) (struct hci_conn *conn, __u8 status, __u8 encrypt);
...@@ -513,17 +514,33 @@ static inline void hci_proto_connect_cfm(struct hci_conn *conn, __u8 status) ...@@ -513,17 +514,33 @@ static inline void hci_proto_connect_cfm(struct hci_conn *conn, __u8 status)
hp->connect_cfm(conn, status); hp->connect_cfm(conn, status);
} }
static inline void hci_proto_disconn_ind(struct hci_conn *conn, __u8 reason) static inline int hci_proto_disconn_ind(struct hci_conn *conn)
{ {
register struct hci_proto *hp; register struct hci_proto *hp;
int reason = 0x13;
hp = hci_proto[HCI_PROTO_L2CAP]; hp = hci_proto[HCI_PROTO_L2CAP];
if (hp && hp->disconn_ind) if (hp && hp->disconn_ind)
hp->disconn_ind(conn, reason); reason = hp->disconn_ind(conn);
hp = hci_proto[HCI_PROTO_SCO]; hp = hci_proto[HCI_PROTO_SCO];
if (hp && hp->disconn_ind) if (hp && hp->disconn_ind)
hp->disconn_ind(conn, reason); reason = hp->disconn_ind(conn);
return reason;
}
static inline void hci_proto_disconn_cfm(struct hci_conn *conn, __u8 reason)
{
register struct hci_proto *hp;
hp = hci_proto[HCI_PROTO_L2CAP];
if (hp && hp->disconn_cfm)
hp->disconn_cfm(conn, reason);
hp = hci_proto[HCI_PROTO_SCO];
if (hp && hp->disconn_cfm)
hp->disconn_cfm(conn, reason);
} }
static inline void hci_proto_auth_cfm(struct hci_conn *conn, __u8 status) static inline void hci_proto_auth_cfm(struct hci_conn *conn, __u8 status)
......
...@@ -221,6 +221,8 @@ struct l2cap_conn { ...@@ -221,6 +221,8 @@ struct l2cap_conn {
__u8 rx_ident; __u8 rx_ident;
__u8 tx_ident; __u8 tx_ident;
__u8 disc_reason;
struct l2cap_chan_list chan_list; struct l2cap_chan_list chan_list;
}; };
......
...@@ -159,6 +159,7 @@ static void hci_conn_timeout(unsigned long arg) ...@@ -159,6 +159,7 @@ static void hci_conn_timeout(unsigned long arg)
{ {
struct hci_conn *conn = (void *) arg; struct hci_conn *conn = (void *) arg;
struct hci_dev *hdev = conn->hdev; struct hci_dev *hdev = conn->hdev;
__u8 reason;
BT_DBG("conn %p state %d", conn, conn->state); BT_DBG("conn %p state %d", conn, conn->state);
...@@ -177,7 +178,8 @@ static void hci_conn_timeout(unsigned long arg) ...@@ -177,7 +178,8 @@ static void hci_conn_timeout(unsigned long arg)
break; break;
case BT_CONFIG: case BT_CONFIG:
case BT_CONNECTED: case BT_CONNECTED:
hci_acl_disconn(conn, 0x13); reason = hci_proto_disconn_ind(conn);
hci_acl_disconn(conn, reason);
break; break;
default: default:
conn->state = BT_CLOSED; conn->state = BT_CLOSED;
...@@ -562,7 +564,7 @@ void hci_conn_hash_flush(struct hci_dev *hdev) ...@@ -562,7 +564,7 @@ void hci_conn_hash_flush(struct hci_dev *hdev)
hci_conn_del_sysfs(c); hci_conn_del_sysfs(c);
hci_proto_disconn_ind(c, 0x16); hci_proto_disconn_cfm(c, 0x16);
hci_conn_del(c); hci_conn_del(c);
} }
} }
......
...@@ -1021,7 +1021,7 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff ...@@ -1021,7 +1021,7 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff
hci_conn_del_sysfs(conn); hci_conn_del_sysfs(conn);
hci_proto_disconn_ind(conn, ev->reason); hci_proto_disconn_cfm(conn, ev->reason);
hci_conn_del(conn); hci_conn_del(conn);
} }
......
...@@ -206,6 +206,8 @@ static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct so ...@@ -206,6 +206,8 @@ static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct so
BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid); BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid);
conn->disc_reason = 0x13;
l2cap_pi(sk)->conn = conn; l2cap_pi(sk)->conn = conn;
if (sk->sk_type == SOCK_SEQPACKET) { if (sk->sk_type == SOCK_SEQPACKET) {
...@@ -491,6 +493,8 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status) ...@@ -491,6 +493,8 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
spin_lock_init(&conn->lock); spin_lock_init(&conn->lock);
rwlock_init(&conn->chan_list.lock); rwlock_init(&conn->chan_list.lock);
conn->disc_reason = 0x13;
return conn; return conn;
} }
...@@ -1840,6 +1844,7 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd ...@@ -1840,6 +1844,7 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd
/* Check if the ACL is secure enough (if not SDP) */ /* Check if the ACL is secure enough (if not SDP) */
if (psm != cpu_to_le16(0x0001) && if (psm != cpu_to_le16(0x0001) &&
!hci_conn_check_link_mode(conn->hcon)) { !hci_conn_check_link_mode(conn->hcon)) {
conn->disc_reason = 0x05;
result = L2CAP_CR_SEC_BLOCK; result = L2CAP_CR_SEC_BLOCK;
goto response; goto response;
} }
...@@ -2472,7 +2477,19 @@ static int l2cap_connect_cfm(struct hci_conn *hcon, u8 status) ...@@ -2472,7 +2477,19 @@ static int l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
return 0; return 0;
} }
static int l2cap_disconn_ind(struct hci_conn *hcon, u8 reason) static int l2cap_disconn_ind(struct hci_conn *hcon)
{
struct l2cap_conn *conn = hcon->l2cap_data;
BT_DBG("hcon %p", hcon);
if (hcon->type != ACL_LINK || !conn)
return 0x13;
return conn->disc_reason;
}
static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
{ {
BT_DBG("hcon %p reason %d", hcon, reason); BT_DBG("hcon %p reason %d", hcon, reason);
...@@ -2717,6 +2734,7 @@ static struct hci_proto l2cap_hci_proto = { ...@@ -2717,6 +2734,7 @@ static struct hci_proto l2cap_hci_proto = {
.connect_ind = l2cap_connect_ind, .connect_ind = l2cap_connect_ind,
.connect_cfm = l2cap_connect_cfm, .connect_cfm = l2cap_connect_cfm,
.disconn_ind = l2cap_disconn_ind, .disconn_ind = l2cap_disconn_ind,
.disconn_cfm = l2cap_disconn_cfm,
.security_cfm = l2cap_security_cfm, .security_cfm = l2cap_security_cfm,
.recv_acldata = l2cap_recv_acldata .recv_acldata = l2cap_recv_acldata
}; };
......
...@@ -902,7 +902,7 @@ static int sco_connect_cfm(struct hci_conn *hcon, __u8 status) ...@@ -902,7 +902,7 @@ static int sco_connect_cfm(struct hci_conn *hcon, __u8 status)
return 0; return 0;
} }
static int sco_disconn_ind(struct hci_conn *hcon, __u8 reason) static int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason)
{ {
BT_DBG("hcon %p reason %d", hcon, reason); BT_DBG("hcon %p reason %d", hcon, reason);
...@@ -985,7 +985,7 @@ static struct hci_proto sco_hci_proto = { ...@@ -985,7 +985,7 @@ static struct hci_proto sco_hci_proto = {
.id = HCI_PROTO_SCO, .id = HCI_PROTO_SCO,
.connect_ind = sco_connect_ind, .connect_ind = sco_connect_ind,
.connect_cfm = sco_connect_cfm, .connect_cfm = sco_connect_cfm,
.disconn_ind = sco_disconn_ind, .disconn_cfm = sco_disconn_cfm,
.recv_scodata = sco_recv_scodata .recv_scodata = sco_recv_scodata
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment