/*
   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 <linux/interrupt.h>
#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(&amp_mgr_list_lock);
	list_del(&mgr->list);
	write_unlock(&amp_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(&amp_mgr_list_lock);
	list_for_each_entry(mgr, &amp_mgr_list, list) {
		if ((mgr->a2mp_sock) && (mgr->a2mp_sock->sk == sk)) {
			found = mgr;
			break;
		}
	}
	read_unlock(&amp_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(&amp_mgr_list_lock);
	list_for_each_entry(mgr, &amp_mgr_list, list) {
		if (mgr->l2cap_conn == hcon->l2cap_data) {
			BT_DBG("found %p", mgr);
			write_unlock(&amp_mgr_list_lock);
			goto gc_finished;
		}
	}
	write_unlock(&amp_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(&amp_mgr_list_lock);
	list_add(&(mgr->list), &amp_mgr_list);
	write_unlock(&amp_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(&amp_mgr_list_lock);
	list_for_each_entry(mgr, &amp_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(&amp_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, &amp_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(&amp_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(&amp_notifier);
	flush_workqueue(amp_workqueue);
	destroy_workqueue(amp_workqueue);
}
