Bluetooth: Add BT_MODE socket option
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Fri, 27 Mar 2020 18:32:15 +0000 (11:32 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Thu, 2 Apr 2020 06:25:19 +0000 (08:25 +0200)
This adds BT_MODE socket option which can be used to set L2CAP modes,
including modes only supported over LE which were not supported using
the L2CAP_OPTIONS.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
include/net/bluetooth/bluetooth.h
net/bluetooth/l2cap_sock.c

index 1576353..3fa7b1e 100644 (file)
@@ -139,6 +139,14 @@ struct bt_voice {
 #define BT_PHY_LE_CODED_TX     0x00002000
 #define BT_PHY_LE_CODED_RX     0x00004000
 
+#define BT_MODE                        15
+
+#define BT_MODE_BASIC          0x00
+#define BT_MODE_ERTM           0x01
+#define BT_MODE_STREAMING      0x02
+#define BT_MODE_LE_FLOWCTL     0x03
+#define BT_MODE_EXT_FLOWCTL    0x04
+
 __printf(1, 2)
 void bt_info(const char *fmt, ...);
 __printf(1, 2)
index cfb4026..1cea42e 100644 (file)
@@ -395,6 +395,24 @@ static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr,
        return sizeof(struct sockaddr_l2);
 }
 
+static int l2cap_get_mode(struct l2cap_chan *chan)
+{
+       switch (chan->mode) {
+       case L2CAP_MODE_BASIC:
+               return BT_MODE_BASIC;
+       case L2CAP_MODE_ERTM:
+               return BT_MODE_ERTM;
+       case L2CAP_MODE_STREAMING:
+               return BT_MODE_STREAMING;
+       case L2CAP_MODE_LE_FLOWCTL:
+               return BT_MODE_LE_FLOWCTL;
+       case L2CAP_MODE_EXT_FLOWCTL:
+               return BT_MODE_EXT_FLOWCTL;
+       }
+
+       return -EINVAL;
+}
+
 static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
                                     char __user *optval, int __user *optlen)
 {
@@ -522,7 +540,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
        struct bt_security sec;
        struct bt_power pwr;
        u32 phys;
-       int len, err = 0;
+       int len, mode, err = 0;
 
        BT_DBG("sk %p", sk);
 
@@ -638,6 +656,27 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
                        err = -EFAULT;
                break;
 
+       case BT_MODE:
+               if (!enable_ecred) {
+                       err = -ENOPROTOOPT;
+                       break;
+               }
+
+               if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
+                       err = -EINVAL;
+                       break;
+               }
+
+               mode = l2cap_get_mode(chan);
+               if (mode < 0) {
+                       err = mode;
+                       break;
+               }
+
+               if (put_user(mode, (u8 __user *) optval))
+                       err = -EFAULT;
+               break;
+
        default:
                err = -ENOPROTOOPT;
                break;
@@ -780,6 +819,45 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
        return err;
 }
 
+static int l2cap_set_mode(struct l2cap_chan *chan, u8 mode)
+{
+       switch (mode) {
+       case BT_MODE_BASIC:
+               if (bdaddr_type_is_le(chan->src_type))
+                       return -EINVAL;
+               mode = L2CAP_MODE_BASIC;
+               clear_bit(CONF_STATE2_DEVICE, &chan->conf_state);
+               break;
+       case BT_MODE_ERTM:
+               if (!disable_ertm || bdaddr_type_is_le(chan->src_type))
+                       return -EINVAL;
+               mode = L2CAP_MODE_ERTM;
+               break;
+       case BT_MODE_STREAMING:
+               if (!disable_ertm || bdaddr_type_is_le(chan->src_type))
+                       return -EINVAL;
+               mode = L2CAP_MODE_STREAMING;
+               break;
+       case BT_MODE_LE_FLOWCTL:
+               if (!bdaddr_type_is_le(chan->src_type))
+                       return -EINVAL;
+               mode = L2CAP_MODE_LE_FLOWCTL;
+               break;
+       case BT_MODE_EXT_FLOWCTL:
+               /* TODO: Add support for ECRED PDUs to BR/EDR */
+               if (!bdaddr_type_is_le(chan->src_type))
+                       return -EINVAL;
+               mode = L2CAP_MODE_EXT_FLOWCTL;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       chan->mode = mode;
+
+       return 0;
+}
+
 static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
                                 char __user *optval, unsigned int optlen)
 {
@@ -985,6 +1063,39 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 
                break;
 
+       case BT_MODE:
+               if (!enable_ecred) {
+                       err = -ENOPROTOOPT;
+                       break;
+               }
+
+               BT_DBG("sk->sk_state %u", sk->sk_state);
+
+               if (sk->sk_state != BT_BOUND) {
+                       err = -EINVAL;
+                       break;
+               }
+
+               if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
+                       err = -EINVAL;
+                       break;
+               }
+
+               if (get_user(opt, (u8 __user *) optval)) {
+                       err = -EFAULT;
+                       break;
+               }
+
+               BT_DBG("opt %u", opt);
+
+               err = l2cap_set_mode(chan, opt);
+               if (err)
+                       break;
+
+               BT_DBG("mode 0x%2.2x", chan->mode);
+
+               break;
+
        default:
                err = -ENOPROTOOPT;
                break;