Bluetooth: Add signal handlers for channel moves
[linux-2.6.git] / net / bluetooth / l2cap_core.c
index c12d3bf..335dc6f 100644 (file)
@@ -60,7 +60,7 @@ int disable_ertm;
 int enable_hs;
 
 static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN;
-static u8 l2cap_fixed_chan[8] = { 0x02, };
+static u8 l2cap_fixed_chan[8] = { L2CAP_FC_L2CAP, };
 
 static LIST_HEAD(chan_list);
 static DEFINE_RWLOCK(chan_list_lock);
@@ -220,7 +220,7 @@ static u16 l2cap_alloc_cid(struct l2cap_conn *conn)
 
 static void l2cap_set_timer(struct l2cap_chan *chan, struct timer_list *timer, long timeout)
 {
-       BT_DBG("chan %p state %d timeout %ld", chan->sk, chan->state, timeout);
+       BT_DBG("chan %p state %d timeout %ld", chan, chan->state, timeout);
 
        if (!mod_timer(timer, jiffies + msecs_to_jiffies(timeout)))
                chan_hold(chan);
@@ -564,8 +564,27 @@ static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
                flags = ACL_START;
 
        bt_cb(skb)->force_active = BT_POWER_FORCE_ACTIVE_ON;
+       skb->priority = HCI_PRIO_MAX;
 
-       hci_send_acl(conn->hcon, skb, flags);
+       hci_send_acl(conn->hchan, skb, flags);
+}
+
+static void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb)
+{
+       struct hci_conn *hcon = chan->conn->hcon;
+       u16 flags;
+
+       BT_DBG("chan %p, skb %p len %d priority %u", chan, skb, skb->len,
+                                                       skb->priority);
+
+       if (!test_bit(FLAG_FLUSHABLE, &chan->flags) &&
+                                       lmp_no_flush_capable(hcon->hdev))
+               flags = ACL_START_NO_FLUSH;
+       else
+               flags = ACL_START;
+
+       bt_cb(skb)->force_active = test_bit(FLAG_FORCE_ACTIVE, &chan->flags);
+       hci_send_acl(chan->conn->hchan, skb, flags);
 }
 
 static inline void l2cap_send_sframe(struct l2cap_chan *chan, u32 control)
@@ -574,7 +593,6 @@ static inline void l2cap_send_sframe(struct l2cap_chan *chan, u32 control)
        struct l2cap_hdr *lh;
        struct l2cap_conn *conn = chan->conn;
        int count, hlen;
-       u8 flags;
 
        if (chan->state != BT_CONNECTED)
                return;
@@ -614,14 +632,8 @@ static inline void l2cap_send_sframe(struct l2cap_chan *chan, u32 control)
                put_unaligned_le16(fcs, skb_put(skb, L2CAP_FCS_SIZE));
        }
 
-       if (lmp_no_flush_capable(conn->hcon->hdev))
-               flags = ACL_START_NO_FLUSH;
-       else
-               flags = ACL_START;
-
-       bt_cb(skb)->force_active = test_bit(FLAG_FORCE_ACTIVE, &chan->flags);
-
-       hci_send_acl(chan->conn->hcon, skb, flags);
+       skb->priority = HCI_PRIO_MAX;
+       l2cap_do_send(chan, skb);
 }
 
 static inline void l2cap_send_rr_or_rnr(struct l2cap_chan *chan, u32 control)
@@ -1001,6 +1013,8 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
                chan->ops->close(chan->data);
        }
 
+       hci_chan_del(conn->hchan);
+
        if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT)
                del_timer_sync(&conn->info_timer);
 
@@ -1023,18 +1037,26 @@ static void security_timeout(unsigned long arg)
 static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
 {
        struct l2cap_conn *conn = hcon->l2cap_data;
+       struct hci_chan *hchan;
 
        if (conn || status)
                return conn;
 
+       hchan = hci_chan_create(hcon);
+       if (!hchan)
+               return NULL;
+
        conn = kzalloc(sizeof(struct l2cap_conn), GFP_ATOMIC);
-       if (!conn)
+       if (!conn) {
+               hci_chan_del(hchan);
                return NULL;
+       }
 
        hcon->l2cap_data = conn;
        conn->hcon = hcon;
+       conn->hchan = hchan;
 
-       BT_DBG("hcon %p conn %p", hcon, conn);
+       BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan);
 
        if (hcon->hdev->le_mtu && hcon->type == LE_LINK)
                conn->mtu = hcon->hdev->le_mtu;
