/* Copyright (c) 2010-2012 The Linux Foundation. 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; list_for_each_entry(mgr, &_mgr_list, list) { if (mgr->discovered) send_a2mp_cl(mgr, next_ident(mgr), A2MP_CHANGE_NOTIFY, 0, NULL); } } 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); }