1 /*
2 * Copyright (c) 2023 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include <errno.h>
7 #include <stdbool.h>
8 #include <stddef.h>
9 #include <stdint.h>
10 #include <string.h>
11 #include <sys/types.h>
12
13 #include <zephyr/autoconf.h>
14 #include <zephyr/bluetooth/audio/gmap.h>
15 #include <zephyr/bluetooth/conn.h>
16 #include <zephyr/bluetooth/gatt.h>
17 #include <zephyr/bluetooth/uuid.h>
18 #include <zephyr/logging/log.h>
19 #include <zephyr/sys/check.h>
20 #include <zephyr/sys/util.h>
21 #include <zephyr/sys/util_macro.h>
22
23 #include "audio_internal.h"
24
25 LOG_MODULE_REGISTER(bt_gmap_server, CONFIG_BT_GMAP_LOG_LEVEL);
26
27 #define BT_GMAP_ROLE_MASK \
28 (BT_GMAP_ROLE_UGG | BT_GMAP_ROLE_UGT | BT_GMAP_ROLE_BGS | BT_GMAP_ROLE_BGR)
29
30 static uint8_t gmap_role;
31 static struct bt_gmap_feat gmap_features;
32
read_gmap_role(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)33 static ssize_t read_gmap_role(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
34 uint16_t len, uint16_t offset)
35 {
36 LOG_DBG("role 0x%02X", gmap_role);
37
38 return bt_gatt_attr_read(conn, attr, buf, len, offset, &gmap_role, sizeof(gmap_role));
39 }
40
41 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
read_gmap_ugg_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)42 static ssize_t read_gmap_ugg_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
43 uint16_t len, uint16_t offset)
44 {
45 const uint8_t feat = (uint8_t)gmap_features.ugg_feat;
46
47 LOG_DBG("feat 0x%02X", feat);
48
49 return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
50 }
51
52 static const struct bt_gatt_attr ugg_feat_chrc[] = {
53 BT_AUDIO_CHRC(BT_UUID_GMAP_UGG_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
54 read_gmap_ugg_feat, NULL, NULL),
55 };
56 #endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
57
58 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
read_gmap_ugt_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)59 static ssize_t read_gmap_ugt_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
60 uint16_t len, uint16_t offset)
61 {
62 const uint8_t feat = (uint8_t)gmap_features.ugt_feat;
63
64 LOG_DBG("feat 0x%02X", feat);
65
66 return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
67 }
68
69 static const struct bt_gatt_attr ugt_feat_chrc[] = {
70 BT_AUDIO_CHRC(BT_UUID_GMAP_UGT_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
71 read_gmap_ugt_feat, NULL, NULL),
72 };
73
74 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
75
76 #if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
read_gmap_bgs_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)77 static ssize_t read_gmap_bgs_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
78 uint16_t len, uint16_t offset)
79 {
80 const uint8_t feat = (uint8_t)gmap_features.bgs_feat;
81
82 LOG_DBG("feat 0x%02X", feat);
83
84 return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
85 }
86
87 static const struct bt_gatt_attr bgs_feat_chrc[] = {
88 BT_AUDIO_CHRC(BT_UUID_GMAP_BGS_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
89 read_gmap_bgs_feat, NULL, NULL),
90 };
91 #endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
92
93 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
read_gmap_bgr_feat(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)94 static ssize_t read_gmap_bgr_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
95 uint16_t len, uint16_t offset)
96 {
97 const uint8_t feat = (uint8_t)gmap_features.bgr_feat;
98
99 LOG_DBG("feat 0x%02X", feat);
100
101 return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
102 }
103
104 static const struct bt_gatt_attr bgr_feat_chrc[] = {
105 BT_AUDIO_CHRC(BT_UUID_GMAP_BGR_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
106 read_gmap_bgr_feat, NULL, NULL),
107 };
108 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
109
110 /* There are 4 optional characteristics - Use a dummy definition to allocate memory and then add the
111 * characteristics at will when registering or modifying the role(s)
112 */
113 #define GMAS_DUMMY_CHRC BT_AUDIO_CHRC(0, 0, 0, NULL, NULL, NULL)
114
115 /* Gaming Audio Service attributes */
116 static struct bt_gatt_attr svc_attrs[] = {
117 BT_GATT_PRIMARY_SERVICE(BT_UUID_GMAS),
118 BT_AUDIO_CHRC(BT_UUID_GMAP_ROLE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
119 read_gmap_role, NULL, NULL),
120 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
121 GMAS_DUMMY_CHRC,
122 #endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
123 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
124 GMAS_DUMMY_CHRC,
125 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
126 #if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
127 GMAS_DUMMY_CHRC,
128 #endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
129 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
130 GMAS_DUMMY_CHRC,
131 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
132 };
133 static struct bt_gatt_service gmas;
134
valid_gmap_role(enum bt_gmap_role role)135 static bool valid_gmap_role(enum bt_gmap_role role)
136 {
137 if (role == 0 || (role & BT_GMAP_ROLE_MASK) != role) {
138 LOG_DBG("Invalid role %d", role);
139 }
140
141 if ((role & BT_GMAP_ROLE_UGG) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_UGG_SUPPORTED)) {
142 LOG_DBG("Device does not support the UGG role");
143
144 return false;
145 }
146
147 if ((role & BT_GMAP_ROLE_UGT) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_UGT_SUPPORTED)) {
148 LOG_DBG("Device does not support the UGT role");
149
150 return false;
151 }
152
153 if ((role & BT_GMAP_ROLE_BGS) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGS_SUPPORTED)) {
154 LOG_DBG("Device does not support the BGS role");
155
156 return false;
157 }
158
159 if ((role & BT_GMAP_ROLE_BGR) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED)) {
160 LOG_DBG("Device does not support the BGR role");
161
162 return false;
163 }
164
165 return true;
166 }
167
valid_gmap_features(enum bt_gmap_role role,struct bt_gmap_feat features)168 static bool valid_gmap_features(enum bt_gmap_role role, struct bt_gmap_feat features)
169 {
170 /* Guard with BT_GMAP_UGG_SUPPORTED as the Kconfigs may not be available without it*/
171 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
172 if ((role & BT_GMAP_ROLE_UGG) != 0) {
173 enum bt_gmap_ugg_feat ugg_feat = features.ugg_feat;
174
175 if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTIPLEX) != 0 &&
176 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
177 LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTIPLEX with "
178 "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0");
179
180 return false;
181 }
182
183 if ((ugg_feat & BT_GMAP_UGG_FEAT_96KBPS_SOURCE) != 0 &&
184 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
185 LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_96KBPS_SOURCE with "
186 "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0");
187
188 return false;
189 }
190
191 if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTISINK) != 0 &&
192 (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT < 2 ||
193 CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2)) {
194 LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTISINK with "
195 "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT (%d) or "
196 "CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d) < 2",
197 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT,
198 CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT);
199
200 return false;
201 }
202 }
203 #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
204
205 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
206 if ((role & BT_GMAP_ROLE_UGT) != 0) {
207 enum bt_gmap_ugt_feat ugt_feat = features.ugt_feat;
208 enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat;
209
210 if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 &&
211 (ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0) {
212 LOG_DBG("Device shall support either BT_GMAP_UGT_FEAT_SOURCE or "
213 "BT_GMAP_UGT_FEAT_SINK");
214
215 return false;
216 }
217
218 if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 &&
219 ((ugt_feat & BT_GMAP_UGT_FEAT_80KBPS_SOURCE) != 0 ||
220 (ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0)) {
221 LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SOURCE if "
222 "BT_GMAP_UGT_FEAT_80KBPS_SOURCE or BT_GMAP_UGT_FEAT_MULTISOURCE is "
223 "supported");
224
225 return false;
226 }
227
228 if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) != 0 &&
229 CONFIG_BT_ASCS_ASE_SRC_COUNT == 0) {
230 LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SOURCE with "
231 "CONFIG_BT_ASCS_ASE_SRC_COUNT == 0");
232
233 return false;
234 }
235
236 if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0 &&
237 (CONFIG_BT_ASCS_ASE_SRC_COUNT < 2 || CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) {
238 LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISOURCE with "
239 "CONFIG_BT_ASCS_ASE_SRC_COUNT (%d) or "
240 "CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2",
241 CONFIG_BT_ASCS_ASE_SRC_COUNT, CONFIG_BT_ASCS_MAX_ACTIVE_ASES);
242
243 return false;
244 }
245
246 if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) != 0 && CONFIG_BT_ASCS_ASE_SNK_COUNT == 0) {
247 LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SINK with "
248 "CONFIG_BT_ASCS_ASE_SNK_COUNT == 0");
249
250 return false;
251 }
252
253 if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0 &&
254 ((ugt_feat & BT_GMAP_UGT_FEAT_64KBPS_SINK) != 0 ||
255 (ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 ||
256 (ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) != 0)) {
257 LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SINK if "
258 "BT_GMAP_UGT_FEAT_64KBPS_SINK, BT_GMAP_UGT_FEAT_MULTISINK or "
259 "BT_GMAP_UGT_FEAT_MULTIPLEX is supported");
260
261 return false;
262 }
263
264 if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 &&
265 (CONFIG_BT_ASCS_ASE_SNK_COUNT < 2 || CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) {
266 LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISINK with "
267 "CONFIG_BT_ASCS_ASE_SNK_COUNT (%d) or "
268 "CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2",
269 CONFIG_BT_ASCS_ASE_SNK_COUNT, CONFIG_BT_ASCS_MAX_ACTIVE_ASES);
270
271 return false;
272 }
273
274 /* If the device supports both the UGT and BGT roles, then it needs have the same
275 * support for multiplexing for both roles
276 */
277 if ((role & BT_GMAP_ROLE_BGR) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED) &&
278 (ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) !=
279 (bgr_feat & BT_GMAP_BGR_FEAT_MULTIPLEX)) {
280 LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_MULTIPLEX if "
281 "BT_GMAP_BGR_FEAT_MULTIPLEX is supported, and vice versa");
282 }
283 }
284 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
285
286 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
287 if ((role & BT_GMAP_ROLE_BGR) != 0) {
288 enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat;
289
290 if ((bgr_feat & BT_GMAP_BGR_FEAT_MULTISINK) != 0 &&
291 CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT < 2) {
292 LOG_DBG("Cannot support BT_GMAP_BGR_FEAT_MULTISINK with "
293 "CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT (%d) < 2",
294 CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT);
295
296 return false;
297 }
298 }
299 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
300
301 /* If the roles are not supported, then the feature characteristics are not instantiated and
302 * the feature values do not need to be checked, as they will never be read (thus ignore by
303 * the stack)
304 */
305
306 return true;
307 }
308
update_service(enum bt_gmap_role role)309 static void update_service(enum bt_gmap_role role)
310 {
311 gmas.attrs = svc_attrs;
312 gmas.attr_count = 3; /* service + 2 attributes for BT_UUID_GMAP_ROLE */
313
314 /* Add characteristics based on the role selected and what is supported */
315 #if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
316 if (role & BT_GMAP_ROLE_UGG) {
317 memcpy(&gmas.attrs[gmas.attr_count], ugg_feat_chrc, sizeof(ugg_feat_chrc));
318 gmas.attr_count += ARRAY_SIZE(ugg_feat_chrc);
319 }
320 #endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
321
322 #if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
323 if (role & BT_GMAP_ROLE_UGT) {
324 memcpy(&gmas.attrs[gmas.attr_count], ugt_feat_chrc, sizeof(ugt_feat_chrc));
325 gmas.attr_count += ARRAY_SIZE(ugt_feat_chrc);
326 }
327 #endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
328
329 #if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
330 if (role & BT_GMAP_ROLE_BGS) {
331 memcpy(&gmas.attrs[gmas.attr_count], bgs_feat_chrc, sizeof(bgs_feat_chrc));
332 gmas.attr_count += ARRAY_SIZE(bgs_feat_chrc);
333 }
334 #endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
335
336 #if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
337 if (role & BT_GMAP_ROLE_BGR) {
338 memcpy(&gmas.attrs[gmas.attr_count], bgr_feat_chrc, sizeof(bgr_feat_chrc));
339 gmas.attr_count += ARRAY_SIZE(bgr_feat_chrc);
340 }
341 #endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
342 }
343
bt_gmap_register(enum bt_gmap_role role,struct bt_gmap_feat features)344 int bt_gmap_register(enum bt_gmap_role role, struct bt_gmap_feat features)
345 {
346 int err;
347
348 CHECKIF(!valid_gmap_role(role)) {
349 LOG_DBG("Invalid role: %d", role);
350
351 return -EINVAL;
352 }
353
354 CHECKIF(!valid_gmap_features(role, features)) {
355 LOG_DBG("Invalid features");
356
357 return -EINVAL;
358 }
359
360 update_service(role);
361
362 err = bt_gatt_service_register(&gmas);
363 if (err) {
364 LOG_DBG("Could not register the GMAS service");
365
366 return -ENOEXEC;
367 }
368
369 gmap_role = role;
370 gmap_features = features;
371
372 return 0;
373 }
374
bt_gmap_set_role(enum bt_gmap_role role,struct bt_gmap_feat features)375 int bt_gmap_set_role(enum bt_gmap_role role, struct bt_gmap_feat features)
376 {
377 int err;
378
379 if (gmap_role == 0) { /* not registered if this is 0 */
380 LOG_DBG("GMAP not registered");
381
382 return -ENOEXEC;
383 }
384
385 CHECKIF(!valid_gmap_role(role)) {
386 LOG_DBG("Invalid role: %d", role);
387
388 return -EINVAL;
389 }
390
391 CHECKIF(!valid_gmap_features(role, features)) {
392 LOG_DBG("Invalid features");
393
394 return -EINVAL;
395 }
396
397 if (gmap_role == role) {
398 LOG_DBG("No role change");
399
400 if (memcmp(&gmap_features, &features, sizeof(gmap_features)) == 0) {
401 LOG_DBG("No feature change");
402
403 return -EALREADY;
404 }
405
406 gmap_features = features;
407
408 return 0;
409 }
410
411 /* Re-register the service to trigger a db_changed() if the roles changed */
412 err = bt_gatt_service_unregister(&gmas);
413 if (err != 0) {
414 LOG_DBG("Failed to unregister service: %d", err);
415
416 return -ENOENT;
417 }
418
419 err = bt_gmap_register(role, gmap_features);
420 if (err != 0) {
421 LOG_DBG("Failed to update GMAS: %d", err);
422
423 return -ECANCELED;
424 }
425
426 return 0;
427 }
428