@@ -1260,23 +1282,6 @@ static void l2cap_drop_acked_frames(struct l2cap_chan *chan)
                __clear_retrans_timer(chan);
 }
 
-static void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb)
-{
-       struct hci_conn *hcon = chan->conn->hcon;
-       u16 flags;
-
-       BT_DBG("chan %p, skb %p len %d", chan, skb, skb->len);
-
-       if (!test_bit(FLAG_FLUSHABLE, &chan->flags) &&
-                                       lmp_no_flush_capable(hcon->hdev))
-               flags = ACL_START_NO_FLUSH;
-       else
-               flags = ACL_START;
-
-       bt_cb(skb)->force_active = test_bit(FLAG_FORCE_ACTIVE, &chan->flags);
-       hci_send_acl(hcon, skb, flags);
-}
-
 static void l2cap_streaming_send(struct l2cap_chan *chan)
 {
        struct sk_buff *skb;
@@ -1483,6 +1488,8 @@ static inline int l2cap_skbuff_fromiovec(struct sock *sk, struct msghdr *msg, in
                if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count))
                        return -EFAULT;
 
+               (*frag)->priority = skb->priority;
+
                sent += count;
                len  -= count;
 
@@ -1492,7 +1499,9 @@ static inline int l2cap_skbuff_fromiovec(struct sock *sk, struct msghdr *msg, in
        return sent;
 }
 
-static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
+static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan,
+                                               struct msghdr *msg, size_t len,
+                                               u32 priority)
 {
        struct sock *sk = chan->sk;
        struct l2cap_conn *conn = chan->conn;
@@ -1500,7 +1509,7 @@ static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct
        int err, count, hlen = L2CAP_HDR_SIZE + L2CAP_PSMLEN_SIZE;
        struct l2cap_hdr *lh;
 
-       BT_DBG("sk %p len %d", sk, (int)len);
+       BT_DBG("sk %p len %d priority %u", sk, (int)len, priority);
 
        count = min_t(unsigned int, (conn->mtu - hlen), len);
        skb = bt_skb_send_alloc(sk, count + hlen,
@@ -1508,6 +1517,8 @@ static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct
        if (!skb)
                return ERR_PTR(err);
 
+       skb->priority = priority;
+
        /* Create L2CAP header */
        lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
        lh->cid = cpu_to_le16(chan->dcid);
@@ -1522,7 +1533,9 @@ static struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct
        return skb;
 }
 
-static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
+static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan,
+                                               struct msghdr *msg, size_t len,
+                                               u32 priority)
 {
        struct sock *sk = chan->sk;
        struct l2cap_conn *conn = chan->conn;
@@ -1538,6 +1551,8 @@ static struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, struct ms
        if (!skb)
                return ERR_PTR(err);
 
+       skb->priority = priority;
+
        /* Create L2CAP header */
        lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
        lh->cid = cpu_to_le16(chan->dcid);
@@ -1651,7 +1666,8 @@ static int l2cap_sar_segment_sdu(struct l2cap_chan *chan, struct msghdr *msg, si
        return size;
 }
 
-int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
+int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
+                                                               u32 priority)
 {
        struct sk_buff *skb;
        u32 control;
@@ -1659,7 +1675,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
 
        /* Connectionless channel */
        if (chan->chan_type == L2CAP_CHAN_CONN_LESS) {
-               skb = l2cap_create_connless_pdu(chan, msg, len);
+               skb = l2cap_create_connless_pdu(chan, msg, len, priority);
                if (IS_ERR(skb))
                        return PTR_ERR(skb);
 
@@ -1674,7 +1690,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
                        return -EMSGSIZE;
 
                /* Create a basic PDU */
-               skb = l2cap_create_basic_pdu(chan, msg, len);
+               skb = l2cap_create_basic_pdu(chan, msg, len, priority);
                if (IS_ERR(skb))
                        return PTR_ERR(skb);
 
@@ -2237,11 +2253,10 @@ done:
                                        return -ECONNREFUSED;
 
                                l2cap_add_conf_opt(&ptr, L2CAP_CONF_EFS,
-                                                               sizeof(efs),
+                                                       sizeof(efs),
                                                        (unsigned long) &efs);
                        } else {
-                               /* Send PENDING Conf Rsp and mark state
-                                  local PENDING */
+                               /* Send PENDING Conf Rsp */
                                result = L2CAP_CONF_PENDING;
                                set_bit(CONF_LOC_CONF_PEND, &chan->conf_state);
                        }
@@ -2373,8 +2388,8 @@ static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, voi
                case L2CAP_CONF_EWS:
                        chan->tx_win = min_t(u16, val,
                                                L2CAP_DEFAULT_EXT_WINDOW);
-                       l2cap_add_conf_opt(&ptr, L2CAP_CONF_EWS,
-                                                       2, chan->tx_win);
+                       l2cap_add_conf_opt(&ptr, L2CAP_CONF_EWS, 2,
+                                                       chan->tx_win);
                        break;
                }
        }
