Coverage Report

Created: 2026-04-09 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/adhd/cras/src/server/cras_lea_manager.c
Line
Count
Source
1
/* Copyright 2024 The ChromiumOS Authors
2
 * Use of this source code is governed by a BSD-style license that can be
3
 * found in the LICENSE file.
4
 */
5
6
#ifndef _GNU_SOURCE
7
#define _GNU_SOURCE  // for ppoll
8
#endif
9
10
#include "cras/src/server/cras_lea_manager.h"
11
12
#include <errno.h>
13
#include <poll.h>
14
#include <stdio.h>
15
#include <stdlib.h>
16
#include <sys/socket.h>
17
#include <sys/time.h>
18
#include <sys/un.h>
19
#include <syslog.h>
20
21
#include "cras/server/main_message.h"
22
#include "cras/src/server/cras_bt_log.h"
23
#include "cras/src/server/cras_fl_media.h"
24
#include "cras/src/server/cras_iodev_list.h"
25
#include "cras/src/server/cras_lea_iodev.h"
26
#include "cras/src/server/cras_system_state.h"
27
#include "cras/src/server/cras_tm.h"
28
#include "cras_config.h"
29
#include "cras_util.h"
30
#include "third_party/utlist/utlist.h"
31
32
#define FLOSS_LEA_DATA_PATH "/run/bluetooth/audio/.lea_data"
33
34
/* Object (list) holding information about LE-Audio groups. */
35
struct lea_group {
36
  char* name;
37
  int group_id;
38
  uint8_t direction;           /* Bitmask of |LEA_AUDIO_DIRECTION| */
39
  uint16_t available_contexts; /* Bitmask of |LEA_AUDIO_CONTEXT_TYPE| */
40
  uint32_t data_interval_us;
41
  uint32_t sample_rate;
42
  uint8_t bits_per_sample;
43
  uint8_t channels_count;
44
45
  struct cras_iodev *idev, *odev;
46
  struct lea_group *prev, *next;
47
};
48
49
/* Object holding information and resources of LEA. */
50
struct cras_lea {
51
  // Object representing the media interface of BT adapter.
52
  struct fl_media* fm;
53
  // A list of connected LE-Audio groups.
54
  // The first group in the list is the primary group.
55
  struct lea_group* connected_groups;
56
  // The file descriptor for LEA socket.
57
  int fd;
58
  // If the input has started. This is used to determine if
59
  // a lea start or stop is required.
60
  int idev_started;
61
  // If the output has started. This is used to determine if
62
  // a lea start or stop is required.
63
  int odev_started;
64
  // The current context type.
65
  enum LEA_AUDIO_CONTEXT_TYPE current_context;
66
  // The target context type.
67
  // Ignore if set to |LEA_AUDIO_CONTEXT_UNINITIALIZED|.
68
  enum LEA_AUDIO_CONTEXT_TYPE target_context;
69
  // Whether there is a pending context switch event.
70
  bool is_context_switching;
71
};
72
73
static struct cras_lea* g_lea;
74
75
0
bool cras_floss_lea_is_active(struct cras_lea* lea) {
76
0
  return lea && lea == g_lea;
77
0
}
78
79
0
bool cras_floss_lea_is_context_switching(struct cras_lea* lea) {
80
0
  return lea->is_context_switching;
81
0
}
82
83
void cras_floss_lea_set_is_context_switching(struct cras_lea* lea,
84
0
                                             bool is_context_switching) {
85
0
  lea->is_context_switching = is_context_switching;
86
0
}
87
88
0
struct cras_iodev* cras_floss_lea_get_primary_odev(struct cras_lea* lea) {
89
0
  struct lea_group* group = lea->connected_groups;
90
0
  return group ? group->odev : NULL;
91
0
}
92
93
0
struct cras_iodev* cras_floss_lea_get_primary_idev(struct cras_lea* lea) {
94
0
  struct lea_group* group = lea->connected_groups;
95
0
  return group ? group->idev : NULL;
96
0
}
97
98
0
bool cras_floss_lea_is_odev_started(struct cras_lea* lea) {
99
0
  return lea->odev_started;
100
0
}
101
102
0
bool cras_floss_lea_is_idev_started(struct cras_lea* lea) {
103
0
  return lea->idev_started;
104
0
}
105
106
0
void fill_floss_lea_skt_addr(struct sockaddr_un* addr) {
107
0
  memset(addr, 0, sizeof(*addr));
108
0
  addr->sun_family = AF_UNIX;
109
0
  snprintf(addr->sun_path, CRAS_MAX_SOCKET_PATH_SIZE, FLOSS_LEA_DATA_PATH);
110
0
}
111
112
static void set_dev_started(struct cras_lea* lea,
113
                            enum CRAS_STREAM_DIRECTION dir,
114
0
                            int started) {
115
0
  if (dir == CRAS_STREAM_INPUT) {
116
0
    lea->idev_started = started;
117
0
  } else if (dir == CRAS_STREAM_OUTPUT) {
118
0
    lea->odev_started = started;
119
0
  }
120
0
}
121
122
/* Creates a |cras_lea| object representing the LEA service. */
123
0
struct cras_lea* cras_floss_lea_create(struct fl_media* fm) {
124
0
  if (g_lea) {
125
0
    syslog(LOG_WARNING, "%s: memory leak in g_lea", __func__);
126
0
  }
127
128
0
  g_lea = (struct cras_lea*)calloc(1, sizeof(*g_lea));
129
130
0
  if (!g_lea) {
131
0
    return NULL;
132
0
  }
133
134
0
  g_lea->fm = fm;
135
0
  g_lea->fd = -1;
136
137
0
  return g_lea;
138
0
}
139
140
int cras_floss_lea_start(struct cras_lea* lea,
141
                         thread_callback cb,
142
0
                         enum CRAS_STREAM_DIRECTION dir) {
143
0
  int skt_fd;
144
0
  int rc;
145
0
  struct sockaddr_un addr;
146
0
  struct timespec timeout = {10, 0};
147
0
  struct pollfd poll_fd;
148
0
  struct lea_group* group = lea->connected_groups;
149
150
0
  if ((dir == CRAS_STREAM_INPUT && lea->idev_started) ||
151
0
      (dir == CRAS_STREAM_OUTPUT && lea->odev_started)) {
152
0
    return -EINVAL;
153
0
  }
154
155
0
  if (!group) {
156
0
    return -EINVAL;
157
0
  }
158
159
0
  if (dir == CRAS_STREAM_INPUT) {
160
0
    rc = floss_media_lea_peer_start_audio_request(
161
0
        lea->fm, &group->data_interval_us, &group->sample_rate,
162
0
        &group->bits_per_sample, &group->channels_count);
163
0
  } else if (dir == CRAS_STREAM_OUTPUT) {
164
0
    rc = floss_media_lea_host_start_audio_request(
165
0
        lea->fm, &group->data_interval_us, &group->sample_rate,
166
0
        &group->bits_per_sample, &group->channels_count);
167
0
  } else {
168
0
    syslog(LOG_ERR, "%s: unsupported direction %d", __func__, dir);
169
0
    return -EINVAL;
170
0
  }
171
172
0
  if (rc < 0) {
173
0
    return rc;
174
0
  }
175
176
  /* Check if the socket connection has been started by another
177
   * direction's iodev. We can skip the data channel setup if so. */
178
0
  if (lea->idev_started || lea->odev_started) {
179
0
    goto start_dev;
180
0
  }
181
182
0
  skt_fd = socket(PF_UNIX, SOCK_STREAM | O_NONBLOCK, 0);
183
0
  if (skt_fd < 0) {
184
0
    syslog(LOG_WARNING, "Create LEA socket failed with error %d", errno);
185
0
    rc = skt_fd;
186
0
    goto error;
187
0
  }
188
189
0
  fill_floss_lea_skt_addr(&addr);
190
191
0
  syslog(LOG_DEBUG, "Connect to LEA socket at %s ", addr.sun_path);
192
0
  rc = connect(skt_fd, (struct sockaddr*)&addr, sizeof(addr));
193
0
  if (rc < 0) {
194
0
    syslog(LOG_WARNING, "Connect to LEA socket failed with error %d", errno);
195
0
    goto error;
196
0
  }
197
198
0
  poll_fd.fd = skt_fd;
199
0
  poll_fd.events = POLLIN | POLLOUT;
200
201
0
  rc = ppoll(&poll_fd, 1, &timeout, NULL);
202
0
  if (rc <= 0) {
203
0
    syslog(LOG_WARNING, "Poll for LEA socket failed with error %d", errno);
204
0
    goto error;
205
0
  }
206
207
0
  if (poll_fd.revents & (POLLERR | POLLHUP)) {
208
0
    syslog(LOG_WARNING, "LEA socket error, revents: %u.", poll_fd.revents);
209
0
    rc = -1;
210
0
    goto error;
211
0
  }
212
213
0
  lea->fd = skt_fd;
214
215
0
  audio_thread_add_events_callback(lea->fd, cb, lea,
216
0
                                   POLLOUT | POLLIN | POLLERR | POLLHUP);
217
218
0
start_dev:
219
0
  set_dev_started(lea, dir, 1);
220
221
0
  return 0;
222
0
error:
223
0
  if (dir == CRAS_STREAM_INPUT) {
224
0
    floss_media_lea_peer_stop_audio_request(lea->fm);
225
0
  } else if (dir == CRAS_STREAM_OUTPUT) {
226
0
    floss_media_lea_host_stop_audio_request(lea->fm);
227
0
  }
228
229
0
  if (skt_fd >= 0) {
230
0
    close(skt_fd);
231
0
    unlink(addr.sun_path);
232
0
  }
233
0
  return rc;
234
0
}
235
236
0
int cras_floss_lea_stop(struct cras_lea* lea, enum CRAS_STREAM_DIRECTION dir) {
237
0
  int rc;
238
239
  // i/odev_started is only used to determine LEA status.
240
0
  if (!(lea->idev_started || lea->odev_started)) {
241
0
    return 0;
242
0
  }
243
244
0
  set_dev_started(lea, dir, 0);
245
246
0
  if (dir == CRAS_STREAM_INPUT) {
247
0
    rc = floss_media_lea_peer_stop_audio_request(lea->fm);
248
0
    if (rc < 0) {
249
0
      syslog(LOG_ERR, "%s: Failed to stop peer audio request", __func__);
250
0
      return rc;
251
0
    }
252
0
  } else if (dir == CRAS_STREAM_OUTPUT) {
253
0
    rc = floss_media_lea_host_stop_audio_request(lea->fm);
254
0
    if (rc < 0) {
255
0
      syslog(LOG_ERR, "%s: Failed to stop host audio request", __func__);
256
0
      return rc;
257
0
    }
258
0
  }
259
260
0
  if (lea->idev_started || lea->odev_started) {
261
0
    return 0;
262
0
  }
263
264
0
  if (lea->fd >= 0) {
265
0
    audio_thread_rm_callback_sync(cras_iodev_list_get_audio_thread(), lea->fd);
266
0
    close(lea->fd);
267
0
  }
268
0
  lea->fd = -1;
269
270
0
  return 0;
271
0
}
272
273
int cras_floss_lea_fill_format(struct cras_lea* lea,
274
                               size_t** rates,
275
                               snd_pcm_format_t** formats,
276
0
                               size_t** channel_counts) {
277
0
  struct lea_group* group = lea->connected_groups;
278
279
0
  if (group == NULL) {
280
0
    return 0;
281
0
  }
282
283
0
  *rates = (size_t*)malloc(2 * sizeof(size_t));
284
0
  if (!*rates) {
285
0
    return -ENOMEM;
286
0
  }
287
0
  (*rates)[0] = group->sample_rate;
288
0
  (*rates)[1] = 0;
289
290
0
  *formats = (snd_pcm_format_t*)malloc(2 * sizeof(snd_pcm_format_t));
291
0
  if (!*formats) {
292
0
    return -ENOMEM;
293
0
  }
294
0
  switch (group->bits_per_sample) {
295
0
    case 16:
296
0
      (*formats)[0] = SND_PCM_FORMAT_S16_LE;
297
0
      break;
298
0
    case 24:
299
0
      (*formats)[0] = SND_PCM_FORMAT_S24_3LE;
300
0
      break;
301
0
    case 32:
302
0
      (*formats)[0] = SND_PCM_FORMAT_S32_LE;
303
0
      break;
304
0
    default:
305
0
      syslog(LOG_ERR, "%s: Unknown bits_per_sample %d", __func__,
306
0
             group->bits_per_sample);
307
0
      return -EINVAL;
308
0
  }
309
0
  (*formats)[1] = (snd_pcm_format_t)0;
310
311
0
  *channel_counts = (size_t*)malloc(2 * sizeof(size_t));
312
0
  if (!*channel_counts) {
313
0
    return -ENOMEM;
314
0
  }
315
0
  (*channel_counts)[0] = group->channels_count;
316
0
  (*channel_counts)[1] = 0;
317
0
  return 0;
318
0
}
319
320
0
void cras_floss_lea_set_volume(struct cras_lea* lea, unsigned int volume) {
321
0
  syslog(LOG_DEBUG, "%s: set_volume(%u)", __func__, volume);
322
0
  struct lea_group* group = lea->connected_groups;
323
0
  floss_media_lea_set_group_volume(lea->fm, group->group_id,
324
0
                                   volume * 255 / 100);
325
0
  BTLOG(btlog, BT_LEA_SET_GROUP_VOLUME, group->group_id, volume);
326
0
}
327
328
0
bool cras_floss_lea_has_connected_group(struct cras_lea* lea) {
329
0
  return lea->connected_groups != NULL;
330
0
}
331
332
0
void cras_floss_lea_destroy(struct cras_lea* lea) {
333
0
  if (lea != g_lea) {
334
0
    syslog(LOG_WARNING, "%s: unrecognized lea object", __func__);
335
0
    return;
336
0
  }
337
338
0
  if (cras_floss_lea_has_connected_group(lea)) {
339
0
    syslog(LOG_WARNING, "%s: there are still connected groups", __func__);
340
0
    return;
341
0
  }
342
343
0
  if (lea->fd >= 0) {
344
0
    close(lea->fd);
345
0
  }
346
347
0
  free(lea);
348
349
0
  g_lea = NULL;
350
0
}
351
352
void cras_floss_lea_set_active(struct cras_lea* lea,
353
                               int group_id,
354
0
                               unsigned enabled) {
355
  // Action is needed (and meaningful) only when there is no stream.
356
0
  if (lea->idev_started || lea->odev_started) {
357
0
    return;
358
0
  }
359
360
0
  if (!enabled) {
361
0
    group_id = FL_LEA_GROUP_NONE;
362
0
    lea->current_context = LEA_AUDIO_CONTEXT_UNINITIALIZED;
363
0
  }
364
365
0
  floss_media_lea_set_active_group(lea->fm, group_id);
366
0
}
367
368
void cras_floss_lea_set_target_context(struct cras_lea* lea,
369
0
                                       enum LEA_AUDIO_CONTEXT_TYPE context) {
370
0
  syslog(LOG_INFO, "%s: set target context to %d", __func__, context);
371
0
  lea->target_context = context;
372
0
}
373
374
0
void cras_floss_lea_apply_target_context(struct cras_lea* lea) {
375
0
  if (lea->target_context == LEA_AUDIO_CONTEXT_UNINITIALIZED) {
376
0
    syslog(LOG_DEBUG, "%s: target context is none", __func__);
377
0
    return;
378
0
  }
379
380
0
  syslog(LOG_INFO, "%s: update context from %d to %d", __func__,
381
0
         lea->current_context, lea->target_context);
382
383
0
  switch (lea->target_context) {
384
0
    case LEA_AUDIO_CONTEXT_MEDIA:
385
0
      floss_media_lea_source_metadata_changed(lea->fm, FL_LEA_AUDIO_USAGE_MEDIA,
386
0
                                              FL_LEA_AUDIO_CONTENT_TYPE_MUSIC,
387
0
                                              1.0);
388
0
      break;
389
0
    case LEA_AUDIO_CONTEXT_CONVERSATIONAL:
390
0
      floss_media_lea_source_metadata_changed(
391
0
          lea->fm, FL_LEA_AUDIO_USAGE_VOICE_COMMUNICATION,
392
0
          FL_LEA_AUDIO_CONTENT_TYPE_MUSIC, 0.0);
393
0
      floss_media_lea_sink_metadata_changed(
394
0
          lea->fm, FL_LEA_AUDIO_SOURCE_VOICE_COMMUNICATION, 1.0);
395
0
      break;
396
0
    default:
397
0
      break;
398
0
  }
399
400
0
  lea->current_context = lea->target_context;
401
0
  lea->target_context = LEA_AUDIO_CONTEXT_UNINITIALIZED;
402
0
}
403
404
0
int cras_floss_lea_get_fd(struct cras_lea* lea) {
405
0
  return lea->fd;
406
0
}
407
408
// TODO: check I/O availability instead of adding both
409
void cras_floss_lea_add_group(struct cras_lea* lea,
410
                              const char* name,
411
0
                              int group_id) {
412
0
  struct lea_group* group;
413
0
  DL_FOREACH (lea->connected_groups, group) {
414
0
    if (group_id == group->group_id) {
415
0
      syslog(LOG_WARNING, "%s: Skipping added group %s", __func__, name);
416
0
      return;
417
0
    }
418
0
  }
419
420
0
  group = (struct lea_group*)calloc(1, sizeof(struct lea_group));
421
0
  group->name = strdup(name);
422
0
  group->group_id = group_id;
423
424
0
  DL_APPEND(lea->connected_groups, group);
425
426
0
  group->idev = lea_iodev_create(lea, name, group_id, CRAS_STREAM_INPUT);
427
0
  group->odev = lea_iodev_create(lea, name, group_id, CRAS_STREAM_OUTPUT);
428
429
  // Set plugged and UI will see these iodevs.
430
0
  cras_iodev_set_node_plugged(group->idev->active_node, 1);
431
0
  cras_iodev_set_node_plugged(group->odev->active_node, 1);
432
433
0
  cras_iodev_list_notify_nodes_changed();
434
0
}
435
436
0
void cras_floss_lea_remove_group(struct cras_lea* lea, int group_id) {
437
0
  struct lea_group* group;
438
0
  DL_FOREACH (lea->connected_groups, group) {
439
0
    if (group_id == group->group_id) {
440
0
      if (group->idev) {
441
0
        cras_iodev_set_node_plugged(group->idev->active_node, 0);
442
0
        lea_iodev_destroy(group->idev);
443
0
      }
444
445
0
      if (group->odev) {
446
0
        cras_iodev_set_node_plugged(group->odev->active_node, 0);
447
0
        lea_iodev_destroy(group->odev);
448
0
      }
449
450
0
      DL_DELETE(lea->connected_groups, group);
451
0
      free(group->name);
452
0
      free(group);
453
0
    }
454
0
  }
455
0
}
456
457
int cras_floss_lea_audio_conf_updated(struct cras_lea* lea,
458
                                      uint8_t direction,
459
                                      int group_id,
460
                                      uint32_t snk_audio_location,
461
                                      uint32_t src_audio_location,
462
0
                                      uint16_t available_contexts) {
463
0
  struct lea_group* group = lea->connected_groups;
464
465
0
  if (!group || group->group_id != group_id) {
466
0
    syslog(LOG_WARNING, "Cannot find lea_group %d to update audio conf",
467
0
           group_id);
468
0
    return -ENOENT;
469
0
  }
470
471
0
  group->available_contexts = available_contexts;
472
473
0
  if ((group->direction & LEA_AUDIO_DIRECTION_OUTPUT) !=
474
0
      (direction & LEA_AUDIO_DIRECTION_OUTPUT)) {
475
0
    if (direction & LEA_AUDIO_DIRECTION_OUTPUT) {
476
0
      group->odev->active_node->plugged = 1;
477
0
      gettimeofday(&group->odev->active_node->plugged_time, NULL);
478
0
    } else {
479
0
      cras_iodev_list_disable_and_close_dev_group(group->odev);
480
0
    }
481
0
  }
482
483
0
  if ((group->direction & LEA_AUDIO_DIRECTION_INPUT) !=
484
0
      (direction & LEA_AUDIO_DIRECTION_INPUT)) {
485
0
    if (direction & LEA_AUDIO_DIRECTION_INPUT) {
486
0
      group->idev->active_node->plugged = 1;
487
0
      gettimeofday(&group->idev->active_node->plugged_time, NULL);
488
0
    } else {
489
0
      cras_iodev_list_disable_and_close_dev_group(group->idev);
490
0
    }
491
0
  }
492
493
0
  group->direction = direction;
494
495
0
  cras_iodev_list_notify_nodes_changed();
496
497
0
  return 0;
498
0
}
499
500
int cras_floss_lea_set_support_absolute_volume(struct cras_lea* lea,
501
                                               int group_id,
502
0
                                               bool support_absolute_volume) {
503
0
  struct lea_group* group = lea->connected_groups;
504
505
0
  if (!group || group->group_id != group_id) {
506
0
    syslog(LOG_WARNING, "Cannot find lea_group %d to set abs vol", group_id);
507
0
    return -ENOENT;
508
0
  }
509
510
0
  if (!group->odev) {
511
0
    syslog(LOG_WARNING, "No output device to set absolute volume");
512
0
    return -ENOENT;
513
0
  }
514
515
0
  group->odev->software_volume_needed = !support_absolute_volume;
516
517
0
  return 0;
518
0
}
519
520
bool cras_floss_lea_get_support_absolute_volume(struct cras_lea* lea,
521
0
                                                int group_id) {
522
0
  struct lea_group* group = lea->connected_groups;
523
524
0
  if (!group || group->group_id != group_id || !group->odev) {
525
0
    return false;
526
0
  }
527
528
0
  return !group->odev->software_volume_needed;
529
0
}
530
531
int cras_floss_lea_update_group_volume(struct cras_lea* lea,
532
                                       int group_id,
533
0
                                       uint8_t volume) {
534
0
  struct lea_group* group = lea->connected_groups;
535
536
0
  if (!group || group->group_id != group_id) {
537
0
    syslog(LOG_WARNING, "Cannot find lea_group %d to update volume", group_id);
538
0
    return -ENOENT;
539
0
  }
540
541
0
  if (!group->odev || !group->odev->active_node) {
542
0
    syslog(LOG_WARNING, "No active output device to update volume");
543
0
    return -ENOENT;
544
0
  }
545
546
0
  group->odev->active_node->volume = volume * 100 / 255;
547
0
  cras_iodev_list_notify_node_volume(group->odev->active_node);
548
549
0
  return 0;
550
0
}