diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2011-01-20 05:40:27 -0500 |
---|---|---|
committer | Gustavo F. Padovan <padovan@profusion.mobi> | 2011-02-07 22:40:07 -0500 |
commit | 8962ee74be48df16027100f657b2b12e8ef3d34d (patch) | |
tree | bbafd1e6cf773c4712c57f578c84f44eae012ec0 /net/bluetooth | |
parent | f7520543ab40341edbc2aeee7fef68218be19a0a (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>
Diffstat (limited to 'net/bluetooth')
-rw-r--r-- | net/bluetooth/hci_event.c | 9 | ||||
-rw-r--r-- | net/bluetooth/mgmt.c | 119 |
2 files changed, 126 insertions, 2 deletions
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 46ddb029912b..335c60bad96c 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 7cf1968157d8..48f266a64caf 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 | ||
890 | static 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 | |||
937 | failed: | ||
938 | hci_dev_unlock_bh(hdev); | ||
939 | hci_dev_put(hdev); | ||
940 | |||
941 | return err; | ||
942 | } | ||
943 | |||
890 | int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) | 944 | int 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 | ||
1161 | static 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 | |||
1104 | int mgmt_disconnected(u16 index, bdaddr_t *bdaddr) | 1195 | int 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 | |||
1214 | int 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 | } |