@@ -2801,7 +2816,7 @@ static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr
                set_bit(CONF_OUTPUT_DONE, &chan->conf_state);
 
                l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
-                               l2cap_build_conf_rsp(chan, rsp,
+                                       l2cap_build_conf_rsp(chan, rsp,
                                        L2CAP_CONF_SUCCESS, 0x0000), rsp);
        }
 
@@ -2856,7 +2871,7 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr
                        set_bit(CONF_OUTPUT_DONE, &chan->conf_state);
 
                        l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP,
-                                       l2cap_build_conf_rsp(chan, buf,
+                                               l2cap_build_conf_rsp(chan, buf,
                                                L2CAP_CONF_SUCCESS, 0x0000), buf);
                }
                goto done;
@@ -3020,9 +3035,15 @@ static inline int l2cap_information_req(struct l2cap_conn *conn, struct l2cap_cm
        } else if (type == L2CAP_IT_FIXED_CHAN) {
                u8 buf[12];
                struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf;
+
+               if (enable_hs)
+                       l2cap_fixed_chan[0] |= L2CAP_FC_A2MP;
+               else
+                       l2cap_fixed_chan[0] &= ~L2CAP_FC_A2MP;
+
                rsp->type   = cpu_to_le16(L2CAP_IT_FIXED_CHAN);
                rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS);
