aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2011-01-20 05:40:27 -0500
committerGustavo F. Padovan <padovan@profusion.mobi>2011-02-07 22:40:07 -0500
commit8962ee74be48df16027100f657b2b12e8ef3d34d (patch)
treebbafd1e6cf773c4712c57f578c84f44eae012ec0
parentf7520543ab40341edbc2aeee7fef68218be19a0a (diff)
Bluetooth: Add disconnect managment command
This patch adds a disconnect command to the managment interface. Using this command user space is able to force the disconnection of connected devices. The command maps directly to the Disconnect HCI command. Signed-off-by: Johan Hedberg <johan.hedberg@nokia.com> Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
-rw-r--r--include/net/bluetooth/hci_core.h1
-rw-r--r--include/net/bluetooth/mgmt.h10
-rw-r--r--net/bluetooth/hci_event.c9
-rw-r--r--net/bluetooth/mgmt.c119
4 files changed, 137 insertions, 2 deletions
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 746f8dc8aad..2197a099a2b 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -716,6 +716,7 @@ int mgmt_connectable(u16 index, u8 connectable);
716int mgmt_new_key(u16 index, struct link_key *key, u8 old_key_type); 716int mgmt_new_key(u16 index, struct link_key *key, u8 old_key_type);
717int mgmt_connected(u16 index, bdaddr_t *bdaddr); 717int mgmt_connected(u16 index, bdaddr_t *bdaddr);
718int mgmt_disconnected(u16 index, bdaddr_t *bdaddr); 718int mgmt_disconnected(u16 index, bdaddr_t *bdaddr);
719int mgmt_disconnect_failed(u16 index);
719 720
720/* HCI info for socket */ 721/* HCI info for socket */
721#define hci_pi(sk) ((struct hci_pinfo *) sk) 722#define hci_pi(sk) ((struct hci_pinfo *) sk)
diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h
index 6719e9a3661..2c47601b6e6 100644
--- a/include/net/bluetooth/mgmt.h
+++ b/include/net/bluetooth/mgmt.h
@@ -120,6 +120,16 @@ struct mgmt_cp_remove_key {
120 __u8 disconnect; 120 __u8 disconnect;
121} __packed; 121} __packed;
122 122
123#define MGMT_OP_DISCONNECT 0x000F
124struct mgmt_cp_disconnect {
125 __le16 index;
126 bdaddr_t bdaddr;
127} __packed;
128struct mgmt_rp_disconnect {
129 __le16 index;
130 bdaddr_t bdaddr;
131} __packed;
132
123#define MGMT_EV_CMD_COMPLETE 0x0001 133#define MGMT_EV_CMD_COMPLETE 0x0001
124struct mgmt_ev_cmd_complete { 134struct mgmt_ev_cmd_complete {
125 __le16 opcode; 135 __le16 opcode;
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 46ddb029912..335c60bad96 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -1264,8 +1264,10 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff
1264 1264
1265 BT_DBG("%s status %d", hdev->name, ev->status); 1265 BT_DBG("%s status %d", hdev->name, ev->status);
1266 1266
1267 if (ev->status) 1267 if (ev->status) {
1268 mgmt_disconnect_failed(hdev->id);
1268 return; 1269 return;
1270 }
1269 1271
1270 hci_dev_lock(hdev); 1272 hci_dev_lock(hdev);
1271 1273
@@ -1680,6 +1682,11 @@ static inline void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb)
1680 hci_cs_exit_sniff_mode(hdev, ev->status); 1682 hci_cs_exit_sniff_mode(hdev, ev->status);
1681 break; 1683 break;
1682 1684
1685 case HCI_OP_DISCONNECT:
1686 if (ev->status != 0)
1687 mgmt_disconnect_failed(hdev->id);
1688 break;
1689
1683 default: 1690 default:
1684 BT_DBG("%s opcode 0x%x", hdev->name, opcode); 1691 BT_DBG("%s opcode 0x%x", hdev->name, opcode);
1685 break; 1692 break;
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 7cf1968157d..48f266a64ca 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -887,6 +887,60 @@ unlock:
887 return err; 887 return err;
888} 888}
889 889
890static int disconnect(struct sock *sk, unsigned char *data, u16 len)
891{
892 struct hci_dev *hdev;
893 struct mgmt_cp_disconnect *cp;
894 struct hci_cp_disconnect dc;
895 struct hci_conn *conn;
896 u16 dev_id;
897 int err;
898
899 BT_DBG("");
900
901 cp = (void *) data;
902 dev_id = get_unaligned_le16(&cp->index);
903
904 hdev = hci_dev_get(dev_id);
905 if (!hdev)
906 return cmd_status(sk, MGMT_OP_DISCONNECT, ENODEV);
907
908 hci_dev_lock_bh(hdev);
909
910 if (!test_bit(HCI_UP, &hdev->flags)) {
911 err = cmd_status(sk, MGMT_OP_DISCONNECT, ENETDOWN);
912 goto failed;
913 }
914
915 if (mgmt_pending_find(MGMT_OP_DISCONNECT, dev_id)) {
916 err = cmd_status(sk, MGMT_OP_DISCONNECT, EBUSY);
917 goto failed;
918 }
919
920 conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr);
921 if (!conn) {
922 err = cmd_status(sk, MGMT_OP_DISCONNECT, ENOTCONN);
923 goto failed;
924 }
925
926 err = mgmt_pending_add(sk, MGMT_OP_DISCONNECT, dev_id, data, len);
927 if (err < 0)
928 goto failed;
929
930 put_unaligned_le16(conn->handle, &dc.handle);
931 dc.reason = 0x13; /* Remote User Terminated Connection */
932
933 err = hci_send_cmd(hdev, HCI_OP_DISCONNECT, sizeof(dc), &dc);
934 if (err < 0)
935 mgmt_pending_remove(MGMT_OP_DISCONNECT, dev_id);
936
937failed:
938 hci_dev_unlock_bh(hdev);
939 hci_dev_put(hdev);
940
941 return err;
942}
943
890int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) 944int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
891{ 945{
892 unsigned char *buf; 946 unsigned char *buf;
@@ -957,6 +1011,9 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
957 case MGMT_OP_REMOVE_KEY: 1011 case MGMT_OP_REMOVE_KEY:
958 err = remove_key(sk, buf + sizeof(*hdr), len); 1012 err = remove_key(sk, buf + sizeof(*hdr), len);
959 break; 1013 break;
1014 case MGMT_OP_DISCONNECT:
1015 err = disconnect(sk, buf + sizeof(*hdr), len);
1016 break;
960 default: 1017 default:
961 BT_DBG("Unknown op %u", opcode); 1018 BT_DBG("Unknown op %u", opcode);
962 err = cmd_status(sk, opcode, 0x01); 1019 err = cmd_status(sk, opcode, 0x01);
@@ -1101,12 +1158,72 @@ int mgmt_connected(u16 index, bdaddr_t *bdaddr)
1101 return mgmt_event(MGMT_EV_CONNECTED, &ev, sizeof(ev), NULL); 1158 return mgmt_event(MGMT_EV_CONNECTED, &ev, sizeof(ev), NULL);
1102} 1159}
1103 1160
1161static void disconnect_rsp(struct pending_cmd *cmd, void *data)
1162{
1163 struct mgmt_cp_disconnect *cp = cmd->cmd;
1164 struct sock **sk = data;
1165 struct sk_buff *skb;
1166 struct mgmt_hdr *hdr;
1167 struct mgmt_ev_cmd_complete *ev;
1168 struct mgmt_rp_disconnect *rp;
1169
1170 skb = alloc_skb(sizeof(*hdr) + sizeof(*ev) + sizeof(*rp), GFP_ATOMIC);
1171 if (!skb)
1172 return;
1173
1174 hdr = (void *) skb_put(skb, sizeof(*hdr));
1175 hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE);
1176 hdr->len = cpu_to_le16(sizeof(*ev) + sizeof(*rp));
1177
1178 ev = (void *) skb_put(skb, sizeof(*ev));
1179 put_unaligned_le16(MGMT_OP_DISCONNECT, &ev->opcode);
1180
1181 rp = (void *) skb_put(skb, sizeof(*rp));
1182 put_unaligned_le16(cmd->index, &rp->index);
1183 bacpy(&rp->bdaddr, &cp->bdaddr);
1184
1185 if (sock_queue_rcv_skb(cmd->sk, skb) < 0)
1186 kfree_skb(skb);
1187
1188 *sk = cmd->sk;
1189 sock_hold(*sk);
1190
1191 list_del(&cmd->list);
1192 mgmt_pending_free(cmd);
1193}
1194
1104int mgmt_disconnected(u16 index, bdaddr_t *bdaddr) 1195int mgmt_disconnected(u16 index, bdaddr_t *bdaddr)
1105{ 1196{
1106 struct mgmt_ev_disconnected ev; 1197 struct mgmt_ev_disconnected ev;
1198 struct sock *sk = NULL;
1199 int err;
1200
1201 mgmt_pending_foreach(MGMT_OP_DISCONNECT, index, disconnect_rsp, &sk);
1107 1202
1108 put_unaligned_le16(index, &ev.index); 1203 put_unaligned_le16(index, &ev.index);
1109 bacpy(&ev.bdaddr, bdaddr); 1204 bacpy(&ev.bdaddr, bdaddr);
1110 1205
1111 return mgmt_event(MGMT_EV_DISCONNECTED, &ev, sizeof(ev), NULL); 1206 err = mgmt_event(MGMT_EV_DISCONNECTED, &ev, sizeof(ev), sk);
1207
1208 if (sk)
1209 sock_put(sk);
1210
1211 return err;
1212}
1213
1214int mgmt_disconnect_failed(u16 index)
1215{
1216 struct pending_cmd *cmd;
1217 int err;
1218
1219 cmd = mgmt_pending_find(MGMT_OP_DISCONNECT, index);
1220 if (!cmd)
1221 return -ENOENT;
1222
1223 err = cmd_status(cmd->sk, MGMT_OP_DISCONNECT, EIO);
1224
1225 list_del(&cmd->list);
1226 mgmt_pending_free(cmd);
1227
1228 return err;
1112} 1229}