diff options
author | Luiz Augusto von Dentz <luiz.von.dentz@intel.com> | 2022-03-09 13:14:41 -0800 |
---|---|---|
committer | Luiz Augusto von Dentz <luiz.von.dentz@intel.com> | 2022-07-22 17:14:13 -0700 |
commit | f764a6c2c1e446f560faa3232271a0637369170b (patch) | |
tree | 56c488d81ad85ea2b5c507730594014bfda95fcd /net/bluetooth/iso.c | |
parent | eca0ae4aea66914515e5e3098ea051b518ee5316 (diff) |
Bluetooth: ISO: Add broadcast support
This adds broadcast support for BTPROTO_ISO by extending the
sockaddr_iso with a new struct sockaddr_iso_bc where the socket user
can set the broadcast address when receiving, the SID and the BIS
indexes it wants to synchronize.
When using BTPROTO_ISO for broadcast the roles are:
Broadcaster -> uses connect with address set to BDADDR_ANY:
> tools/isotest -s 00:00:00:00:00:00
Broadcast Receiver -> uses listen with address set to broadcaster:
> tools/isotest -d 00:AA:01:00:00:00
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Diffstat (limited to 'net/bluetooth/iso.c')
-rw-r--r-- | net/bluetooth/iso.c | 391 |
1 files changed, 357 insertions, 34 deletions
diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index caaaa670cc2c..ff09c353e64e 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -50,8 +50,14 @@ struct iso_pinfo { __u8 src_type; bdaddr_t dst; __u8 dst_type; + __u8 bc_sid; + __u8 bc_num_bis; + __u8 bc_bis[ISO_MAX_NUM_BIS]; + __u16 sync_handle; __u32 flags; struct bt_iso_qos qos; + __u8 base_len; + __u8 base[HCI_MAX_PER_AD_LENGTH]; struct iso_conn *conn; }; @@ -130,6 +136,7 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) static void iso_chan_del(struct sock *sk, int err) { struct iso_conn *conn; + struct sock *parent; conn = iso_pi(sk)->conn; @@ -147,7 +154,14 @@ static void iso_chan_del(struct sock *sk, int err) sk->sk_state = BT_CLOSED; sk->sk_err = err; - sk->sk_state_change(sk); + + parent = bt_sk(sk)->parent; + if (parent) { + bt_accept_unlink(sk); + parent->sk_data_ready(parent); + } else { + sk->sk_state_change(sk); + } sock_set_flag(sk, SOCK_ZAPPED); } @@ -218,7 +232,70 @@ static int iso_chan_add(struct iso_conn *conn, struct sock *sk, return err; } -static int iso_connect(struct sock *sk) +static int iso_connect_bis(struct sock *sk) +{ + struct iso_conn *conn; + struct hci_conn *hcon; + struct hci_dev *hdev; + int err; + + BT_DBG("%pMR", &iso_pi(sk)->src); + + hdev = hci_get_route(&iso_pi(sk)->dst, &iso_pi(sk)->src, + iso_pi(sk)->src_type); + if (!hdev) + return -EHOSTUNREACH; + + hci_dev_lock(hdev); + + if (!bis_capable(hdev)) { + err = -EOPNOTSUPP; + goto done; + } + + /* Fail if out PHYs are marked as disabled */ + if (!iso_pi(sk)->qos.out.phy) { + err = -EINVAL; + goto done; + } + + hcon = hci_connect_bis(hdev, &iso_pi(sk)->dst, iso_pi(sk)->dst_type, + &iso_pi(sk)->qos, iso_pi(sk)->base_len, + iso_pi(sk)->base); + if (IS_ERR(hcon)) { + err = PTR_ERR(hcon); + goto done; + } + + conn = iso_conn_add(hcon); + if (!conn) { + hci_conn_drop(hcon); + err = -ENOMEM; + goto done; + } + + /* Update source addr of the socket */ + bacpy(&iso_pi(sk)->src, &hcon->src); + + err = iso_chan_add(conn, sk, NULL); + if (err) + goto done; + + if (hcon->state == BT_CONNECTED) { + iso_sock_clear_timer(sk); + sk->sk_state = BT_CONNECTED; + } else { + sk->sk_state = BT_CONNECT; + iso_sock_set_timer(sk, sk->sk_sndtimeo); + } + +done: + hci_dev_unlock(hdev); + hci_dev_put(hdev); + return err; +} + +static int iso_connect_cis(struct sock *sk) { struct iso_conn *conn; struct hci_conn *hcon; @@ -359,10 +436,39 @@ static struct sock *__iso_get_sock_listen_by_addr(bdaddr_t *ba) return NULL; } -/* Find socket listening on source bdaddr. +static struct sock *__iso_get_sock_listen_by_sid(bdaddr_t *ba, bdaddr_t *bc, + __u8 sid) +{ + struct sock *sk; + + sk_for_each(sk, &iso_sk_list.head) { + if (sk->sk_state != BT_LISTEN) + continue; + + if (bacmp(&iso_pi(sk)->src, ba)) + continue; + + if (bacmp(&iso_pi(sk)->dst, bc)) + continue; + + if (iso_pi(sk)->bc_sid == sid) + return sk; + } + + return NULL; +} + +typedef bool (*iso_sock_match_t)(struct sock *sk, void *data); + +/* Find socket listening: + * source bdaddr (Unicast) + * destination bdaddr (Broadcast only) + * match func - pass NULL to ignore + * match func data - pass -1 to ignore * Returns closest match. */ -static struct sock *iso_get_sock_listen(bdaddr_t *src) +static struct sock *iso_get_sock_listen(bdaddr_t *src, bdaddr_t *dst, + iso_sock_match_t match, void *data) { struct sock *sk = NULL, *sk1 = NULL; @@ -372,6 +478,14 @@ static struct sock *iso_get_sock_listen(bdaddr_t *src) if (sk->sk_state != BT_LISTEN) continue; + /* Match Broadcast destination */ + if (bacmp(dst, BDADDR_ANY) && bacmp(&iso_pi(sk)->dst, dst)) + continue; + + /* Use Match function if provided */ + if (match && !match(sk, data)) + continue; + /* Exact match. */ if (!bacmp(&iso_pi(sk)->src, src)) break; @@ -587,6 +701,38 @@ static int iso_sock_create(struct net *net, struct socket *sock, int protocol, return 0; } +static int iso_sock_bind_bc(struct socket *sock, struct sockaddr *addr, + int addr_len) +{ + struct sockaddr_iso *sa = (struct sockaddr_iso *)addr; + struct sock *sk = sock->sk; + int i; + + BT_DBG("sk %p bc_sid %u bc_num_bis %u", sk, sa->iso_bc->bc_sid, + sa->iso_bc->bc_num_bis); + + if (addr_len > sizeof(*sa) + sizeof(*sa->iso_bc) || + sa->iso_bc->bc_num_bis < 0x01 || sa->iso_bc->bc_num_bis > 0x1f) + return -EINVAL; + + bacpy(&iso_pi(sk)->dst, &sa->iso_bc->bc_bdaddr); + iso_pi(sk)->dst_type = sa->iso_bc->bc_bdaddr_type; + iso_pi(sk)->sync_handle = -1; + iso_pi(sk)->bc_sid = sa->iso_bc->bc_sid; + iso_pi(sk)->bc_num_bis = sa->iso_bc->bc_num_bis; + + for (i = 0; i < iso_pi(sk)->bc_num_bis; i++) { + if (sa->iso_bc->bc_bis[i] < 0x01 || + sa->iso_bc->bc_bis[i] > 0x1f) + return -EINVAL; + + memcpy(iso_pi(sk)->bc_bis, sa->iso_bc->bc_bis, + iso_pi(sk)->bc_num_bis); + } + + return 0; +} + static int iso_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) { @@ -621,6 +767,13 @@ static int iso_sock_bind(struct socket *sock, struct sockaddr *addr, bacpy(&iso_pi(sk)->src, &sa->iso_bdaddr); iso_pi(sk)->src_type = sa->iso_bdaddr_type; + /* Check for Broadcast address */ + if (addr_len > sizeof(*sa)) { + err = iso_sock_bind_bc(sock, addr, addr_len); + if (err) + goto done; + } + sk->sk_state = BT_BOUND; done: @@ -656,7 +809,11 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr *addr, bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr); iso_pi(sk)->dst_type = sa->iso_bdaddr_type; - err = iso_connect(sk); + if (bacmp(&iso_pi(sk)->dst, BDADDR_ANY)) + err = iso_connect_cis(sk); + else + err = iso_connect_bis(sk); + if (err) goto done; @@ -670,10 +827,59 @@ done: return err; } +static int iso_listen_bis(struct sock *sk) +{ + struct hci_dev *hdev; + int err = 0; + + BT_DBG("%pMR -> %pMR (SID 0x%2.2x)", &iso_pi(sk)->src, + &iso_pi(sk)->dst, iso_pi(sk)->bc_sid); + + write_lock(&iso_sk_list.lock); + + if (__iso_get_sock_listen_by_sid(&iso_pi(sk)->src, &iso_pi(sk)->dst, + iso_pi(sk)->bc_sid)) + err = -EADDRINUSE; + + write_unlock(&iso_sk_list.lock); + + if (err) + return err; + + hdev = hci_get_route(&iso_pi(sk)->dst, &iso_pi(sk)->src, + iso_pi(sk)->src_type); + if (!hdev) + return -EHOSTUNREACH; + + hci_dev_lock(hdev); + + err = hci_pa_create_sync(hdev, &iso_pi(sk)->dst, iso_pi(sk)->dst_type, + iso_pi(sk)->bc_sid); + + hci_dev_unlock(hdev); + + return err; +} + +static int iso_listen_cis(struct sock *sk) +{ + int err = 0; + + BT_DBG("%pMR", &iso_pi(sk)->src); + + write_lock(&iso_sk_list.lock); + + if (__iso_get_sock_listen_by_addr(&iso_pi(sk)->src)) + err = -EADDRINUSE; + + write_unlock(&iso_sk_list.lock); + + return err; +} + static int iso_sock_listen(struct socket *sock, int backlog) { struct sock *sk = sock->sk; - bdaddr_t *src = &iso_pi(sk)->src; int err = 0; BT_DBG("sk %p backlog %d", sk, backlog); @@ -690,21 +896,19 @@ static int iso_sock_listen(struct socket *sock, int backlog) goto done; } - write_lock(&iso_sk_list.lock); + if (!bacmp(&iso_pi(sk)->dst, BDADDR_ANY)) + err = iso_listen_cis(sk); + else + err = iso_listen_bis(sk); - if (__iso_get_sock_listen_by_addr(src)) { - err = -EADDRINUSE; - goto unlock; - } + if (err) + goto done; sk->sk_max_ack_backlog = backlog; sk->sk_ack_backlog = 0; sk->sk_state = BT_LISTEN; -unlock: - write_unlock(&iso_sk_list.lock); - done: release_sock(sk); return err; @@ -886,7 +1090,7 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, release_sock(sk); return 0; case BT_CONNECT: - err = iso_connect(sk); + err = iso_connect_cis(sk); release_sock(sk); return err; } @@ -917,10 +1121,6 @@ static bool check_io_qos(struct bt_iso_io_qos *qos) static bool check_qos(struct bt_iso_qos *qos) { - /* CIS shall not be set */ - if (qos->cis != BT_ISO_QOS_CIS_UNSET) - return false; - if (qos->sca > 0x07) return false; @@ -996,6 +1196,29 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; + case BT_ISO_BASE: + if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND && + sk->sk_state != BT_CONNECT2) { + err = -EINVAL; + break; + } + + if (optlen > sizeof(iso_pi(sk)->base)) { + err = -EOVERFLOW; + break; + } + + len = min_t(unsigned int, sizeof(iso_pi(sk)->base), optlen); + + if (copy_from_sockptr(iso_pi(sk)->base, optval, len)) { + err = -EFAULT; + break; + } + + iso_pi(sk)->base_len = len; + + break; + default: err = -ENOPROTOOPT; break; @@ -1011,6 +1234,8 @@ static int iso_sock_getsockopt(struct socket *sock, int level, int optname, struct sock *sk = sock->sk; int len, err = 0; struct bt_iso_qos qos; + u8 base_len; + u8 *base; BT_DBG("sk %p", sk); @@ -1044,6 +1269,21 @@ static int iso_sock_getsockopt(struct socket *sock, int level, int optname, break; + case BT_ISO_BASE: + if (sk->sk_state == BT_CONNECTED) { + base_len = iso_pi(sk)->conn->hcon->le_per_adv_data_len; + base = iso_pi(sk)->conn->hcon->le_per_adv_data; + } else { + base_len = iso_pi(sk)->base_len; + base = iso_pi(sk)->base; + } + + len = min_t(unsigned int, len, base_len); + if (copy_to_user(optval, base, len)) + err = -EFAULT; + + break; + default: err = -ENOPROTOOPT; break; @@ -1126,10 +1366,18 @@ struct iso_list_data { int count; }; +static bool iso_match_big(struct sock *sk, void *data) +{ + struct hci_evt_le_big_sync_estabilished *ev = data; + + return ev->handle == iso_pi(sk)->qos.big; +} + static void iso_conn_ready(struct iso_conn *conn) { struct sock *parent; struct sock *sk = conn->sk; + struct hci_ev_le_big_sync_estabilished *ev; BT_DBG("conn %p", conn); @@ -1143,7 +1391,16 @@ static void iso_conn_ready(struct iso_conn *conn) return; } - parent = iso_get_sock_listen(&conn->hcon->src); + ev = hci_recv_event_data(conn->hcon->hdev, + HCI_EVT_LE_BIG_SYNC_ESTABILISHED); + if (ev) + parent = iso_get_sock_listen(&conn->hcon->src, + &conn->hcon->dst, + iso_match_big, ev); + else + parent = iso_get_sock_listen(&conn->hcon->src, + BDADDR_ANY, NULL, NULL); + if (!parent) { iso_conn_unlock(conn); return; @@ -1163,6 +1420,17 @@ static void iso_conn_ready(struct iso_conn *conn) bacpy(&iso_pi(sk)->src, &conn->hcon->src); iso_pi(sk)->src_type = conn->hcon->src_type; + + /* If hcon has no destination address (BDADDR_ANY) it means it + * was created by HCI_EV_LE_BIG_SYNC_ESTABILISHED so we need to + * initialize using the parent socket destination address. + */ + if (!bacmp(&conn->hcon->dst, BDADDR_ANY)) { + bacpy(&conn->hcon->dst, &iso_pi(parent)->dst); + conn->hcon->dst_type = iso_pi(parent)->dst_type; + conn->hcon->sync_handle = iso_pi(parent)->sync_handle; + } + bacpy(&iso_pi(sk)->dst, &conn->hcon->dst); iso_pi(sk)->dst_type = conn->hcon->dst_type; @@ -1183,30 +1451,85 @@ static void iso_conn_ready(struct iso_conn *conn) } } +static bool iso_match_sid(struct sock *sk, void *data) +{ + struct hci_ev_le_pa_sync_established *ev = data; + + return ev->sid == iso_pi(sk)->bc_sid; +} + +static bool iso_match_sync_handle(struct sock *sk, void *data) +{ + struct hci_evt_le_big_info_adv_report *ev = data; + + return le16_to_cpu(ev->sync_handle) == iso_pi(sk)->sync_handle; +} + /* ----- ISO interface with lower layer (HCI) ----- */ + int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) { + struct hci_ev_le_pa_sync_established *ev1; + struct hci_evt_le_big_info_adv_report *ev2; struct sock *sk; int lm = 0; - BT_DBG("hdev %s, bdaddr %pMR", hdev->name, bdaddr); - - /* Find listening sockets */ - read_lock(&iso_sk_list.lock); - sk_for_each(sk, &iso_sk_list.head) { - if (sk->sk_state != BT_LISTEN) - continue; + bt_dev_dbg(hdev, "bdaddr %pMR", bdaddr); + + /* Broadcast receiver requires handling of some events before it can + * proceed to establishing a BIG sync: + * + * 1. HCI_EV_LE_PA_SYNC_ESTABLISHED: The socket may specify a specific + * SID to listen to and once sync is estabilished its handle needs to + * be stored in iso_pi(sk)->sync_handle so it can be matched once + * receiving the BIG Info. + * 2. HCI_EVT_LE_BIG_INFO_ADV_REPORT: When connect_ind is triggered by a + * a BIG Info it attempts to check if there any listening socket with + * the same sync_handle and if it does then attempt to create a sync. + */ + ev1 = hci_recv_event_data(hdev, HCI_EV_LE_PA_SYNC_ESTABLISHED); + if (ev1) { + sk = iso_get_sock_listen(&hdev->bdaddr, bdaddr, iso_match_sid, + ev1); + if (sk) + iso_pi(sk)->sync_handle = le16_to_cpu(ev1->handle); - if (!bacmp(&iso_pi(sk)->src, &hdev->bdaddr) || - !bacmp(&iso_pi(sk)->src, BDADDR_ANY)) { - lm |= HCI_LM_ACCEPT; + goto done; + } - if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) - *flags |= HCI_PROTO_DEFER; - break; + ev2 = hci_recv_event_data(hdev, HCI_EVT_LE_BIG_INFO_ADV_REPORT); + if (ev2) { + sk = iso_get_sock_listen(&hdev->bdaddr, bdaddr, + iso_match_sync_handle, ev2); + if (sk) { + int err; + + if (ev2->num_bis < iso_pi(sk)->bc_num_bis) + iso_pi(sk)->bc_num_bis = ev2->num_bis; + + err = hci_le_big_create_sync(hdev, + &iso_pi(sk)->qos, + iso_pi(sk)->sync_handle, + iso_pi(sk)->bc_num_bis, + iso_pi(sk)->bc_bis); + if (err) { + bt_dev_err(hdev, "hci_le_big_create_sync: %d", + err); + sk = NULL; + } } + } else { + sk = iso_get_sock_listen(&hdev->bdaddr, BDADDR_ANY, NULL, NULL); } - read_unlock(&iso_sk_list.lock); + +done: + if (!sk) + return lm; + + lm |= HCI_LM_ACCEPT; + + if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) + *flags |= HCI_PROTO_DEFER; return lm; } |