-               memcpy(buf + 4, l2cap_fixed_chan, 8);
+               memcpy(rsp->data, l2cap_fixed_chan, sizeof(l2cap_fixed_chan));
                l2cap_send_cmd(conn, cmd->ident,
                                        L2CAP_INFO_RSP, sizeof(buf), buf);
        } else {
@@ -3089,6 +3110,165 @@ static inline int l2cap_information_rsp(struct l2cap_conn *conn, struct l2cap_cm
        return 0;
 }
 
+static inline int l2cap_create_channel_req(struct l2cap_conn *conn,
+                                       struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+                                       void *data)
+{
+       struct l2cap_create_chan_req *req = data;
+       struct l2cap_create_chan_rsp rsp;
+       u16 psm, scid;
+
+       if (cmd_len != sizeof(*req))
+               return -EPROTO;
+
+       if (!enable_hs)
+               return -EINVAL;
+
+       psm = le16_to_cpu(req->psm);
+       scid = le16_to_cpu(req->scid);
+
+       BT_DBG("psm %d, scid %d, amp_id %d", psm, scid, req->amp_id);
+
+       /* Placeholder: Always reject */
+       rsp.dcid = 0;
+       rsp.scid = cpu_to_le16(scid);
+       rsp.result = L2CAP_CR_NO_MEM;
+       rsp.status = L2CAP_CS_NO_INFO;
+
+       l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP,
+                      sizeof(rsp), &rsp);
+
+       return 0;
+}
+
+static inline int l2cap_create_channel_rsp(struct l2cap_conn *conn,
+                                       struct l2cap_cmd_hdr *cmd, void *data)
+{
+       BT_DBG("conn %p", conn);
+
+       return l2cap_connect_rsp(conn, cmd, data);
+}
+
+static void l2cap_send_move_chan_rsp(struct l2cap_conn *conn, u8 ident,
+                                                       u16 icid, u16 result)
+{
+       struct l2cap_move_chan_rsp rsp;
+
+       BT_DBG("icid %d, result %d", icid, result);
+
+       rsp.icid = cpu_to_le16(icid);
+       rsp.result = cpu_to_le16(result);
+
+       l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_RSP, sizeof(rsp), &rsp);
+}
+
+static void l2cap_send_move_chan_cfm(struct l2cap_conn *conn,
+                               struct l2cap_chan *chan, u16 icid, u16 result)
+{
+       struct l2cap_move_chan_cfm cfm;
+       u8 ident;
+
+       BT_DBG("icid %d, result %d", icid, result);
+
+       ident = l2cap_get_ident(conn);
+       if (chan)
+               chan->ident = ident;
+
+       cfm.icid = cpu_to_le16(icid);
+       cfm.result = cpu_to_le16(result);
+
+       l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM, sizeof(cfm), &cfm);
+}
+
+static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident,
+                                                               u16 icid)
+{
+       struct l2cap_move_chan_cfm_rsp rsp;
+
+       BT_DBG("icid %d", icid);
+
+       rsp.icid = cpu_to_le16(icid);
+       l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp);
+}
+
+static inline int l2cap_move_channel_req(struct l2cap_conn *conn,
+                       struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
+{
+       struct l2cap_move_chan_req *req = data;
+       u16 icid = 0;
+       u16 result = L2CAP_MR_NOT_ALLOWED;
+
+       if (cmd_len != sizeof(*req))
+               return -EPROTO;
+
+       icid = le16_to_cpu(req->icid);
+
+       BT_DBG("icid %d, dest_amp_id %d", icid, req->dest_amp_id);
+
+       if (!enable_hs)
+               return -EINVAL;
+
+       /* Placeholder: Always refuse */
+       l2cap_send_move_chan_rsp(conn, cmd->ident, icid, result);
+
+       return 0;
+}
+
+static inline int l2cap_move_channel_rsp(struct l2cap_conn *conn,
+                       struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
+{
+       struct l2cap_move_chan_rsp *rsp = data;
+       u16 icid, result;
+
+       if (cmd_len != sizeof(*rsp))
+               return -EPROTO;
+
+       icid = le16_to_cpu(rsp->icid);
+       result = le16_to_cpu(rsp->result);
+
+       BT_DBG("icid %d, result %d", icid, result);
+
+       /* Placeholder: Always unconfirmed */
+       l2cap_send_move_chan_cfm(conn, NULL, icid, L2CAP_MC_UNCONFIRMED);
+
+       return 0;
+}
+
+static inline int l2cap_move_channel_confirm(struct l2cap_conn *conn,
+                       struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
+{
+       struct l2cap_move_chan_cfm *cfm = data;
+       u16 icid, result;
+
+       if (cmd_len != sizeof(*cfm))
+               return -EPROTO;
+
+       icid = le16_to_cpu(cfm->icid);
+       result = le16_to_cpu(cfm->result);
+
+       BT_DBG("icid %d, result %d", icid, result);
+
+       l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid);
+
+       return 0;
+}
+
+static inline int l2cap_move_channel_confirm_rsp(struct l2cap_conn *conn,
+                       struct l2cap_cmd_hdr *cmd, u16 cmd_len, void *data)
+{
+       struct l2cap_move_chan_cfm_rsp *rsp = data;
+       u16 icid;
+
+       if (cmd_len != sizeof(*rsp))
+               return -EPROTO;
+
+       icid = le16_to_cpu(rsp->icid);
+
+       BT_DBG("icid %d", icid);
+
+       return 0;
+}
+
 static inline int l2cap_check_conn_param(u16 min, u16 max, u16 latency,
                                                        u16 to_multiplier)
 {
@@ -3201,6 +3381,30 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn,
                err = l2cap_information_rsp(conn, cmd, data);
                break;
 
+       case L2CAP_CREATE_CHAN_REQ:
+               err = l2cap_create_channel_req(conn, cmd, cmd_len, data);
+               break;
+
+       case L2CAP_CREATE_CHAN_RSP:
+               err = l2cap_create_channel_rsp(conn, cmd, data);
+               break;
+
+       case L2CAP_MOVE_CHAN_REQ:
+               err = l2cap_move_channel_req(conn, cmd, cmd_len, data);
+               break;
+
+       case L2CAP_MOVE_CHAN_RSP:
+               err = l2cap_move_channel_rsp(conn, cmd, cmd_len, data);
+               break;
+
+       case L2CAP_MOVE_CHAN_CFM:
+               err = l2cap_move_channel_confirm(conn, cmd, cmd_len, data);
+               break;
+
+       case L2CAP_MOVE_CHAN_CFM_RSP:
+               err = l2cap_move_channel_confirm_rsp(conn, cmd, cmd_len, data);
+               break;
+
        default:
                BT_ERR("Unknown BR/EDR signaling command 0x%2.2x", cmd->code);
                err = -EINVAL;