Commit f764a6c2 authored by Luiz Augusto von Dentz's avatar Luiz Augusto von Dentz
Browse files

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: default avatarLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
parent eca0ae4a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -220,6 +220,8 @@ struct bt_codecs {
#define BT_CODEC_TRANSPARENT	0x03
#define BT_CODEC_MSBC		0x05

#define BT_ISO_BASE		20

__printf(1, 2)
void bt_info(const char *fmt, ...);
__printf(1, 2)
+11 −0
Original line number Diff line number Diff line
@@ -10,12 +10,23 @@

/* ISO defaults */
#define ISO_DEFAULT_MTU		251
#define ISO_MAX_NUM_BIS		0x1f

/* ISO socket broadcast address */
struct sockaddr_iso_bc {
	bdaddr_t	bc_bdaddr;
	__u8		bc_bdaddr_type;
	__u8		bc_sid;
	__u8		bc_num_bis;
	__u8		bc_bis[ISO_MAX_NUM_BIS];
};

/* ISO socket address */
struct sockaddr_iso {
	sa_family_t	iso_family;
	bdaddr_t	iso_bdaddr;
	__u8		iso_bdaddr_type;
	struct sockaddr_iso_bc iso_bc[];
};

#endif /* __ISO_H */
+357 −34
Original line number Diff line number Diff line
@@ -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;

	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 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr *addr,
	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);
	bt_dev_dbg(hdev, "bdaddr %pMR", 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;
	/* 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);

		goto done;
	}

	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);
	}

done:
	if (!sk)
		return lm;

		if (!bacmp(&iso_pi(sk)->src, &hdev->bdaddr) ||
		    !bacmp(&iso_pi(sk)->src, BDADDR_ANY)) {
	lm |= HCI_LM_ACCEPT;

	if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags))
		*flags |= HCI_PROTO_DEFER;
			break;
		}
	}
	read_unlock(&iso_sk_list.lock);

	return lm;
}