| /* |
| Copyright (c) 2010-2012 Code Aurora Forum. All rights reserved. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 and |
| only version 2 as published by the Free Software Foundation. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| |
| #include <linux/skbuff.h> |
| #include <linux/list.h> |
| #include <linux/workqueue.h> |
| #include <linux/timer.h> |
| |
| #include <linux/crypto.h> |
| #include <linux/scatterlist.h> |
| #include <linux/err.h> |
| #include <crypto/hash.h> |
| |
| #include <net/bluetooth/bluetooth.h> |
| #include <net/bluetooth/hci_core.h> |
| #include <net/bluetooth/l2cap.h> |
| #include <net/bluetooth/amp.h> |
| |
| static struct workqueue_struct *amp_workqueue; |
| |
| LIST_HEAD(amp_mgr_list); |
| DEFINE_RWLOCK(amp_mgr_list_lock); |
| |
| static int send_a2mp(struct socket *sock, u8 *data, int len); |
| |
| static void ctx_timeout(unsigned long data); |
| |
| static void launch_ctx(struct amp_mgr *mgr); |
| static int execute_ctx(struct amp_ctx *ctx, u8 evt_type, void *data); |
| static int kill_ctx(struct amp_ctx *ctx); |
| static int cancel_ctx(struct amp_ctx *ctx); |
| |
| static struct socket *open_fixed_channel(bdaddr_t *src, bdaddr_t *dst); |
| |
| static void remove_amp_mgr(struct amp_mgr *mgr) |
| { |
| BT_DBG("mgr %p", mgr); |
| |
| write_lock(&_mgr_list_lock); |
| list_del(&mgr->list); |
| write_unlock(&_mgr_list_lock); |
| |
| read_lock(&mgr->ctx_list_lock); |
| while (!list_empty(&mgr->ctx_list)) { |
| struct amp_ctx *ctx; |
| ctx = list_first_entry(&mgr->ctx_list, struct amp_ctx, list); |
| read_unlock(&mgr->ctx_list_lock); |
| BT_DBG("kill ctx %p", ctx); |
| kill_ctx(ctx); |
| read_lock(&mgr->ctx_list_lock); |
| } |
| read_unlock(&mgr->ctx_list_lock); |
| |
| kfree(mgr->ctrls); |
| |
| kfree(mgr); |
| } |
| |
| static struct amp_mgr *get_amp_mgr_sk(struct sock *sk) |
| { |
| struct amp_mgr *mgr; |
| struct amp_mgr *found = NULL; |
| |
| read_lock(&_mgr_list_lock); |
| list_for_each_entry(mgr, &_mgr_list, list) { |
| if ((mgr->a2mp_sock) && (mgr->a2mp_sock->sk == sk)) { |
| found = mgr; |
| break; |
| } |
| } |
| read_unlock(&_mgr_list_lock); |
| return found; |
| } |
| |
| static struct amp_mgr *get_create_amp_mgr(struct hci_conn *hcon, |
| struct sk_buff *skb) |
| { |
| struct amp_mgr *mgr; |
| |
| write_lock(&_mgr_list_lock); |
| list_for_each_entry(mgr, &_mgr_list, list) { |
| if (mgr->l2cap_conn == hcon->l2cap_data) { |
| BT_DBG("found %p", mgr); |
| write_unlock(&_mgr_list_lock); |
| goto gc_finished; |
| } |
| } |
| write_unlock(&_mgr_list_lock); |
| |
| mgr = kzalloc(sizeof(*mgr), GFP_ATOMIC); |
| if (!mgr) |
| return NULL; |
| |
| mgr->l2cap_conn = hcon->l2cap_data; |
| mgr->next_ident = 1; |
| INIT_LIST_HEAD(&mgr->ctx_list); |
| rwlock_init(&mgr->ctx_list_lock); |
| mgr->skb = skb; |
| BT_DBG("hcon %p mgr %p", hcon, mgr); |
| mgr->a2mp_sock = open_fixed_channel(&hcon->hdev->bdaddr, &hcon->dst); |
| if (!mgr->a2mp_sock) { |
| kfree(mgr); |
| return NULL; |
| } |
| write_lock(&_mgr_list_lock); |
| list_add(&(mgr->list), &_mgr_list); |
| write_unlock(&_mgr_list_lock); |
| |
| gc_finished: |
| return mgr; |
| } |
| |
| static struct amp_ctrl *get_ctrl(struct amp_mgr *mgr, u8 remote_id) |
| { |
| if ((mgr->ctrls) && (mgr->ctrls->id == remote_id)) |
| return mgr->ctrls; |
| else |
| return NULL; |
| } |
| |
| static struct amp_ctrl *get_create_ctrl(struct amp_mgr *mgr, u8 id) |
| { |
| struct amp_ctrl *ctrl; |
| |
| BT_DBG("mgr %p, id %d", mgr, id); |
| if ((mgr->ctrls) && (mgr->ctrls->id == id)) |
| ctrl = mgr->ctrls; |
| else { |
| kfree(mgr->ctrls); |
| ctrl = kzalloc(sizeof(struct amp_ctrl), GFP_ATOMIC); |
| if (ctrl) { |
| ctrl->mgr = mgr; |
| ctrl->id = id; |
| } |
| mgr->ctrls = ctrl; |
| } |
| |
| return ctrl; |
| } |
| |
| static struct amp_ctx *create_ctx(u8 type, u8 state) |
| { |
| struct amp_ctx *ctx = NULL; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); |
| if (ctx) { |
| ctx->type = type; |
| ctx->state = state; |
| init_timer(&(ctx->timer)); |
| ctx->timer.function = ctx_timeout; |
| ctx->timer.data = (unsigned long) ctx; |
| } |
| BT_DBG("ctx %p, type %d", ctx, type); |
| return ctx; |
| } |
| |
| static inline void start_ctx(struct amp_mgr *mgr, struct amp_ctx *ctx) |
| { |
| BT_DBG("ctx %p", ctx); |
| write_lock(&mgr->ctx_list_lock); |
| list_add(&ctx->list, &mgr->ctx_list); |
| write_unlock(&mgr->ctx_list_lock); |
| ctx->mgr = mgr; |
| execute_ctx(ctx, AMP_INIT, 0); |
| } |
| |
| static void destroy_ctx(struct amp_ctx *ctx) |
| { |
| struct amp_mgr *mgr = ctx->mgr; |
| |
| BT_DBG("ctx %p deferred %p", ctx, ctx->deferred); |
| del_timer(&ctx->timer); |
| write_lock(&mgr->ctx_list_lock); |
| list_del(&ctx->list); |
| write_unlock(&mgr->ctx_list_lock); |
| if (ctx->deferred) |
| execute_ctx(ctx->deferred, AMP_INIT, 0); |
| kfree(ctx); |
| } |
| |
| static struct amp_ctx *get_ctx_mgr(struct amp_mgr *mgr, u8 type) |
| { |
| struct amp_ctx *fnd = NULL; |
| struct amp_ctx *ctx; |
| |
| read_lock(&mgr->ctx_list_lock); |
| list_for_each_entry(ctx, &mgr->ctx_list, list) { |
| if (ctx->type == type) { |
| fnd = ctx; |
| break; |
| } |
| } |
| read_unlock(&mgr->ctx_list_lock); |
| return fnd; |
| } |
| |
| static struct amp_ctx *get_ctx_type(struct amp_ctx *cur, u8 type) |
| { |
| struct amp_mgr *mgr = cur->mgr; |
| struct amp_ctx *fnd = NULL; |
| struct amp_ctx *ctx; |
| |
| read_lock(&mgr->ctx_list_lock); |
| list_for_each_entry(ctx, &mgr->ctx_list, list) { |
| if ((ctx->type == type) && (ctx != cur)) { |
| fnd = ctx; |
| break; |
| } |
| } |
| read_unlock(&mgr->ctx_list_lock); |
| return fnd; |
| } |
| |
| static struct amp_ctx *get_ctx_a2mp(struct amp_mgr *mgr, u8 ident) |
| { |
| struct amp_ctx *fnd = NULL; |
| struct amp_ctx *ctx; |
| |
| read_lock(&mgr->ctx_list_lock); |
| list_for_each_entry(ctx, &mgr->ctx_list, list) { |
| if ((ctx->evt_type & AMP_A2MP_RSP) && |
| (ctx->rsp_ident == ident)) { |
| fnd = ctx; |
| break; |
| } |
| } |
| read_unlock(&mgr->ctx_list_lock); |
| return fnd; |
| } |
| |
| static struct amp_ctx *get_ctx_hdev(struct hci_dev *hdev, u8 evt_type, |
| u16 evt_value) |
| { |
| struct amp_mgr *mgr; |
| struct amp_ctx *fnd = NULL; |
| |
| read_lock(&_mgr_list_lock); |
| list_for_each_entry(mgr, &_mgr_list, list) { |
| struct amp_ctx *ctx; |
| read_lock(&mgr->ctx_list_lock); |
| list_for_each_entry(ctx, &mgr->ctx_list, list) { |
| struct hci_dev *ctx_hdev; |
| ctx_hdev = hci_dev_get(ctx->id); |
| if ((ctx_hdev == hdev) && (ctx->evt_type & evt_type)) { |
| switch (evt_type) { |
| case AMP_HCI_CMD_STATUS: |
| case AMP_HCI_CMD_CMPLT: |
| if (ctx->opcode == evt_value) |
| fnd = ctx; |
| break; |
| case AMP_HCI_EVENT: |
| if (ctx->evt_code == (u8) evt_value) |
| fnd = ctx; |
| break; |
| } |
| } |
| if (ctx_hdev) |
| hci_dev_put(ctx_hdev); |
| |
| if (fnd) |
| break; |
| } |
| read_unlock(&mgr->ctx_list_lock); |
| } |
| read_unlock(&_mgr_list_lock); |
| return fnd; |
| } |
| |
| static inline u8 next_ident(struct amp_mgr *mgr) |
| { |
| if (++mgr->next_ident == 0) |
| mgr->next_ident = 1; |
| return mgr->next_ident; |
| } |
| |
| static inline void send_a2mp_cmd2(struct amp_mgr *mgr, u8 ident, u8 code, |
| u16 len, void *data, u16 len2, void *data2) |
| { |
| struct a2mp_cmd_hdr *hdr; |
| int plen; |
| u8 *p, *cmd; |
| |
| BT_DBG("ident %d code 0x%02x", ident, code); |
| if (!mgr->a2mp_sock) |
| return; |
| plen = sizeof(*hdr) + len + len2; |
| cmd = kzalloc(plen, GFP_ATOMIC); |
| if (!cmd) |
| return; |
| hdr = (struct a2mp_cmd_hdr *) cmd; |
| hdr->code = code; |
| hdr->ident = ident; |
| hdr->len = cpu_to_le16(len+len2); |
| p = cmd + sizeof(*hdr); |
| memcpy(p, data, len); |
| p += len; |
| memcpy(p, data2, len2); |
| send_a2mp(mgr->a2mp_sock, cmd, plen); |
| kfree(cmd); |
| } |
| |
| static inline void send_a2mp_cmd(struct amp_mgr *mgr, u8 ident, |
| u8 code, u16 len, void *data) |
| { |
| send_a2mp_cmd2(mgr, ident, code, len, data, 0, NULL); |
| } |
| |
| static inline int command_rej(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| struct a2mp_cmd_rej *rej; |
| struct amp_ctx *ctx; |
| |
| BT_DBG("ident %d code %d", hdr->ident, hdr->code); |
| rej = (struct a2mp_cmd_rej *) skb_pull(skb, sizeof(*hdr)); |
| if (skb->len < sizeof(*rej)) |
| return -EINVAL; |
| BT_DBG("reason %d", le16_to_cpu(rej->reason)); |
| ctx = get_ctx_a2mp(mgr, hdr->ident); |
| if (ctx) |
| kill_ctx(ctx); |
| skb_pull(skb, sizeof(*rej)); |
| return 0; |
| } |
| |
| static int send_a2mp_cl(struct amp_mgr *mgr, u8 ident, u8 code, u16 len, |
| void *msg) |
| { |
| struct a2mp_cl clist[16]; |
| struct a2mp_cl *cl; |
| struct hci_dev *hdev; |
| int num_ctrls = 1, id; |
| |
| cl = clist; |
| cl->id = 0; |
| cl->type = 0; |
| cl->status = 1; |
| |
| for (id = 0; id < 16; ++id) { |
| hdev = hci_dev_get(id); |
| if (hdev) { |
| if ((hdev->amp_type != HCI_BREDR) && |
| test_bit(HCI_UP, &hdev->flags)) { |
| (cl + num_ctrls)->id = hdev->id; |
| (cl + num_ctrls)->type = hdev->amp_type; |
| (cl + num_ctrls)->status = hdev->amp_status; |
| ++num_ctrls; |
| } |
| hci_dev_put(hdev); |
| } |
| } |
| send_a2mp_cmd2(mgr, ident, code, len, msg, |
| num_ctrls*sizeof(*cl), clist); |
| |
| return 0; |
| } |
| |
| static void send_a2mp_change_notify(void) |
| { |
| struct amp_mgr *mgr; |
| |
| read_lock(&_mgr_list_lock); |
| list_for_each_entry(mgr, &_mgr_list, list) { |
| if (mgr->discovered) |
| send_a2mp_cl(mgr, next_ident(mgr), |
| A2MP_CHANGE_NOTIFY, 0, NULL); |
| } |
| read_unlock(&_mgr_list_lock); |
| } |
| |
| static inline int discover_req(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| struct a2mp_discover_req *req; |
| u16 *efm; |
| struct a2mp_discover_rsp rsp; |
| |
| req = (struct a2mp_discover_req *) skb_pull(skb, sizeof(*hdr)); |
| if (skb->len < sizeof(*req)) |
| return -EINVAL; |
| efm = (u16 *) skb_pull(skb, sizeof(*req)); |
| |
| BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(req->mtu), |
| le16_to_cpu(req->ext_feat)); |
| |
| while (le16_to_cpu(req->ext_feat) & 0x8000) { |
| if (skb->len < sizeof(*efm)) |
| return -EINVAL; |
| req->ext_feat = *efm; |
| BT_DBG("efm 0x%4.4x", le16_to_cpu(req->ext_feat)); |
| efm = (u16 *) skb_pull(skb, sizeof(*efm)); |
| } |
| |
| rsp.mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU); |
| rsp.ext_feat = 0; |
| |
| mgr->discovered = 1; |
| |
| return send_a2mp_cl(mgr, hdr->ident, A2MP_DISCOVER_RSP, |
| sizeof(rsp), &rsp); |
| } |
| |
| static inline int change_notify(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| struct a2mp_cl *cl; |
| |
| cl = (struct a2mp_cl *) skb_pull(skb, sizeof(*hdr)); |
| while (skb->len >= sizeof(*cl)) { |
| struct amp_ctrl *ctrl; |
| if (cl->id != 0) { |
| ctrl = get_create_ctrl(mgr, cl->id); |
| if (ctrl != NULL) { |
| ctrl->type = cl->type; |
| ctrl->status = cl->status; |
| } |
| } |
| cl = (struct a2mp_cl *) skb_pull(skb, sizeof(*cl)); |
| } |
| |
| /* TODO find controllers in manager that were not on received */ |
| /* controller list and destroy them */ |
| send_a2mp_cmd(mgr, hdr->ident, A2MP_CHANGE_RSP, 0, NULL); |
| |
| return 0; |
| } |
| |
| static inline int getinfo_req(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| u8 *data; |
| int id; |
| struct hci_dev *hdev; |
| struct a2mp_getinfo_rsp rsp; |
| |
| data = (u8 *) skb_pull(skb, sizeof(*hdr)); |
| if (le16_to_cpu(hdr->len) < sizeof(*data)) |
| return -EINVAL; |
| if (skb->len < sizeof(*data)) |
| return -EINVAL; |
| id = *data; |
| skb_pull(skb, sizeof(*data)); |
| rsp.id = id; |
| rsp.status = 1; |
| |
| BT_DBG("id %d", id); |
| hdev = hci_dev_get(id); |
| |
| if (hdev && hdev->amp_type != HCI_BREDR) { |
| rsp.status = 0; |
| rsp.total_bw = cpu_to_le32(hdev->amp_total_bw); |
| rsp.max_bw = cpu_to_le32(hdev->amp_max_bw); |
| rsp.min_latency = cpu_to_le32(hdev->amp_min_latency); |
| rsp.pal_cap = cpu_to_le16(hdev->amp_pal_cap); |
| rsp.assoc_size = cpu_to_le16(hdev->amp_assoc_size); |
| } |
| |
| send_a2mp_cmd(mgr, hdr->ident, A2MP_GETINFO_RSP, sizeof(rsp), &rsp); |
| |
| if (hdev) |
| hci_dev_put(hdev); |
| |
| return 0; |
| } |
| |
| static void create_physical(struct l2cap_conn *conn, struct sock *sk) |
| { |
| struct amp_mgr *mgr; |
| struct amp_ctx *ctx = NULL; |
| |
| BT_DBG("conn %p", conn); |
| mgr = get_create_amp_mgr(conn->hcon, NULL); |
| if (!mgr) |
| goto cp_finished; |
| BT_DBG("mgr %p", mgr); |
| ctx = create_ctx(AMP_CREATEPHYSLINK, AMP_CPL_INIT); |
| if (!ctx) |
| goto cp_finished; |
| ctx->sk = sk; |
| sock_hold(sk); |
| start_ctx(mgr, ctx); |
| return; |
| |
| cp_finished: |
| l2cap_amp_physical_complete(-ENOMEM, 0, 0, sk); |
| } |
| |
| static void accept_physical(struct l2cap_conn *lcon, u8 id, struct sock *sk) |
| { |
| struct amp_mgr *mgr; |
| struct hci_dev *hdev; |
| struct hci_conn *conn; |
| struct amp_ctx *aplctx = NULL; |
| u8 remote_id = 0; |
| int result = -EINVAL; |
| |
| BT_DBG("lcon %p", lcon); |
| hdev = hci_dev_get(id); |
| if (!hdev) |
| goto ap_finished; |
| BT_DBG("hdev %p", hdev); |
| mgr = get_create_amp_mgr(lcon->hcon, NULL); |
| if (!mgr) |
| goto ap_finished; |
| BT_DBG("mgr %p", mgr); |
| conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, |
| &mgr->l2cap_conn->hcon->dst); |
| if (conn) { |
| BT_DBG("conn %p", hdev); |
| result = 0; |
| remote_id = conn->dst_id; |
| goto ap_finished; |
| } |
| aplctx = get_ctx_mgr(mgr, AMP_ACCEPTPHYSLINK); |
| if (!aplctx) |
| goto ap_finished; |
| aplctx->sk = sk; |
| sock_hold(sk); |
| return; |
| |
| ap_finished: |
| if (hdev) |
| hci_dev_put(hdev); |
| l2cap_amp_physical_complete(result, id, remote_id, sk); |
| } |
| |
| static int getampassoc_req(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| struct amp_ctx *ctx; |
| struct a2mp_getampassoc_req *req; |
| |
| if (hdr->len < sizeof(*req)) |
| return -EINVAL; |
| req = (struct a2mp_getampassoc_req *) skb_pull(skb, sizeof(*hdr)); |
| skb_pull(skb, sizeof(*req)); |
| |
| ctx = create_ctx(AMP_GETAMPASSOC, AMP_GAA_INIT); |
| if (!ctx) |
| return -ENOMEM; |
| ctx->id = req->id; |
| ctx->d.gaa.req_ident = hdr->ident; |
| ctx->hdev = hci_dev_get(ctx->id); |
| if (ctx->hdev) |
| ctx->d.gaa.assoc = kmalloc(ctx->hdev->amp_assoc_size, |
| GFP_ATOMIC); |
| start_ctx(mgr, ctx); |
| return 0; |
| } |
| |
| static u8 getampassoc_handler(struct amp_ctx *ctx, u8 evt_type, void *data) |
| { |
| struct sk_buff *skb = (struct sk_buff *) data; |
| struct hci_cp_read_local_amp_assoc cp; |
| struct hci_rp_read_local_amp_assoc *rp; |
| struct a2mp_getampassoc_rsp rsp; |
| u16 rem_len; |
| u16 frag_len; |
| |
| rsp.status = 1; |
| if ((evt_type == AMP_KILLED) || (!ctx->hdev) || (!ctx->d.gaa.assoc)) |
| goto gaa_finished; |
| |
| switch (ctx->state) { |
| case AMP_GAA_INIT: |
| ctx->state = AMP_GAA_RLAA_COMPLETE; |
| ctx->evt_type = AMP_HCI_CMD_CMPLT; |
| ctx->opcode = HCI_OP_READ_LOCAL_AMP_ASSOC; |
| ctx->d.gaa.len_so_far = 0; |
| cp.phy_handle = 0; |
| cp.len_so_far = 0; |
| cp.max_len = ctx->hdev->amp_assoc_size; |
| hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(cp), &cp); |
| break; |
| |
| case AMP_GAA_RLAA_COMPLETE: |
| if (skb->len < 4) |
| goto gaa_finished; |
| rp = (struct hci_rp_read_local_amp_assoc *) skb->data; |
| if (rp->status) |
| goto gaa_finished; |
| rem_len = le16_to_cpu(rp->rem_len); |
| skb_pull(skb, 4); |
| frag_len = skb->len; |
| |
| if (ctx->d.gaa.len_so_far + rem_len <= |
| ctx->hdev->amp_assoc_size) { |
| struct hci_cp_read_local_amp_assoc cp; |
| u8 *assoc = ctx->d.gaa.assoc + ctx->d.gaa.len_so_far; |
| memcpy(assoc, rp->frag, frag_len); |
| ctx->d.gaa.len_so_far += rem_len; |
| rem_len -= frag_len; |
| if (rem_len == 0) { |
| rsp.status = 0; |
| goto gaa_finished; |
| } |
| /* more assoc data to read */ |
| cp.phy_handle = 0; |
| cp.len_so_far = ctx->d.gaa.len_so_far; |
| cp.max_len = ctx->hdev->amp_assoc_size; |
| hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(cp), &cp); |
| } |
| break; |
| |
| default: |
| goto gaa_finished; |
| break; |
| } |
| return 0; |
| |
| gaa_finished: |
| rsp.id = ctx->id; |
| send_a2mp_cmd2(ctx->mgr, ctx->d.gaa.req_ident, A2MP_GETAMPASSOC_RSP, |
| sizeof(rsp), &rsp, |
| ctx->d.gaa.len_so_far, ctx->d.gaa.assoc); |
| kfree(ctx->d.gaa.assoc); |
| if (ctx->hdev) |
| hci_dev_put(ctx->hdev); |
| return 1; |
| } |
| |
| struct hmac_sha256_result { |
| struct completion completion; |
| int err; |
| }; |
| |
| static void hmac_sha256_final(struct crypto_async_request *req, int err) |
| { |
| struct hmac_sha256_result *r = req->data; |
| if (err == -EINPROGRESS) |
| return; |
| r->err = err; |
| complete(&r->completion); |
| } |
| |
| int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, |
| u8 *output, u8 outlen) |
| { |
| int ret = 0; |
| struct crypto_ahash *tfm; |
| struct scatterlist sg; |
| struct ahash_request *req; |
| struct hmac_sha256_result tresult; |
| void *hash_buff = NULL; |
| |
| unsigned char hash_result[64]; |
| int i; |
| |
| memset(output, 0, outlen); |
| |
| init_completion(&tresult.completion); |
| |
| tfm = crypto_alloc_ahash("hmac(sha256)", CRYPTO_ALG_TYPE_AHASH, |
| CRYPTO_ALG_TYPE_AHASH_MASK); |
| if (IS_ERR(tfm)) { |
| BT_DBG("crypto_alloc_ahash failed"); |
| ret = PTR_ERR(tfm); |
| goto err_tfm; |
| } |
| |
| req = ahash_request_alloc(tfm, GFP_KERNEL); |
| if (!req) { |
| BT_DBG("failed to allocate request for hmac(sha256)"); |
| ret = -ENOMEM; |
| goto err_req; |
| } |
| |
| ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, |
| hmac_sha256_final, &tresult); |
| |
| hash_buff = kzalloc(psize, GFP_KERNEL); |
| if (!hash_buff) { |
| BT_DBG("failed to kzalloc hash_buff"); |
| ret = -ENOMEM; |
| goto err_hash_buf; |
| } |
| |
| memset(hash_result, 0, 64); |
| memcpy(hash_buff, plaintext, psize); |
| sg_init_one(&sg, hash_buff, psize); |
| |
| if (ksize) { |
| crypto_ahash_clear_flags(tfm, ~0); |
| ret = crypto_ahash_setkey(tfm, key, ksize); |
| |
| if (ret) { |
| BT_DBG("crypto_ahash_setkey failed"); |
| goto err_setkey; |
| } |
| } |
| |
| ahash_request_set_crypt(req, &sg, hash_result, psize); |
| ret = crypto_ahash_digest(req); |
| |
| BT_DBG("ret 0x%x", ret); |
| |
| switch (ret) { |
| case 0: |
| for (i = 0; i < outlen; i++) |
| output[i] = hash_result[i]; |
| break; |
| case -EINPROGRESS: |
| case -EBUSY: |
| ret = wait_for_completion_interruptible(&tresult.completion); |
| if (!ret && !tresult.err) { |
| INIT_COMPLETION(tresult.completion); |
| break; |
| } else { |
| BT_DBG("wait_for_completion_interruptible failed"); |
| if (!ret) |
| ret = tresult.err; |
| goto out; |
| } |
| default: |
| goto out; |
| } |
| |
| out: |
| err_setkey: |
| kfree(hash_buff); |
| err_hash_buf: |
| ahash_request_free(req); |
| err_req: |
| crypto_free_ahash(tfm); |
| err_tfm: |
| return ret; |
| } |
| |
| static void show_key(u8 *k) |
| { |
| int i = 0; |
| for (i = 0; i < 32; i += 8) |
| BT_DBG(" %02x %02x %02x %02x %02x %02x %02x %02x", |
| *(k+i+0), *(k+i+1), *(k+i+2), *(k+i+3), |
| *(k+i+4), *(k+i+5), *(k+i+6), *(k+i+7)); |
| } |
| |
| static int physlink_security(struct hci_conn *conn, u8 *data, u8 *len, u8 *type) |
| { |
| u8 bt2_key[32]; |
| u8 gamp_key[32]; |
| u8 b802_key[32]; |
| int result; |
| |
| if (!hci_conn_check_link_mode(conn)) |
| return -EACCES; |
| |
| BT_DBG("key_type %d", conn->key_type); |
| if (conn->key_type < 3) |
| return -EACCES; |
| |
| *type = conn->key_type; |
| *len = 32; |
| memcpy(&bt2_key[0], conn->link_key, 16); |
| memcpy(&bt2_key[16], conn->link_key, 16); |
| result = hmac_sha256(bt2_key, 32, "gamp", 4, gamp_key, 32); |
| if (result) |
| goto ps_finished; |
| |
| if (conn->key_type == 3) { |
| BT_DBG("gamp_key"); |
| show_key(gamp_key); |
| memcpy(data, gamp_key, 32); |
| goto ps_finished; |
| } |
| |
| result = hmac_sha256(gamp_key, 32, "802b", 4, b802_key, 32); |
| if (result) |
| goto ps_finished; |
| |
| BT_DBG("802b_key"); |
| show_key(b802_key); |
| memcpy(data, b802_key, 32); |
| |
| ps_finished: |
| return result; |
| } |
| |
| static u8 amp_next_handle; |
| static inline u8 physlink_handle(struct hci_dev *hdev) |
| { |
| /* TODO amp_next_handle should be part of hci_dev */ |
| if (amp_next_handle == 0) |
| amp_next_handle = 1; |
| return amp_next_handle++; |
| } |
| |
| /* Start an Accept Physical Link sequence */ |
| static int createphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| struct amp_ctx *ctx = NULL; |
| struct a2mp_createphyslink_req *req; |
| |
| if (hdr->len < sizeof(*req)) |
| return -EINVAL; |
| req = (struct a2mp_createphyslink_req *) skb_pull(skb, sizeof(*hdr)); |
| skb_pull(skb, sizeof(*req)); |
| BT_DBG("local_id %d, remote_id %d", req->local_id, req->remote_id); |
| |
| /* initialize the context */ |
| ctx = create_ctx(AMP_ACCEPTPHYSLINK, AMP_APL_INIT); |
| if (!ctx) |
| return -ENOMEM; |
| ctx->d.apl.req_ident = hdr->ident; |
| ctx->d.apl.remote_id = req->local_id; |
| ctx->id = req->remote_id; |
| |
| /* add the supplied remote assoc to the context */ |
| ctx->d.apl.remote_assoc = kmalloc(skb->len, GFP_ATOMIC); |
| if (ctx->d.apl.remote_assoc) |
| memcpy(ctx->d.apl.remote_assoc, skb->data, skb->len); |
| ctx->d.apl.len_so_far = 0; |
| ctx->d.apl.rem_len = skb->len; |
| skb_pull(skb, skb->len); |
| ctx->hdev = hci_dev_get(ctx->id); |
| start_ctx(mgr, ctx); |
| return 0; |
| } |
| |
| static u8 acceptphyslink_handler(struct amp_ctx *ctx, u8 evt_type, void *data) |
| { |
| struct sk_buff *skb = data; |
| struct hci_cp_accept_phys_link acp; |
| struct hci_cp_write_remote_amp_assoc wcp; |
| struct hci_rp_write_remote_amp_assoc *wrp; |
| struct hci_ev_cmd_status *cs = data; |
| struct hci_ev_phys_link_complete *ev; |
| struct a2mp_createphyslink_rsp rsp; |
| struct amp_ctx *cplctx; |
| struct amp_ctx *aplctx; |
| u16 frag_len; |
| struct hci_conn *conn; |
| int result; |
| |
| BT_DBG("state %d", ctx->state); |
| result = -EINVAL; |
| rsp.status = 1; /* Invalid Controller ID */ |
| if (!ctx->hdev || !test_bit(HCI_UP, &ctx->hdev->flags)) |
| goto apl_finished; |
| if (evt_type == AMP_KILLED) { |
| result = -EAGAIN; |
| rsp.status = 4; /* Disconnect request received */ |
| goto apl_finished; |
| } |
| if (!ctx->d.apl.remote_assoc) { |
| result = -ENOMEM; |
| rsp.status = 2; /* Unable to Start */ |
| goto apl_finished; |
| } |
| |
| switch (ctx->state) { |
| case AMP_APL_INIT: |
| BT_DBG("local_id %d, remote_id %d", |
| ctx->id, ctx->d.apl.remote_id); |
| conn = hci_conn_hash_lookup_id(ctx->hdev, |
| &ctx->mgr->l2cap_conn->hcon->dst, |
| ctx->d.apl.remote_id); |
| if (conn) { |
| result = -EEXIST; |
| rsp.status = 5; /* Already Exists */ |
| goto apl_finished; |
| } |
| |
| aplctx = get_ctx_type(ctx, AMP_ACCEPTPHYSLINK); |
| if ((aplctx) && |
| (aplctx->d.cpl.remote_id == ctx->d.apl.remote_id)) { |
| BT_DBG("deferred to %p", aplctx); |
| aplctx->deferred = ctx; |
| break; |
| } |
| |
| cplctx = get_ctx_type(ctx, AMP_CREATEPHYSLINK); |
| if ((cplctx) && |
| (cplctx->d.cpl.remote_id == ctx->d.apl.remote_id)) { |
| struct hci_conn *bcon = ctx->mgr->l2cap_conn->hcon; |
| BT_DBG("local %s remote %s", |
| batostr(&bcon->hdev->bdaddr), |
| batostr(&bcon->dst)); |
| if ((cplctx->state < AMP_CPL_PL_COMPLETE) || |
| (bacmp(&bcon->hdev->bdaddr, &bcon->dst) < 0)) { |
| BT_DBG("COLLISION LOSER"); |
| cplctx->deferred = ctx; |
| cancel_ctx(cplctx); |
| break; |
| } else { |
| BT_DBG("COLLISION WINNER"); |
| result = -EISCONN; |
| rsp.status = 3; /* Collision */ |
| goto apl_finished; |
| } |
| } |
| |
| result = physlink_security(ctx->mgr->l2cap_conn->hcon, acp.data, |
| &acp.key_len, &acp.type); |
| if (result) { |
| BT_DBG("SECURITY"); |
| rsp.status = 6; /* Security Violation */ |
| goto apl_finished; |
| } |
| |
| ctx->d.apl.phy_handle = physlink_handle(ctx->hdev); |
| ctx->state = AMP_APL_APL_STATUS; |
| ctx->evt_type = AMP_HCI_CMD_STATUS; |
| ctx->opcode = HCI_OP_ACCEPT_PHYS_LINK; |
| acp.phy_handle = ctx->d.apl.phy_handle; |
| hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(acp), &acp); |
| break; |
| |
| case AMP_APL_APL_STATUS: |
| if (cs->status != 0) |
| goto apl_finished; |
| /* PAL will accept link, send a2mp response */ |
| rsp.local_id = ctx->id; |
| rsp.remote_id = ctx->d.apl.remote_id; |
| rsp.status = 0; |
| send_a2mp_cmd(ctx->mgr, ctx->d.apl.req_ident, |
| A2MP_CREATEPHYSLINK_RSP, sizeof(rsp), &rsp); |
| |
| /* send the first assoc fragment */ |
| wcp.phy_handle = ctx->d.apl.phy_handle; |
| wcp.len_so_far = cpu_to_le16(ctx->d.apl.len_so_far); |
| wcp.rem_len = cpu_to_le16(ctx->d.apl.rem_len); |
| frag_len = min_t(u16, 248, ctx->d.apl.rem_len); |
| memcpy(wcp.frag, ctx->d.apl.remote_assoc, frag_len); |
| ctx->state = AMP_APL_WRA_COMPLETE; |
| ctx->evt_type = AMP_HCI_CMD_CMPLT; |
| ctx->opcode = HCI_OP_WRITE_REMOTE_AMP_ASSOC; |
| hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); |
| break; |
| |
| case AMP_APL_WRA_COMPLETE: |
| /* received write remote amp assoc command complete event */ |
| wrp = (struct hci_rp_write_remote_amp_assoc *) skb->data; |
| if (wrp->status != 0) |
| goto apl_finished; |
| if (wrp->phy_handle != ctx->d.apl.phy_handle) |
| goto apl_finished; |
| /* update progress */ |
| frag_len = min_t(u16, 248, ctx->d.apl.rem_len); |
| ctx->d.apl.len_so_far += frag_len; |
| ctx->d.apl.rem_len -= frag_len; |
| if (ctx->d.apl.rem_len > 0) { |
| u8 *assoc; |
| /* another assoc fragment to send */ |
| wcp.phy_handle = ctx->d.apl.phy_handle; |
| wcp.len_so_far = cpu_to_le16(ctx->d.apl.len_so_far); |
| wcp.rem_len = cpu_to_le16(ctx->d.apl.rem_len); |
| frag_len = min_t(u16, 248, ctx->d.apl.rem_len); |
| assoc = ctx->d.apl.remote_assoc + ctx->d.apl.len_so_far; |
| memcpy(wcp.frag, assoc, frag_len); |
| hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); |
| break; |
| } |
| /* wait for physical link complete event */ |
| ctx->state = AMP_APL_PL_COMPLETE; |
| ctx->evt_type = AMP_HCI_EVENT; |
| ctx->evt_code = HCI_EV_PHYS_LINK_COMPLETE; |
| break; |
| |
| case AMP_APL_PL_COMPLETE: |
| /* physical link complete event received */ |
| if (skb->len < sizeof(*ev)) |
| goto apl_finished; |
| ev = (struct hci_ev_phys_link_complete *) skb->data; |
| if (ev->phy_handle != ctx->d.apl.phy_handle) |
| break; |
| if (ev->status != 0) |
| goto apl_finished; |
| conn = hci_conn_hash_lookup_handle(ctx->hdev, ev->phy_handle); |
| if (!conn) |
| goto apl_finished; |
| result = 0; |
| BT_DBG("PL_COMPLETE phy_handle %x", ev->phy_handle); |
| conn->dst_id = ctx->d.apl.remote_id; |
| bacpy(&conn->dst, &ctx->mgr->l2cap_conn->hcon->dst); |
| goto apl_finished; |
| break; |
| |
| default: |
| goto apl_finished; |
| break; |
| } |
| return 0; |
| |
| apl_finished: |
| if (ctx->sk) |
| l2cap_amp_physical_complete(result, ctx->id, |
| ctx->d.apl.remote_id, ctx->sk); |
| if ((result) && (ctx->state < AMP_APL_PL_COMPLETE)) { |
| rsp.local_id = ctx->id; |
| rsp.remote_id = ctx->d.apl.remote_id; |
| send_a2mp_cmd(ctx->mgr, ctx->d.apl.req_ident, |
| A2MP_CREATEPHYSLINK_RSP, sizeof(rsp), &rsp); |
| } |
| kfree(ctx->d.apl.remote_assoc); |
| if (ctx->sk) |
| sock_put(ctx->sk); |
| if (ctx->hdev) |
| hci_dev_put(ctx->hdev); |
| return 1; |
| } |
| |
| static void cancel_cpl_ctx(struct amp_ctx *ctx, u8 reason) |
| { |
| struct hci_cp_disconn_phys_link dcp; |
| |
| ctx->state = AMP_CPL_PL_CANCEL; |
| ctx->evt_type = AMP_HCI_EVENT; |
| ctx->evt_code = HCI_EV_DISCONN_PHYS_LINK_COMPLETE; |
| dcp.phy_handle = ctx->d.cpl.phy_handle; |
| dcp.reason = reason; |
| hci_send_cmd(ctx->hdev, HCI_OP_DISCONN_PHYS_LINK, sizeof(dcp), &dcp); |
| } |
| |
| static u8 createphyslink_handler(struct amp_ctx *ctx, u8 evt_type, void *data) |
| { |
| struct amp_ctrl *ctrl; |
| struct sk_buff *skb = data; |
| struct a2mp_cmd_hdr *hdr; |
| struct hci_ev_cmd_status *cs = data; |
| struct amp_ctx *cplctx; |
| struct a2mp_discover_req dreq; |
| struct a2mp_discover_rsp *drsp; |
| u16 *efm; |
| struct a2mp_getinfo_req greq; |
| struct a2mp_getinfo_rsp *grsp; |
| struct a2mp_cl *cl; |
| struct a2mp_getampassoc_req areq; |
| struct a2mp_getampassoc_rsp *arsp; |
| struct hci_cp_create_phys_link cp; |
| struct hci_cp_write_remote_amp_assoc wcp; |
| struct hci_rp_write_remote_amp_assoc *wrp; |
| struct hci_ev_channel_selected *cev; |
| struct hci_cp_read_local_amp_assoc rcp; |
| struct hci_rp_read_local_amp_assoc *rrp; |
| struct a2mp_createphyslink_req creq; |
| struct a2mp_createphyslink_rsp *crsp; |
| struct hci_ev_phys_link_complete *pev; |
| struct hci_ev_disconn_phys_link_complete *dev; |
| u8 *assoc, *rassoc, *lassoc; |
| u16 frag_len; |
| u16 rem_len; |
| int result = -EAGAIN; |
| struct hci_conn *conn; |
| |
| BT_DBG("state %d", ctx->state); |
| if (evt_type == AMP_KILLED) |
| goto cpl_finished; |
| |
| if (evt_type == AMP_CANCEL) { |
| if ((ctx->state < AMP_CPL_CPL_STATUS) || |
| ((ctx->state == AMP_CPL_PL_COMPLETE) && |
| !(ctx->evt_type & AMP_HCI_EVENT))) |
| goto cpl_finished; |
| |
| cancel_cpl_ctx(ctx, 0x16); |
| return 0; |
| } |
| |
| switch (ctx->state) { |
| case AMP_CPL_INIT: |
| cplctx = get_ctx_type(ctx, AMP_CREATEPHYSLINK); |
| if (cplctx) { |
| BT_DBG("deferred to %p", cplctx); |
| cplctx->deferred = ctx; |
| break; |
| } |
| ctx->state = AMP_CPL_DISC_RSP; |
| ctx->evt_type = AMP_A2MP_RSP; |
| ctx->rsp_ident = next_ident(ctx->mgr); |
| dreq.mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU); |
| dreq.ext_feat = 0; |
| send_a2mp_cmd(ctx->mgr, ctx->rsp_ident, A2MP_DISCOVER_REQ, |
| sizeof(dreq), &dreq); |
| break; |
| |
| case AMP_CPL_DISC_RSP: |
| drsp = (struct a2mp_discover_rsp *) skb_pull(skb, sizeof(*hdr)); |
| if (skb->len < (sizeof(*drsp))) { |
| result = -EINVAL; |
| goto cpl_finished; |
| } |
| |
| efm = (u16 *) skb_pull(skb, sizeof(*drsp)); |
| BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(drsp->mtu), |
| le16_to_cpu(drsp->ext_feat)); |
| |
| while (le16_to_cpu(drsp->ext_feat) & 0x8000) { |
| if (skb->len < sizeof(*efm)) { |
| result = -EINVAL; |
| goto cpl_finished; |
| } |
| drsp->ext_feat = *efm; |
| BT_DBG("efm 0x%4.4x", le16_to_cpu(drsp->ext_feat)); |
| efm = (u16 *) skb_pull(skb, sizeof(*efm)); |
| } |
| cl = (struct a2mp_cl *) efm; |
| |
| /* find the first remote and local controller with the |
| * same type |
| */ |
| greq.id = 0; |
| result = -ENODEV; |
| while (skb->len >= sizeof(*cl)) { |
| if ((cl->id != 0) && (greq.id == 0)) { |
| struct hci_dev *hdev; |
| hdev = hci_dev_get_type(cl->type); |
| if (hdev) { |
| struct hci_conn *conn; |
| ctx->hdev = hdev; |
| ctx->id = hdev->id; |
| ctx->d.cpl.remote_id = cl->id; |
| conn = hci_conn_hash_lookup_ba(hdev, |
| ACL_LINK, |
| &ctx->mgr->l2cap_conn->hcon->dst); |
| if (conn) { |
| BT_DBG("PL_COMPLETE exists %x", |
| (int) conn->handle); |
| result = 0; |
| } |
| ctrl = get_create_ctrl(ctx->mgr, |
| cl->id); |
| if (ctrl) { |
| ctrl->type = cl->type; |
| ctrl->status = cl->status; |
| } |
| greq.id = cl->id; |
| } |
| } |
| cl = (struct a2mp_cl *) skb_pull(skb, sizeof(*cl)); |
| } |
| if ((!greq.id) || (!result)) |
| goto cpl_finished; |
| ctx->state = AMP_CPL_GETINFO_RSP; |
| ctx->evt_type = AMP_A2MP_RSP; |
| ctx->rsp_ident = next_ident(ctx->mgr); |
| send_a2mp_cmd(ctx->mgr, ctx->rsp_ident, A2MP_GETINFO_REQ, |
| sizeof(greq), &greq); |
| break; |
| |
| case AMP_CPL_GETINFO_RSP: |
| if (skb->len < sizeof(*grsp)) |
| goto cpl_finished; |
| grsp = (struct a2mp_getinfo_rsp *) skb_pull(skb, sizeof(*hdr)); |
| skb_pull(skb, sizeof(*grsp)); |
| if (grsp->status) |
| goto cpl_finished; |
| if (grsp->id != ctx->d.cpl.remote_id) |
| goto cpl_finished; |
| ctrl = get_ctrl(ctx->mgr, grsp->id); |
| if (!ctrl) |
| goto cpl_finished; |
| ctrl->status = grsp->status; |
| ctrl->total_bw = le32_to_cpu(grsp->total_bw); |
| ctrl->max_bw = le32_to_cpu(grsp->max_bw); |
| ctrl->min_latency = le32_to_cpu(grsp->min_latency); |
| ctrl->pal_cap = le16_to_cpu(grsp->pal_cap); |
| ctrl->max_assoc_size = le16_to_cpu(grsp->assoc_size); |
| |
| ctx->d.cpl.max_len = ctrl->max_assoc_size; |
| |
| /* setup up GAA request */ |
| areq.id = ctx->d.cpl.remote_id; |
| |
| /* advance context state */ |
| ctx->state = AMP_CPL_GAA_RSP; |
| ctx->evt_type = AMP_A2MP_RSP; |
| ctx->rsp_ident = next_ident(ctx->mgr); |
| send_a2mp_cmd(ctx->mgr, ctx->rsp_ident, A2MP_GETAMPASSOC_REQ, |
| sizeof(areq), &areq); |
| break; |
| |
| case AMP_CPL_GAA_RSP: |
| if (skb->len < sizeof(*arsp)) |
| goto cpl_finished; |
| hdr = (void *) skb->data; |
| arsp = (void *) skb_pull(skb, sizeof(*hdr)); |
| if (arsp->status != 0) |
| goto cpl_finished; |
| |
| /* store away remote assoc */ |
| assoc = (u8 *) skb_pull(skb, sizeof(*arsp)); |
| ctx->d.cpl.len_so_far = 0; |
| ctx->d.cpl.rem_len = hdr->len - sizeof(*arsp); |
| skb_pull(skb, ctx->d.cpl.rem_len); |
| rassoc = kmalloc(ctx->d.cpl.rem_len, GFP_ATOMIC); |
| if (!rassoc) |
| goto cpl_finished; |
| memcpy(rassoc, assoc, ctx->d.cpl.rem_len); |
| ctx->d.cpl.remote_assoc = rassoc; |
| |
| /* set up CPL command */ |
| ctx->d.cpl.phy_handle = physlink_handle(ctx->hdev); |
| cp.phy_handle = ctx->d.cpl.phy_handle; |
| if (physlink_security(ctx->mgr->l2cap_conn->hcon, cp.data, |
| &cp.key_len, &cp.type)) { |
| result = -EPERM; |
| goto cpl_finished; |
| } |
| |
| /* advance context state */ |
| ctx->state = AMP_CPL_CPL_STATUS; |
| ctx->evt_type = AMP_HCI_CMD_STATUS; |
| ctx->opcode = HCI_OP_CREATE_PHYS_LINK; |
| hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(cp), &cp); |
| break; |
| |
| case AMP_CPL_CPL_STATUS: |
| /* received create physical link command status */ |
| if (cs->status != 0) |
| goto cpl_finished; |
| /* send the first assoc fragment */ |
| wcp.phy_handle = ctx->d.cpl.phy_handle; |
| wcp.len_so_far = ctx->d.cpl.len_so_far; |
| wcp.rem_len = cpu_to_le16(ctx->d.cpl.rem_len); |
| frag_len = min_t(u16, 248, ctx->d.cpl.rem_len); |
| memcpy(wcp.frag, ctx->d.cpl.remote_assoc, frag_len); |
| ctx->state = AMP_CPL_WRA_COMPLETE; |
| ctx->evt_type = AMP_HCI_CMD_CMPLT; |
| ctx->opcode = HCI_OP_WRITE_REMOTE_AMP_ASSOC; |
| hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); |
| break; |
| |
| case AMP_CPL_WRA_COMPLETE: |
| /* received write remote amp assoc command complete event */ |
| if (skb->len < sizeof(*wrp)) |
| goto cpl_finished; |
| wrp = (struct hci_rp_write_remote_amp_assoc *) skb->data; |
| if (wrp->status != 0) |
| goto cpl_finished; |
| if (wrp->phy_handle != ctx->d.cpl.phy_handle) |
| goto cpl_finished; |
| |
| /* update progress */ |
| frag_len = min_t(u16, 248, ctx->d.cpl.rem_len); |
| ctx->d.cpl.len_so_far += frag_len; |
| ctx->d.cpl.rem_len -= frag_len; |
| if (ctx->d.cpl.rem_len > 0) { |
| /* another assoc fragment to send */ |
| wcp.phy_handle = ctx->d.cpl.phy_handle; |
| wcp.len_so_far = cpu_to_le16(ctx->d.cpl.len_so_far); |
| wcp.rem_len = cpu_to_le16(ctx->d.cpl.rem_len); |
| frag_len = min_t(u16, 248, ctx->d.cpl.rem_len); |
| memcpy(wcp.frag, |
| ctx->d.cpl.remote_assoc + ctx->d.cpl.len_so_far, |
| frag_len); |
| hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); |
| break; |
| } |
| /* now wait for channel selected event */ |
| ctx->state = AMP_CPL_CHANNEL_SELECT; |
| ctx->evt_type = AMP_HCI_EVENT; |
| ctx->evt_code = HCI_EV_CHANNEL_SELECTED; |
| break; |
| |
| case AMP_CPL_CHANNEL_SELECT: |
| /* received channel selection event */ |
| if (skb->len < sizeof(*cev)) |
| goto cpl_finished; |
| cev = (void *) skb->data; |
| /* TODO - PK This check is valid but Libra PAL returns 0 for handle during |
| Create Physical Link collision scenario |
| if (cev->phy_handle != ctx->d.cpl.phy_handle) |
| goto cpl_finished; |
| */ |
| |
| /* request the first local assoc fragment */ |
| rcp.phy_handle = ctx->d.cpl.phy_handle; |
| rcp.len_so_far = 0; |
| rcp.max_len = ctx->d.cpl.max_len; |
| lassoc = kmalloc(ctx->d.cpl.max_len, GFP_ATOMIC); |
| if (!lassoc) |
| goto cpl_finished; |
| ctx->d.cpl.local_assoc = lassoc; |
| ctx->d.cpl.len_so_far = 0; |
| ctx->state = AMP_CPL_RLA_COMPLETE; |
| ctx->evt_type = AMP_HCI_CMD_CMPLT; |
| ctx->opcode = HCI_OP_READ_LOCAL_AMP_ASSOC; |
| hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(rcp), &rcp); |
| break; |
| |
| case AMP_CPL_RLA_COMPLETE: |
| /* received read local amp assoc command complete event */ |
| if (skb->len < 4) |
| goto cpl_finished; |
| rrp = (struct hci_rp_read_local_amp_assoc *) skb->data; |
| if (rrp->status) |
| goto cpl_finished; |
| if (rrp->phy_handle != ctx->d.cpl.phy_handle) |
| goto cpl_finished; |
| rem_len = le16_to_cpu(rrp->rem_len); |
| skb_pull(skb, 4); |
| frag_len = skb->len; |
| |
| if (ctx->d.cpl.len_so_far + rem_len > ctx->d.cpl.max_len) |
| goto cpl_finished; |
| |
| /* save this fragment in context */ |
| lassoc = ctx->d.cpl.local_assoc + ctx->d.cpl.len_so_far; |
| memcpy(lassoc, rrp->frag, frag_len); |
| ctx->d.cpl.len_so_far += frag_len; |
| rem_len -= frag_len; |
| if (rem_len > 0) { |
| /* request another local assoc fragment */ |
| rcp.phy_handle = ctx->d.cpl.phy_handle; |
| rcp.len_so_far = ctx->d.cpl.len_so_far; |
| rcp.max_len = ctx->d.cpl.max_len; |
| hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(rcp), &rcp); |
| } else { |
| creq.local_id = ctx->id; |
| creq.remote_id = ctx->d.cpl.remote_id; |
| /* wait for A2MP rsp AND phys link complete event */ |
| ctx->state = AMP_CPL_PL_COMPLETE; |
| ctx->evt_type = AMP_A2MP_RSP | AMP_HCI_EVENT; |
| ctx->rsp_ident = next_ident(ctx->mgr); |
| ctx->evt_code = HCI_EV_PHYS_LINK_COMPLETE; |
| send_a2mp_cmd2(ctx->mgr, ctx->rsp_ident, |
| A2MP_CREATEPHYSLINK_REQ, sizeof(creq), &creq, |
| ctx->d.cpl.len_so_far, ctx->d.cpl.local_assoc); |
| } |
| break; |
| |
| case AMP_CPL_PL_COMPLETE: |
| if (evt_type == AMP_A2MP_RSP) { |
| /* create physical link response received */ |
| ctx->evt_type &= ~AMP_A2MP_RSP; |
| if (skb->len < sizeof(*crsp)) |
| goto cpl_finished; |
| crsp = (void *) skb_pull(skb, sizeof(*hdr)); |
| if ((crsp->local_id != ctx->d.cpl.remote_id) || |
| (crsp->remote_id != ctx->id) || |
| (crsp->status != 0)) { |
| cancel_cpl_ctx(ctx, 0x13); |
| break; |
| } |
| |
| /* notify Qualcomm PAL */ |
| if (ctx->hdev->manufacturer == 0x001d) |
| hci_send_cmd(ctx->hdev, |
| hci_opcode_pack(0x3f, 0x00), 0, NULL); |
| } |
| if (evt_type == AMP_HCI_EVENT) { |
| ctx->evt_type &= ~AMP_HCI_EVENT; |
| /* physical link complete event received */ |
| if (skb->len < sizeof(*pev)) |
| goto cpl_finished; |
| pev = (void *) skb->data; |
| if (pev->phy_handle != ctx->d.cpl.phy_handle) |
| break; |
| if (pev->status != 0) |
| goto cpl_finished; |
| } |
| if (ctx->evt_type) |
| break; |
| conn = hci_conn_hash_lookup_handle(ctx->hdev, |
| ctx->d.cpl.phy_handle); |
| if (!conn) |
| goto cpl_finished; |
| result = 0; |
| BT_DBG("PL_COMPLETE phy_handle %x", ctx->d.cpl.phy_handle); |
| bacpy(&conn->dst, &ctx->mgr->l2cap_conn->hcon->dst); |
| conn->dst_id = ctx->d.cpl.remote_id; |
| conn->out = 1; |
| goto cpl_finished; |
| break; |
| |
| case AMP_CPL_PL_CANCEL: |
| dev = (void *) skb->data; |
| BT_DBG("PL_COMPLETE cancelled %x", dev->phy_handle); |
| result = -EISCONN; |
| goto cpl_finished; |
| break; |
| |
| default: |
| goto cpl_finished; |
| break; |
| } |
| return 0; |
| |
| cpl_finished: |
| l2cap_amp_physical_complete(result, ctx->id, ctx->d.cpl.remote_id, |
| ctx->sk); |
| if (ctx->sk) |
| sock_put(ctx->sk); |
| if (ctx->hdev) |
| hci_dev_put(ctx->hdev); |
| kfree(ctx->d.cpl.remote_assoc); |
| kfree(ctx->d.cpl.local_assoc); |
| return 1; |
| } |
| |
| static int disconnphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (void *) skb->data; |
| struct a2mp_disconnphyslink_req *req; |
| struct a2mp_disconnphyslink_rsp rsp; |
| struct hci_dev *hdev; |
| struct hci_conn *conn; |
| struct amp_ctx *aplctx; |
| |
| BT_DBG("mgr %p skb %p", mgr, skb); |
| if (hdr->len < sizeof(*req)) |
| return -EINVAL; |
| req = (void *) skb_pull(skb, sizeof(*hdr)); |
| skb_pull(skb, sizeof(*req)); |
| |
| rsp.local_id = req->remote_id; |
| rsp.remote_id = req->local_id; |
| rsp.status = 0; |
| BT_DBG("local_id %d remote_id %d", |
| (int) rsp.local_id, (int) rsp.remote_id); |
| hdev = hci_dev_get(rsp.local_id); |
| if (!hdev) { |
| rsp.status = 1; /* Invalid Controller ID */ |
| goto dpl_finished; |
| } |
| BT_DBG("hdev %p", hdev); |
| conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, |
| &mgr->l2cap_conn->hcon->dst); |
| if (!conn) { |
| aplctx = get_ctx_mgr(mgr, AMP_ACCEPTPHYSLINK); |
| if (aplctx) { |
| kill_ctx(aplctx); |
| rsp.status = 0; |
| goto dpl_finished; |
| } |
| rsp.status = 2; /* No Physical Link exists */ |
| goto dpl_finished; |
| } |
| BT_DBG("conn %p", conn); |
| hci_disconnect(conn, 0x13); |
| |
| dpl_finished: |
| send_a2mp_cmd(mgr, hdr->ident, |
| A2MP_DISCONNPHYSLINK_RSP, sizeof(rsp), &rsp); |
| if (hdev) |
| hci_dev_put(hdev); |
| return 0; |
| } |
| |
| static int execute_ctx(struct amp_ctx *ctx, u8 evt_type, void *data) |
| { |
| struct amp_mgr *mgr = ctx->mgr; |
| u8 finished = 0; |
| |
| if (!mgr->connected) |
| return 0; |
| |
| switch (ctx->type) { |
| case AMP_GETAMPASSOC: |
| finished = getampassoc_handler(ctx, evt_type, data); |
| break; |
| case AMP_CREATEPHYSLINK: |
| finished = createphyslink_handler(ctx, evt_type, data); |
| break; |
| case AMP_ACCEPTPHYSLINK: |
| finished = acceptphyslink_handler(ctx, evt_type, data); |
| break; |
| } |
| |
| if (!finished) |
| mod_timer(&(ctx->timer), jiffies + |
| msecs_to_jiffies(A2MP_RSP_TIMEOUT)); |
| else |
| destroy_ctx(ctx); |
| return finished; |
| } |
| |
| static int cancel_ctx(struct amp_ctx *ctx) |
| { |
| return execute_ctx(ctx, AMP_CANCEL, 0); |
| } |
| |
| static int kill_ctx(struct amp_ctx *ctx) |
| { |
| return execute_ctx(ctx, AMP_KILLED, 0); |
| } |
| |
| static void ctx_timeout_worker(struct work_struct *w) |
| { |
| struct amp_work_ctx_timeout *work = (struct amp_work_ctx_timeout *) w; |
| struct amp_ctx *ctx = work->ctx; |
| kill_ctx(ctx); |
| kfree(work); |
| } |
| |
| static void ctx_timeout(unsigned long data) |
| { |
| struct amp_ctx *ctx = (struct amp_ctx *) data; |
| struct amp_work_ctx_timeout *work; |
| |
| BT_DBG("ctx %p", ctx); |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, ctx_timeout_worker); |
| work->ctx = ctx; |
| if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) |
| kfree(work); |
| } |
| } |
| |
| static void launch_ctx(struct amp_mgr *mgr) |
| { |
| struct amp_ctx *ctx = NULL; |
| |
| BT_DBG("mgr %p", mgr); |
| read_lock(&mgr->ctx_list_lock); |
| if (!list_empty(&mgr->ctx_list)) |
| ctx = list_first_entry(&mgr->ctx_list, struct amp_ctx, list); |
| read_unlock(&mgr->ctx_list_lock); |
| BT_DBG("ctx %p", ctx); |
| if (ctx) |
| execute_ctx(ctx, AMP_INIT, NULL); |
| } |
| |
| static inline int a2mp_rsp(struct amp_mgr *mgr, struct sk_buff *skb) |
| { |
| struct amp_ctx *ctx; |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| u16 hdr_len = le16_to_cpu(hdr->len); |
| |
| /* find context waiting for A2MP rsp with this rsp's identifier */ |
| BT_DBG("ident %d code %d", hdr->ident, hdr->code); |
| ctx = get_ctx_a2mp(mgr, hdr->ident); |
| if (ctx) { |
| execute_ctx(ctx, AMP_A2MP_RSP, skb); |
| } else { |
| BT_DBG("context not found"); |
| skb_pull(skb, sizeof(*hdr)); |
| if (hdr_len > skb->len) |
| hdr_len = skb->len; |
| skb_pull(skb, hdr_len); |
| } |
| return 0; |
| } |
| |
| /* L2CAP-A2MP interface */ |
| |
| static void a2mp_receive(struct sock *sk, struct sk_buff *skb) |
| { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| int len; |
| int err = 0; |
| struct amp_mgr *mgr; |
| |
| mgr = get_amp_mgr_sk(sk); |
| if (!mgr) |
| goto a2mp_finished; |
| |
| len = skb->len; |
| while (len >= sizeof(*hdr)) { |
| struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; |
| u16 clen = le16_to_cpu(hdr->len); |
| |
| BT_DBG("code 0x%02x id %d len %d", hdr->code, hdr->ident, clen); |
| if (clen > len || !hdr->ident) { |
| err = -EINVAL; |
| break; |
| } |
| switch (hdr->code) { |
| case A2MP_COMMAND_REJ: |
| command_rej(mgr, skb); |
| break; |
| case A2MP_DISCOVER_REQ: |
| err = discover_req(mgr, skb); |
| break; |
| case A2MP_CHANGE_NOTIFY: |
| err = change_notify(mgr, skb); |
| break; |
| case A2MP_GETINFO_REQ: |
| err = getinfo_req(mgr, skb); |
| break; |
| case A2MP_GETAMPASSOC_REQ: |
| err = getampassoc_req(mgr, skb); |
| break; |
| case A2MP_CREATEPHYSLINK_REQ: |
| err = createphyslink_req(mgr, skb); |
| break; |
| case A2MP_DISCONNPHYSLINK_REQ: |
| err = disconnphyslink_req(mgr, skb); |
| break; |
| case A2MP_CHANGE_RSP: |
| case A2MP_DISCOVER_RSP: |
| case A2MP_GETINFO_RSP: |
| case A2MP_GETAMPASSOC_RSP: |
| case A2MP_CREATEPHYSLINK_RSP: |
| case A2MP_DISCONNPHYSLINK_RSP: |
| err = a2mp_rsp(mgr, skb); |
| break; |
| default: |
| BT_ERR("Unknown A2MP signaling command 0x%2.2x", |
| hdr->code); |
| skb_pull(skb, sizeof(*hdr)); |
| err = -EINVAL; |
| break; |
| } |
| len = skb->len; |
| } |
| |
| a2mp_finished: |
| if (err && mgr) { |
| struct a2mp_cmd_rej rej; |
| rej.reason = cpu_to_le16(0); |
| send_a2mp_cmd(mgr, hdr->ident, A2MP_COMMAND_REJ, |
| sizeof(rej), &rej); |
| } |
| } |
| |
| /* L2CAP-A2MP interface */ |
| |
| static int send_a2mp(struct socket *sock, u8 *data, int len) |
| { |
| struct kvec iv = { data, len }; |
| struct msghdr msg; |
| |
| memset(&msg, 0, sizeof(msg)); |
| |
| return kernel_sendmsg(sock, &msg, &iv, 1, len); |
| } |
| |
| static void data_ready_worker(struct work_struct *w) |
| { |
| struct amp_work_data_ready *work = (struct amp_work_data_ready *) w; |
| struct sock *sk = work->sk; |
| struct sk_buff *skb; |
| |
| /* skb_dequeue() is thread-safe */ |
| while ((skb = skb_dequeue(&sk->sk_receive_queue))) { |
| a2mp_receive(sk, skb); |
| kfree_skb(skb); |
| } |
| sock_put(work->sk); |
| kfree(work); |
| } |
| |
| static void data_ready(struct sock *sk, int bytes) |
| { |
| struct amp_work_data_ready *work; |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, data_ready_worker); |
| sock_hold(sk); |
| work->sk = sk; |
| work->bytes = bytes; |
| if (!queue_work(amp_workqueue, (struct work_struct *) work)) { |
| kfree(work); |
| sock_put(sk); |
| } |
| } |
| } |
| |
| static void state_change_worker(struct work_struct *w) |
| { |
| struct amp_work_state_change *work = (struct amp_work_state_change *) w; |
| struct amp_mgr *mgr; |
| switch (work->sk->sk_state) { |
| case BT_CONNECTED: |
| /* socket is up */ |
| BT_DBG("CONNECTED"); |
| mgr = get_amp_mgr_sk(work->sk); |
| if (mgr) { |
| mgr->connected = 1; |
| if (mgr->skb) { |
| l2cap_recv_deferred_frame(work->sk, mgr->skb); |
| mgr->skb = NULL; |
| } |
| launch_ctx(mgr); |
| } |
| break; |
| |
| case BT_CLOSED: |
| /* connection is gone */ |
| BT_DBG("CLOSED"); |
| mgr = get_amp_mgr_sk(work->sk); |
| if (mgr) { |
| if (!sock_flag(work->sk, SOCK_DEAD)) |
| sock_release(mgr->a2mp_sock); |
| mgr->a2mp_sock = NULL; |
| remove_amp_mgr(mgr); |
| } |
| break; |
| |
| default: |
| /* something else happened */ |
| break; |
| } |
| sock_put(work->sk); |
| kfree(work); |
| } |
| |
| static void state_change(struct sock *sk) |
| { |
| struct amp_work_state_change *work; |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, state_change_worker); |
| sock_hold(sk); |
| work->sk = sk; |
| if (!queue_work(amp_workqueue, (struct work_struct *) work)) { |
| kfree(work); |
| sock_put(sk); |
| } |
| } |
| } |
| |
| static struct socket *open_fixed_channel(bdaddr_t *src, bdaddr_t *dst) |
| { |
| int err; |
| struct socket *sock; |
| struct sockaddr_l2 addr; |
| struct sock *sk; |
| struct l2cap_options opts = {L2CAP_A2MP_DEFAULT_MTU, |
| L2CAP_A2MP_DEFAULT_MTU, L2CAP_DEFAULT_FLUSH_TO, |
| L2CAP_MODE_ERTM, 1, 0xFF, 1}; |
| |
| |
| err = sock_create_kern(PF_BLUETOOTH, SOCK_SEQPACKET, |
| BTPROTO_L2CAP, &sock); |
| |
| if (err) { |
| BT_ERR("sock_create_kern failed %d", err); |
| return NULL; |
| } |
| |
| sk = sock->sk; |
| sk->sk_data_ready = data_ready; |
| sk->sk_state_change = state_change; |
| |
| memset(&addr, 0, sizeof(addr)); |
| bacpy(&addr.l2_bdaddr, src); |
| addr.l2_family = AF_BLUETOOTH; |
| addr.l2_cid = L2CAP_CID_A2MP; |
| err = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr)); |
| if (err) { |
| BT_ERR("kernel_bind failed %d", err); |
| sock_release(sock); |
| return NULL; |
| } |
| |
| l2cap_fixed_channel_config(sk, &opts); |
| |
| memset(&addr, 0, sizeof(addr)); |
| bacpy(&addr.l2_bdaddr, dst); |
| addr.l2_family = AF_BLUETOOTH; |
| addr.l2_cid = L2CAP_CID_A2MP; |
| err = kernel_connect(sock, (struct sockaddr *) &addr, sizeof(addr), |
| O_NONBLOCK); |
| if ((err == 0) || (err == -EINPROGRESS)) |
| return sock; |
| else { |
| BT_ERR("kernel_connect failed %d", err); |
| sock_release(sock); |
| return NULL; |
| } |
| } |
| |
| static void conn_ind_worker(struct work_struct *w) |
| { |
| struct amp_work_conn_ind *work = (struct amp_work_conn_ind *) w; |
| struct hci_conn *hcon = work->hcon; |
| struct sk_buff *skb = work->skb; |
| struct amp_mgr *mgr; |
| |
| mgr = get_create_amp_mgr(hcon, skb); |
| BT_DBG("mgr %p", mgr); |
| hci_conn_put(hcon); |
| kfree(work); |
| } |
| |
| static void create_physical_worker(struct work_struct *w) |
| { |
| struct amp_work_create_physical *work = |
| (struct amp_work_create_physical *) w; |
| |
| create_physical(work->conn, work->sk); |
| sock_put(work->sk); |
| kfree(work); |
| } |
| |
| static void accept_physical_worker(struct work_struct *w) |
| { |
| struct amp_work_accept_physical *work = |
| (struct amp_work_accept_physical *) w; |
| |
| accept_physical(work->conn, work->id, work->sk); |
| sock_put(work->sk); |
| kfree(work); |
| } |
| |
| /* L2CAP Fixed Channel interface */ |
| |
| void amp_conn_ind(struct hci_conn *hcon, struct sk_buff *skb) |
| { |
| struct amp_work_conn_ind *work; |
| BT_DBG("hcon %p, skb %p", hcon, skb); |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, conn_ind_worker); |
| hci_conn_hold(hcon); |
| work->hcon = hcon; |
| work->skb = skb; |
| if (!queue_work(amp_workqueue, (struct work_struct *) work)) { |
| hci_conn_put(hcon); |
| kfree(work); |
| } |
| } |
| } |
| |
| /* L2CAP Physical Link interface */ |
| |
| void amp_create_physical(struct l2cap_conn *conn, struct sock *sk) |
| { |
| struct amp_work_create_physical *work; |
| BT_DBG("conn %p", conn); |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, create_physical_worker); |
| work->conn = conn; |
| work->sk = sk; |
| sock_hold(sk); |
| if (!queue_work(amp_workqueue, (struct work_struct *) work)) { |
| sock_put(sk); |
| kfree(work); |
| } |
| } |
| } |
| |
| void amp_accept_physical(struct l2cap_conn *conn, u8 id, struct sock *sk) |
| { |
| struct amp_work_accept_physical *work; |
| BT_DBG("conn %p", conn); |
| |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, accept_physical_worker); |
| work->conn = conn; |
| work->sk = sk; |
| work->id = id; |
| sock_hold(sk); |
| if (!queue_work(amp_workqueue, (struct work_struct *) work)) { |
| sock_put(sk); |
| kfree(work); |
| } |
| } |
| } |
| |
| /* HCI interface */ |
| |
| static void amp_cmd_cmplt_worker(struct work_struct *w) |
| { |
| struct amp_work_cmd_cmplt *work = (struct amp_work_cmd_cmplt *) w; |
| struct hci_dev *hdev = work->hdev; |
| u16 opcode = work->opcode; |
| struct sk_buff *skb = work->skb; |
| struct amp_ctx *ctx; |
| |
| ctx = get_ctx_hdev(hdev, AMP_HCI_CMD_CMPLT, opcode); |
| if (ctx) |
| execute_ctx(ctx, AMP_HCI_CMD_CMPLT, skb); |
| kfree_skb(skb); |
| kfree(w); |
| } |
| |
| static void amp_cmd_cmplt_evt(struct hci_dev *hdev, u16 opcode, |
| struct sk_buff *skb) |
| { |
| struct amp_work_cmd_cmplt *work; |
| struct sk_buff *skbc; |
| BT_DBG("hdev %p opcode 0x%x skb %p len %d", |
| hdev, opcode, skb, skb->len); |
| skbc = skb_clone(skb, GFP_ATOMIC); |
| if (!skbc) |
| return; |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, amp_cmd_cmplt_worker); |
| work->hdev = hdev; |
| work->opcode = opcode; |
| work->skb = skbc; |
| if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) |
| kfree(work); |
| } |
| } |
| |
| static void amp_cmd_status_worker(struct work_struct *w) |
| { |
| struct amp_work_cmd_status *work = (struct amp_work_cmd_status *) w; |
| struct hci_dev *hdev = work->hdev; |
| u16 opcode = work->opcode; |
| u8 status = work->status; |
| struct amp_ctx *ctx; |
| |
| ctx = get_ctx_hdev(hdev, AMP_HCI_CMD_STATUS, opcode); |
| if (ctx) |
| execute_ctx(ctx, AMP_HCI_CMD_STATUS, &status); |
| kfree(w); |
| } |
| |
| static void amp_cmd_status_evt(struct hci_dev *hdev, u16 opcode, u8 status) |
| { |
| struct amp_work_cmd_status *work; |
| BT_DBG("hdev %p opcode 0x%x status %d", hdev, opcode, status); |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, amp_cmd_status_worker); |
| work->hdev = hdev; |
| work->opcode = opcode; |
| work->status = status; |
| if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) |
| kfree(work); |
| } |
| } |
| |
| static void amp_event_worker(struct work_struct *w) |
| { |
| struct amp_work_event *work = (struct amp_work_event *) w; |
| struct hci_dev *hdev = work->hdev; |
| u8 event = work->event; |
| struct sk_buff *skb = work->skb; |
| struct amp_ctx *ctx; |
| |
| if (event == HCI_EV_AMP_STATUS_CHANGE) { |
| struct hci_ev_amp_status_change *ev; |
| if (skb->len < sizeof(*ev)) |
| goto amp_event_finished; |
| ev = (void *) skb->data; |
| if (ev->status != 0) |
| goto amp_event_finished; |
| if (ev->amp_status == hdev->amp_status) |
| goto amp_event_finished; |
| hdev->amp_status = ev->amp_status; |
| send_a2mp_change_notify(); |
| goto amp_event_finished; |
| } |
| ctx = get_ctx_hdev(hdev, AMP_HCI_EVENT, (u16) event); |
| if (ctx) |
| execute_ctx(ctx, AMP_HCI_EVENT, skb); |
| |
| amp_event_finished: |
| kfree_skb(skb); |
| kfree(w); |
| } |
| |
| static void amp_evt(struct hci_dev *hdev, u8 event, struct sk_buff *skb) |
| { |
| struct amp_work_event *work; |
| struct sk_buff *skbc; |
| BT_DBG("hdev %p event 0x%x skb %p", hdev, event, skb); |
| skbc = skb_clone(skb, GFP_ATOMIC); |
| if (!skbc) |
| return; |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, amp_event_worker); |
| work->hdev = hdev; |
| work->event = event; |
| work->skb = skbc; |
| if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) |
| kfree(work); |
| } |
| } |
| |
| static void amp_dev_event_worker(struct work_struct *w) |
| { |
| send_a2mp_change_notify(); |
| kfree(w); |
| } |
| |
| static int amp_dev_event(struct notifier_block *this, unsigned long event, |
| void *ptr) |
| { |
| struct hci_dev *hdev = (struct hci_dev *) ptr; |
| struct amp_work_event *work; |
| |
| if (hdev->amp_type == HCI_BREDR) |
| return NOTIFY_DONE; |
| |
| switch (event) { |
| case HCI_DEV_UNREG: |
| case HCI_DEV_REG: |
| case HCI_DEV_UP: |
| case HCI_DEV_DOWN: |
| BT_DBG("hdev %p event %ld", hdev, event); |
| work = kmalloc(sizeof(*work), GFP_ATOMIC); |
| if (work) { |
| INIT_WORK((struct work_struct *) work, |
| amp_dev_event_worker); |
| if (queue_work(amp_workqueue, |
| (struct work_struct *) work) == 0) |
| kfree(work); |
| } |
| } |
| return NOTIFY_DONE; |
| } |
| |
| |
| /* L2CAP module init continued */ |
| |
| static struct notifier_block amp_notifier = { |
| .notifier_call = amp_dev_event |
| }; |
| |
| static struct amp_mgr_cb hci_amp = { |
| .amp_cmd_complete_event = amp_cmd_cmplt_evt, |
| .amp_cmd_status_event = amp_cmd_status_evt, |
| .amp_event = amp_evt |
| }; |
| |
| int amp_init(void) |
| { |
| hci_register_amp(&hci_amp); |
| hci_register_notifier(&_notifier); |
| amp_next_handle = 1; |
| amp_workqueue = create_singlethread_workqueue("a2mp"); |
| if (!amp_workqueue) |
| return -EPERM; |
| return 0; |
| } |
| |
| void amp_exit(void) |
| { |
| hci_unregister_amp(&hci_amp); |
| hci_unregister_notifier(&_notifier); |
| flush_workqueue(amp_workqueue); |
| destroy_workqueue(amp_workqueue); |
| } |