/*
Copyright (c) 2010,2011 Code Aurora Forum. All rights reserved.
Copyright (c) 2011,2012 Intel Corp.
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 <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/l2cap.h>
#include "hci_request.h"
#include "a2mp.h"
#include "amp.h"
#define A2MP_FEAT_EXT 0x8000
/* Global AMP Manager list */
static LIST_HEAD(amp_mgr_list);
static DEFINE_MUTEX(amp_mgr_list_lock);
/* A2MP build & send command helper functions */
static struct a2mp_cmd *__a2mp_build(u8 code, u8 ident, u16 len, void *data)
{
struct a2mp_cmd *cmd;
int plen;
plen = sizeof(*cmd) + len;
cmd = kzalloc(plen, GFP_KERNEL);
if (!cmd)
return NULL;
cmd->code = code;
cmd->ident = ident;
cmd->len = cpu_to_le16(len);
memcpy(cmd->data, data, len);
return cmd;
}
static void a2mp_send(struct amp_mgr *mgr, u8 code, u8 ident, u16 len, void *data)
{
struct l2cap_chan *chan = mgr->a2mp_chan;
struct a2mp_cmd *cmd;
u16 total_len = len + sizeof(*cmd);
struct kvec iv;
struct msghdr msg;
cmd = __a2mp_build(code, ident, len, data);
if (!cmd)
return;
iv.iov_base = cmd;
iv.iov_len = total_len;
memset(&msg, 0, sizeof(msg));
iov_iter_kvec(&msg.msg_iter, WRITE | ITER_KVEC, &iv, 1, total_len);
l2cap_chan_send(chan, &msg, total_len);
kfree(cmd);
}
static u8 __next_ident(struct amp_mgr *mgr)
{
if (++mgr->ident == 0)
mgr->ident = 1;
return mgr->ident;
}
static struct amp_mgr *amp_mgr_lookup_by_state(u8 state)
{
struct amp_mgr *mgr;
mutex_lock(&_mgr_list_lock);
list_for_each_entry(mgr, &_mgr_list, list) {
if (test_and_clear_bit(state, &mgr->state)) {
amp_mgr_get(mgr);
mutex_unlock(&_mgr_list_lock);
return mgr;
}
}
mutex_unlock(&_mgr_list_lock);
return NULL;
}
/* hci_dev_list shall be locked */
static void __a2mp_add_cl(struct amp_mgr *mgr, struct a2mp_cl *cl)
{
struct hci_dev *hdev;
int i = 1;
cl[0].id = AMP_ID_BREDR;
cl[0].type = AMP_TYPE_BREDR;
cl[0].status = AMP_STATUS_BLUETOOTH_ONLY;
list_for_each_entry(hdev, &hci_dev_list, list) {
if (hdev->dev_type == HCI_AMP) {
cl[i].id = hdev->id;
cl[i].type = hdev->amp_type;
if (test_bit(HCI_UP, &hdev->flags))
cl[i].status = hdev->amp_status;
else
cl[i].status = AMP_STATUS_POWERED_DOWN;
i++;
}
}
}
/* Processing A2MP messages */
static int a2mp_command_rej(struct amp_mgr *mgr, struct sk_buff *skb,
struct a2mp_cmd *hdr)
{
struct a2mp_cmd_rej *rej = (void *) skb->data;
if (le16_to_cpu(hdr->len) < sizeof(*rej))
return -EINVAL;
BT_DBG("ident %d reason %d", hdr->ident, le16_to_cpu(rej->reason));
skb_pull(skb, sizeof(*rej));
return 0;