1 /*
2  * Copyright 2023 NXP
3  * Copyright (c) 2024 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <errno.h>
9 #include <stdbool.h>
10 #include <stdint.h>
11 #include <string.h>
12 #include <sys/types.h>
13 
14 #include <zephyr/autoconf.h>
15 #include <zephyr/bluetooth/att.h>
16 #include <zephyr/bluetooth/audio/tmap.h>
17 #include <zephyr/bluetooth/bluetooth.h>
18 #include <zephyr/bluetooth/conn.h>
19 #include <zephyr/bluetooth/gatt.h>
20 #include <zephyr/bluetooth/uuid.h>
21 #include <zephyr/device.h>
22 #include <zephyr/init.h>
23 #include <zephyr/kernel.h>
24 #include <zephyr/logging/log.h>
25 #include <zephyr/sys/byteorder.h>
26 #include <zephyr/sys/check.h>
27 #include <zephyr/sys/util_macro.h>
28 #include <zephyr/types.h>
29 
30 #include "audio_internal.h"
31 
32 LOG_MODULE_REGISTER(bt_tmap, CONFIG_BT_TMAP_LOG_LEVEL);
33 
34 /* Hex value if all TMAP role bits are set */
35 #define TMAP_ALL_ROLES                                                                             \
36 	(BT_TMAP_ROLE_CG | BT_TMAP_ROLE_CT | BT_TMAP_ROLE_UMS | BT_TMAP_ROLE_UMR |                 \
37 	 BT_TMAP_ROLE_BMS | BT_TMAP_ROLE_BMR)
38 
39 static uint16_t tmap_role;
40 static const struct bt_tmap_cb *cb;
41 static bool tmas_found;
42 
43 static struct bt_uuid_16 uuid[CONFIG_BT_MAX_CONN] = {BT_UUID_INIT_16(0)};
44 static struct bt_gatt_discover_params discover_params[CONFIG_BT_MAX_CONN];
45 static struct bt_gatt_read_params read_params[CONFIG_BT_MAX_CONN];
46 
tmap_char_read(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)47 uint8_t tmap_char_read(struct bt_conn *conn, uint8_t err,
48 		       struct bt_gatt_read_params *params,
49 		       const void *data, uint16_t length)
50 {
51 	uint16_t peer_role;
52 
53 	/* Check read request result */
54 	if (err != BT_ATT_ERR_SUCCESS) {
55 		if (cb->discovery_complete) {
56 			cb->discovery_complete(0, conn, err);
57 		}
58 
59 		return BT_GATT_ITER_STOP;
60 	}
61 
62 	/* Check data length */
63 	if (length != sizeof(peer_role)) {
64 		if (cb->discovery_complete) {
65 			cb->discovery_complete(0, conn, BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
66 		}
67 
68 		return BT_GATT_ITER_STOP;
69 	}
70 
71 	/* Extract the TMAP role of the peer and inform application of the value found */
72 	peer_role = sys_get_le16(data);
73 
74 	if ((peer_role > 0U) && (peer_role <= TMAP_ALL_ROLES)) {
75 		if (cb->discovery_complete) {
76 			cb->discovery_complete((enum bt_tmap_role)peer_role, conn, 0);
77 		}
78 	} else {
79 		if (cb->discovery_complete) {
80 			cb->discovery_complete(0, conn, BT_ATT_ERR_VALUE_NOT_ALLOWED);
81 		}
82 	}
83 
84 	return BT_GATT_ITER_STOP;
85 }
86 
discover_func(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * params)87 static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
88 			     struct bt_gatt_discover_params *params)
89 {
90 	int err;
91 	uint8_t conn_id = bt_conn_index(conn);
92 
93 	if (!attr) {
94 		(void)memset(params, 0, sizeof(*params));
95 		if (!tmas_found) {
96 			/* TMAS not found on peer */
97 			if (cb->discovery_complete) {
98 				cb->discovery_complete(0, conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND);
99 			}
100 		}
101 
102 		tmas_found = false;
103 
104 		return BT_GATT_ITER_STOP;
105 	}
106 
107 	if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_TMAS)) {
108 		LOG_DBG("Discovered TMAS\n");
109 		tmas_found = true;
110 		memcpy(&uuid[conn_id], BT_UUID_GATT_TMAPR, sizeof(uuid[conn_id]));
111 		discover_params[conn_id].uuid = &uuid[conn_id].uuid;
112 		discover_params[conn_id].start_handle = attr->handle + 1;
113 		discover_params[conn_id].type = BT_GATT_DISCOVER_CHARACTERISTIC;
114 
115 		/* Discovered TMAS - Search for TMAP Role characteristic */
116 		err = bt_gatt_discover(conn, &discover_params[conn_id]);
117 		if (err) {
118 			LOG_DBG("Discover failed (err %d)\n", err);
119 		}
120 	} else if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_GATT_TMAPR)) {
121 		/* Use 0 for now, will expand later */
122 		read_params[conn_id].func = tmap_char_read;
123 		read_params[conn_id].handle_count = 1u;
124 		read_params[conn_id].single.handle = bt_gatt_attr_value_handle(attr);
125 		read_params[conn_id].single.offset = 0;
126 
127 		/* Discovered TMAP Role characteristic - read value */
128 		err = bt_gatt_read(conn, &read_params[0]);
129 		if (err != 0) {
130 			LOG_DBG("Could not read peer TMAP Role");
131 		}
132 	} else {
133 		return BT_GATT_ITER_CONTINUE;
134 	}
135 
136 	return BT_GATT_ITER_STOP;
137 }
138 
read_role(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)139 static ssize_t read_role(struct bt_conn *conn,
140 			 const struct bt_gatt_attr *attr, void *buf,
141 			 uint16_t len, uint16_t offset)
142 {
143 	uint16_t role;
144 
145 	role = sys_cpu_to_le16(tmap_role);
146 	LOG_DBG("TMAP: role 0x%04X", role);
147 
148 	return bt_gatt_attr_read(conn, attr, buf, len, offset,
149 				 &role, sizeof(role));
150 }
151 
152 /* Telephony and Media Audio Service attributes */
153 #define BT_TMAS_SERVICE_DEFINITION \
154 	BT_GATT_PRIMARY_SERVICE(BT_UUID_TMAS), \
155 	BT_GATT_CHARACTERISTIC(BT_UUID_GATT_TMAPR, \
156 			       BT_GATT_CHRC_READ, \
157 			       BT_GATT_PERM_READ, \
158 			       read_role, NULL, NULL)
159 
160 static struct bt_gatt_attr svc_attrs[] = { BT_TMAS_SERVICE_DEFINITION };
161 static struct bt_gatt_service tmas;
162 
valid_tmap_role(enum bt_tmap_role role)163 static bool valid_tmap_role(enum bt_tmap_role role)
164 {
165 	if (role == 0 || (role & TMAP_ALL_ROLES) != role) {
166 		LOG_DBG("Invalid role %d", role);
167 	}
168 
169 	if ((role & BT_TMAP_ROLE_CG) != 0 && !IS_ENABLED(CONFIG_BT_TMAP_CG_SUPPORTED)) {
170 		LOG_DBG("Device does not support the CG role");
171 
172 		return false;
173 	}
174 
175 	if ((role & BT_TMAP_ROLE_CT) != 0 && !IS_ENABLED(CONFIG_BT_TMAP_CT_SUPPORTED)) {
176 		LOG_DBG("Device does not support the CT role");
177 
178 		return false;
179 	}
180 
181 	if ((role & BT_TMAP_ROLE_UMS) != 0 && !IS_ENABLED(CONFIG_BT_TMAP_UMS_SUPPORTED)) {
182 		LOG_DBG("Device does not support the UMS role");
183 
184 		return false;
185 	}
186 
187 	if ((role & BT_TMAP_ROLE_UMR) != 0 && !IS_ENABLED(CONFIG_BT_TMAP_UMR_SUPPORTED)) {
188 		LOG_DBG("Device does not support the UMR role");
189 
190 		return false;
191 	}
192 
193 	if ((role & BT_TMAP_ROLE_BMS) != 0 && !IS_ENABLED(CONFIG_BT_TMAP_BMS_SUPPORTED)) {
194 		LOG_DBG("Device does not support the BMS role");
195 
196 		return false;
197 	}
198 
199 	if ((role & BT_TMAP_ROLE_BMR) != 0 && !IS_ENABLED(CONFIG_BT_TMAP_BMR_SUPPORTED)) {
200 		LOG_DBG("Device does not support the BMR role");
201 
202 		return false;
203 	}
204 
205 	return true;
206 }
207 
bt_tmap_register(enum bt_tmap_role role)208 int bt_tmap_register(enum bt_tmap_role role)
209 {
210 	int err;
211 
212 	CHECKIF(!valid_tmap_role(role)) {
213 		LOG_DBG("Invalid role: %d", role);
214 
215 		return -EINVAL;
216 	}
217 
218 	tmas = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs);
219 
220 	err = bt_gatt_service_register(&tmas);
221 	if (err) {
222 		LOG_DBG("Could not register the TMAS service");
223 		return -ENOEXEC;
224 	}
225 
226 	tmap_role = role;
227 	tmas_found = false;
228 
229 	return 0;
230 }
231 
bt_tmap_discover(struct bt_conn * conn,const struct bt_tmap_cb * tmap_cb)232 int bt_tmap_discover(struct bt_conn *conn, const struct bt_tmap_cb *tmap_cb)
233 {
234 	int err = 0;
235 	uint8_t conn_id = bt_conn_index(conn);
236 
237 	cb = tmap_cb;
238 
239 	memcpy(&uuid[conn_id], BT_UUID_TMAS, sizeof(uuid[conn_id]));
240 	discover_params[conn_id].uuid = &uuid[conn_id].uuid;
241 	discover_params[conn_id].func = discover_func;
242 	discover_params[conn_id].start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
243 	discover_params[conn_id].end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
244 	discover_params[conn_id].type = BT_GATT_DISCOVER_PRIMARY;
245 
246 	err = bt_gatt_discover(conn, &discover_params[conn_id]);
247 
248 	return err;
249 }
250 
bt_tmap_set_role(enum bt_tmap_role role)251 void bt_tmap_set_role(enum bt_tmap_role role)
252 {
253 	tmap_role = role;
254 }
255