blob: 366afb5fc7e112be749bbace3733486e9f5341e1 [file] [log] [blame]
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <syslog.h>
#include "cras_alsa_card.h"
#include "cras_alert.h"
#include "cras_board_config.h"
#include "cras_config.h"
#include "cras_device_blocklist.h"
#include "cras_iodev_list.h"
#include "cras_observer.h"
#include "cras_shm.h"
#include "cras_system_state.h"
#include "cras_tm.h"
#include "cras_types.h"
#include "cras_util.h"
#include "utlist.h"
struct card_list {
struct cras_alsa_card *card;
struct card_list *prev, *next;
};
struct name_list {
char name[NAME_MAX];
struct name_list *prev, *next;
};
/* The system state.
* Members:
* exp_state - The exported system state shared with clients.
* shm_name - Name of posix shm region for exported state.
* shm_fd - fd for shm area of system_state struct.
* shm_fd_ro - fd for shm area of system_state struct, opened read-only.
* This copy is to dup and pass to clients.
* shm_size - Size of the shm area.
* device_config_dir - Directory of device configs where volume curves live.
* internal_ucm_suffix - The suffix to append to internal card name to
* control which ucm config file to load.
* device_blocklist - Blocklist of device the server will ignore.
* cards - A list of active sound cards in the system.
* update_lock - Protects the update_count, as audio threads can update the
* stream count.
* tm - The system-wide timer manager.
* add_task - Function to handle adding a task for main thread to execute.
* task_data - Data to be passed to add_task handler function.
* main_thread_tid - The thread id of the main thread.
* bt_fix_a2dp_packet_size - The flag to override A2DP packet size set by
* Blueetoh peer devices to a smaller default value.
*/
static struct {
struct cras_server_state *exp_state;
char shm_name[NAME_MAX];
int shm_fd;
int shm_fd_ro;
size_t shm_size;
const char *device_config_dir;
const char *internal_ucm_suffix;
struct name_list *ignore_suffix_cards;
struct cras_device_blocklist *device_blocklist;
struct card_list *cards;
pthread_mutex_t update_lock;
struct cras_tm *tm;
/* Select loop callback registration. */
int (*fd_add)(int fd, void (*cb)(void *data, int events), void *cb_data,
int events, void *select_data);
void (*fd_rm)(int fd, void *select_data);
void *select_data;
int (*add_task)(void (*callback)(void *data), void *callback_data,
void *task_data);
void *task_data;
struct cras_audio_thread_snapshot_buffer snapshot_buffer;
pthread_t main_thread_tid;
bool bt_fix_a2dp_packet_size;
} state;
/* The string format is CARD1,CARD2,CARD3. Divide it into a list. */
void init_ignore_suffix_cards(char *str)
{
struct name_list *card;
char *ptr;
state.ignore_suffix_cards = NULL;
if (str == NULL)
return;
ptr = strtok(str, ",");
while (ptr != NULL) {
card = (struct name_list *)calloc(1, sizeof(*card));
if (!card) {
syslog(LOG_ERR, "Failed to call calloc: %d", errno);
return;
}
strncpy(card->name, ptr, NAME_MAX - 1);
DL_APPEND(state.ignore_suffix_cards, card);
ptr = strtok(NULL, ",");
}
}
void deinit_ignore_suffix_cards()
{
struct name_list *card;
DL_FOREACH (state.ignore_suffix_cards, card) {
DL_DELETE(state.ignore_suffix_cards, card);
free(card);
}
}
/*
* Exported Interface.
*/
void cras_system_state_init(const char *device_config_dir, const char *shm_name,
int rw_shm_fd, int ro_shm_fd,
struct cras_server_state *exp_state,
size_t exp_state_size)
{
struct cras_board_config board_config;
int rc;
assert(sizeof(*exp_state) == exp_state_size);
state.shm_size = sizeof(*exp_state);
strncpy(state.shm_name, shm_name, sizeof(state.shm_name));
state.shm_name[sizeof(state.shm_name) - 1] = '\0';
state.shm_fd = rw_shm_fd;
state.shm_fd_ro = ro_shm_fd;
/* Read board config. */
memset(&board_config, 0, sizeof(board_config));
cras_board_config_get(device_config_dir, &board_config);
/* Initial system state. */
exp_state->state_version = CRAS_SERVER_STATE_VERSION;
exp_state->volume = CRAS_MAX_SYSTEM_VOLUME;
exp_state->mute = 0;
exp_state->mute_locked = 0;
exp_state->suspended = 0;
exp_state->capture_mute = 0;
exp_state->capture_mute_locked = 0;
exp_state->min_volume_dBFS = DEFAULT_MIN_VOLUME_DBFS;
exp_state->max_volume_dBFS = DEFAULT_MAX_VOLUME_DBFS;
exp_state->num_streams_attached = 0;
exp_state->default_output_buffer_size =
board_config.default_output_buffer_size;
exp_state->aec_supported = board_config.aec_supported;
exp_state->aec_group_id = board_config.aec_group_id;
exp_state->bt_wbs_enabled = board_config.bt_wbs_enabled;
exp_state->deprioritize_bt_wbs_mic =
board_config.deprioritize_bt_wbs_mic;
exp_state->noise_cancellation_enabled = 0;
exp_state->hotword_pause_at_suspend =
board_config.hotword_pause_at_suspend;
if ((rc = pthread_mutex_init(&state.update_lock, 0) != 0)) {
syslog(LOG_ERR, "Fatal: system state mutex init");
exit(rc);
}
state.exp_state = exp_state;
/* Directory for volume curve configs.
* Note that device_config_dir does not affect device blocklist.
* Device blocklist is common to all boards so we do not need
* to change device blocklist at run time. */
state.device_config_dir = device_config_dir;
state.internal_ucm_suffix = NULL;
init_ignore_suffix_cards(board_config.ucm_ignore_suffix);
free(board_config.ucm_ignore_suffix);
state.tm = cras_tm_init();
if (!state.tm) {
syslog(LOG_ERR, "Fatal: system state timer init");
exit(-ENOMEM);
}
/* Read config file for blocklisted devices. */
state.device_blocklist =
cras_device_blocklist_create(CRAS_CONFIG_FILE_DIR);
/* Initialize snapshot buffer memory */
memset(&state.snapshot_buffer, 0,
sizeof(struct cras_audio_thread_snapshot_buffer));
/* Save thread id of the main thread. */
state.main_thread_tid = pthread_self();
state.bt_fix_a2dp_packet_size = false;
}
void cras_system_state_set_internal_ucm_suffix(const char *internal_ucm_suffix)
{
state.internal_ucm_suffix = internal_ucm_suffix;
}
void cras_system_state_deinit()
{
/* Free any resources used. This prevents unit tests from leaking. */
cras_device_blocklist_destroy(state.device_blocklist);
cras_tm_deinit(state.tm);
if (state.exp_state) {
munmap(state.exp_state, state.shm_size);
cras_shm_close_unlink(state.shm_name, state.shm_fd);
if (state.shm_fd_ro != state.shm_fd)
close(state.shm_fd_ro);
}
deinit_ignore_suffix_cards();
pthread_mutex_destroy(&state.update_lock);
}
void cras_system_set_volume(size_t volume)
{
if (volume > CRAS_MAX_SYSTEM_VOLUME)
syslog(LOG_DEBUG, "system volume set out of range %zu", volume);
state.exp_state->volume = MIN(volume, CRAS_MAX_SYSTEM_VOLUME);
cras_observer_notify_output_volume(state.exp_state->volume);
}
size_t cras_system_get_volume()
{
return state.exp_state->volume;
}
void cras_system_notify_mute(void)
{
cras_observer_notify_output_mute(state.exp_state->mute,
state.exp_state->user_mute,
state.exp_state->mute_locked);
}
void cras_system_set_user_mute(int mute)
{
int current_mute = cras_system_get_mute();
if (state.exp_state->user_mute == !!mute)
return;
state.exp_state->user_mute = !!mute;
if (current_mute == (mute || state.exp_state->mute))
return;
cras_system_notify_mute();
}
void cras_system_set_mute(int mute)
{
int current_mute = cras_system_get_mute();
if (state.exp_state->mute_locked)
return;
if (state.exp_state->mute == !!mute)
return;
state.exp_state->mute = !!mute;
if (current_mute == (mute || state.exp_state->user_mute))
return;
cras_system_notify_mute();
}
void cras_system_set_mute_locked(int locked)
{
if (state.exp_state->mute_locked == !!locked)
return;
state.exp_state->mute_locked = !!locked;
}
int cras_system_get_mute()
{
return state.exp_state->mute || state.exp_state->user_mute;
}
int cras_system_get_user_mute()
{
return state.exp_state->user_mute;
}
int cras_system_get_system_mute()
{
return state.exp_state->mute;
}
int cras_system_get_mute_locked()
{
return state.exp_state->mute_locked;
}
void cras_system_notify_capture_mute(void)
{
cras_observer_notify_capture_mute(state.exp_state->capture_mute,
state.exp_state->capture_mute_locked);
}
void cras_system_set_capture_mute(int mute)
{
if (state.exp_state->capture_mute_locked)
return;
state.exp_state->capture_mute = !!mute;
cras_system_notify_capture_mute();
}
void cras_system_set_capture_mute_locked(int locked)
{
state.exp_state->capture_mute_locked = !!locked;
cras_system_notify_capture_mute();
}
int cras_system_get_capture_mute()
{
return state.exp_state->capture_mute;
}
int cras_system_get_capture_mute_locked()
{
return state.exp_state->capture_mute_locked;
}
int cras_system_get_suspended()
{
return state.exp_state->suspended;
}
void cras_system_set_suspended(int suspended)
{
state.exp_state->suspended = suspended;
cras_observer_notify_suspend_changed(suspended);
cras_alert_process_all_pending_alerts();
}
void cras_system_set_volume_limits(long min, long max)
{
state.exp_state->min_volume_dBFS = min;
state.exp_state->max_volume_dBFS = max;
}
long cras_system_get_min_volume()
{
return state.exp_state->min_volume_dBFS;
}
long cras_system_get_max_volume()
{
return state.exp_state->max_volume_dBFS;
}
int cras_system_get_default_output_buffer_size()
{
return state.exp_state->default_output_buffer_size;
}
int cras_system_get_aec_supported()
{
return state.exp_state->aec_supported;
}
int cras_system_get_aec_group_id()
{
return state.exp_state->aec_group_id;
}
void cras_system_set_bt_wbs_enabled(bool enabled)
{
state.exp_state->bt_wbs_enabled = enabled;
}
bool cras_system_get_bt_wbs_enabled()
{
return !!state.exp_state->bt_wbs_enabled;
}
bool cras_system_get_deprioritize_bt_wbs_mic()
{
return !!state.exp_state->deprioritize_bt_wbs_mic;
}
void cras_system_set_bt_fix_a2dp_packet_size_enabled(bool enabled)
{
state.bt_fix_a2dp_packet_size = enabled;
}
bool cras_system_get_bt_fix_a2dp_packet_size_enabled()
{
return state.bt_fix_a2dp_packet_size;
}
void cras_system_set_noise_cancellation_enabled(bool enabled)
{
/* When the flag is toggled, propagate to all iodevs immediately. */
if (cras_system_get_noise_cancellation_enabled() != enabled) {
state.exp_state->noise_cancellation_enabled = enabled;
cras_iodev_list_reset_for_noise_cancellation();
}
}
bool cras_system_get_noise_cancellation_enabled()
{
return !!state.exp_state->noise_cancellation_enabled;
}
bool cras_system_check_ignore_ucm_suffix(const char *card_name)
{
/* Check the general case: ALSA Loopback card "Loopback". */
if (!strcmp("Loopback", card_name))
return true;
/* Check board-specific ignore ucm suffix cards. */
struct name_list *card;
DL_FOREACH (state.ignore_suffix_cards, card) {
if (!strcmp(card->name, card_name))
return true;
}
return false;
}
bool cras_system_get_hotword_pause_at_suspend()
{
return !!state.exp_state->hotword_pause_at_suspend;
}
void cras_system_set_hotword_pause_at_suspend(bool pause)
{
state.exp_state->hotword_pause_at_suspend = pause;
}
int cras_system_add_alsa_card(struct cras_alsa_card_info *alsa_card_info)
{
struct card_list *card;
struct cras_alsa_card *alsa_card;
unsigned card_index;
if (alsa_card_info == NULL)
return -EINVAL;
card_index = alsa_card_info->card_index;
DL_FOREACH (state.cards, card) {
if (card_index == cras_alsa_card_get_index(card->card))
return -EEXIST;
}
alsa_card =
cras_alsa_card_create(alsa_card_info, state.device_config_dir,
state.device_blocklist,
state.internal_ucm_suffix);
if (alsa_card == NULL)
return -ENOMEM;
card = calloc(1, sizeof(*card));
if (card == NULL)
return -ENOMEM;
card->card = alsa_card;
DL_APPEND(state.cards, card);
return 0;
}
int cras_system_remove_alsa_card(size_t alsa_card_index)
{
struct card_list *card;
DL_FOREACH (state.cards, card) {
if (alsa_card_index == cras_alsa_card_get_index(card->card))
break;
}
if (card == NULL)
return -EINVAL;
DL_DELETE(state.cards, card);
cras_alsa_card_destroy(card->card);
free(card);
return 0;
}
int cras_system_alsa_card_exists(unsigned alsa_card_index)
{
struct card_list *card;
DL_FOREACH (state.cards, card)
if (alsa_card_index == cras_alsa_card_get_index(card->card))
return 1;
return 0;
}
int cras_system_set_select_handler(
int (*add)(int fd, void (*callback)(void *data, int events),
void *callback_data, int events, void *select_data),
void (*rm)(int fd, void *select_data), void *select_data)
{
if (state.fd_add != NULL || state.fd_rm != NULL)
return -EEXIST;
state.fd_add = add;
state.fd_rm = rm;
state.select_data = select_data;
return 0;
}
int cras_system_add_select_fd(int fd, void (*callback)(void *data, int revents),
void *callback_data, int events)
{
if (state.fd_add == NULL)
return -EINVAL;
return state.fd_add(fd, callback, callback_data, events,
state.select_data);
}
int cras_system_set_add_task_handler(int (*add_task)(void (*cb)(void *data),
void *callback_data,
void *task_data),
void *task_data)
{
if (state.add_task != NULL)
return -EEXIST;
state.add_task = add_task;
state.task_data = task_data;
return 0;
}
int cras_system_add_task(void (*callback)(void *data), void *callback_data)
{
if (state.add_task == NULL)
return -EINVAL;
return state.add_task(callback, callback_data, state.task_data);
}
void cras_system_rm_select_fd(int fd)
{
if (state.fd_rm != NULL)
state.fd_rm(fd, state.select_data);
}
void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction,
enum CRAS_CLIENT_TYPE client_type)
{
struct cras_server_state *s;
s = cras_system_state_update_begin();
if (!s)
return;
s->num_active_streams[direction]++;
s->num_streams_attached++;
if (direction == CRAS_STREAM_INPUT) {
s->num_input_streams_with_permission[client_type]++;
cras_observer_notify_input_streams_with_permission(
s->num_input_streams_with_permission);
}
cras_system_state_update_complete();
cras_observer_notify_num_active_streams(
direction, s->num_active_streams[direction]);
}
void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction,
enum CRAS_CLIENT_TYPE client_type)
{
struct cras_server_state *s;
unsigned i, sum;
s = cras_system_state_update_begin();
if (!s)
return;
sum = 0;
for (i = 0; i < CRAS_NUM_DIRECTIONS; i++)
sum += s->num_active_streams[i];
/* Set the last active time when removing the final stream. */
if (sum == 1)
cras_clock_gettime(CLOCK_MONOTONIC_RAW,
&s->last_active_stream_time);
s->num_active_streams[direction]--;
if (direction == CRAS_STREAM_INPUT) {
s->num_input_streams_with_permission[client_type]--;
cras_observer_notify_input_streams_with_permission(
s->num_input_streams_with_permission);
}
cras_system_state_update_complete();
cras_observer_notify_num_active_streams(
direction, s->num_active_streams[direction]);
}
unsigned cras_system_state_get_active_streams()
{
unsigned i, sum;
sum = 0;
for (i = 0; i < CRAS_NUM_DIRECTIONS; i++)
sum += state.exp_state->num_active_streams[i];
return sum;
}
unsigned cras_system_state_get_active_streams_by_direction(
enum CRAS_STREAM_DIRECTION direction)
{
return state.exp_state->num_active_streams[direction];
}
void cras_system_state_get_input_streams_with_permission(
uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE])
{
unsigned type;
for (type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type)
num_input_streams[type] =
state.exp_state->num_input_streams_with_permission[type];
}
void cras_system_state_get_last_stream_active_time(struct cras_timespec *ts)
{
*ts = state.exp_state->last_active_stream_time;
}
int cras_system_state_get_output_devs(const struct cras_iodev_info **devs)
{
*devs = state.exp_state->output_devs;
return state.exp_state->num_output_devs;
}
int cras_system_state_get_input_devs(const struct cras_iodev_info **devs)
{
*devs = state.exp_state->input_devs;
return state.exp_state->num_input_devs;
}
int cras_system_state_get_output_nodes(const struct cras_ionode_info **nodes)
{
*nodes = state.exp_state->output_nodes;
return state.exp_state->num_output_nodes;
}
int cras_system_state_get_input_nodes(const struct cras_ionode_info **nodes)
{
*nodes = state.exp_state->input_nodes;
return state.exp_state->num_input_nodes;
}
void cras_system_state_set_non_empty_status(int non_empty)
{
state.exp_state->non_empty_status = non_empty;
}
int cras_system_state_get_non_empty_status()
{
return state.exp_state->non_empty_status;
}
struct cras_server_state *cras_system_state_update_begin()
{
if (pthread_mutex_lock(&state.update_lock)) {
syslog(LOG_ERR, "Failed to lock stream mutex");
return NULL;
}
__sync_fetch_and_add(&state.exp_state->update_count, 1);
return state.exp_state;
}
void cras_system_state_update_complete()
{
__sync_fetch_and_add(&state.exp_state->update_count, 1);
pthread_mutex_unlock(&state.update_lock);
}
struct cras_server_state *cras_system_state_get_no_lock()
{
return state.exp_state;
}
key_t cras_sys_state_shm_fd()
{
return state.shm_fd_ro;
}
struct cras_tm *cras_system_state_get_tm()
{
return state.tm;
}
void cras_system_state_dump_snapshots()
{
memcpy(&state.exp_state->snapshot_buffer, &state.snapshot_buffer,
sizeof(struct cras_audio_thread_snapshot_buffer));
}
void cras_system_state_add_snapshot(struct cras_audio_thread_snapshot *snapshot)
{
state.snapshot_buffer.snapshots[state.snapshot_buffer.pos++] =
(*snapshot);
state.snapshot_buffer.pos %= CRAS_MAX_AUDIO_THREAD_SNAPSHOTS;
}
int cras_system_state_in_main_thread()
{
return pthread_self() == state.main_thread_tid;
}