/* 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 <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_config.h"
#include "cras_device_blacklist.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;
};
/* 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.
* 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_blacklist - Blacklist 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.
*/
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 cras_device_blacklist *device_blacklist;
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),
void *cb_data, void *select_data);
void (*fd_rm)(int fd, void *select_data);
void *select_data;
} state;
/*
* Exported Interface.
*/
void cras_system_state_init(const char *device_config_dir)
{
struct cras_server_state *exp_state;
int rc;
state.shm_size = sizeof(*exp_state);
snprintf(state.shm_name, sizeof(state.shm_name), "/cras-%d", getpid());
state.shm_fd = cras_shm_open_rw(state.shm_name, state.shm_size);
if (state.shm_fd < 0)
exit(state.shm_fd);
/* mmap shm. */
exp_state = mmap(NULL, state.shm_size,
PROT_READ | PROT_WRITE, MAP_SHARED,
state.shm_fd, 0);
if (exp_state == (struct cras_server_state *)-1)
exit(-ENOMEM);
/* Open a read-only copy to dup and pass to clients. */
state.shm_fd_ro = cras_shm_reopen_ro(state.shm_name, state.shm_fd);
if (state.shm_fd_ro < 0)
exit(state.shm_fd_ro);
/* 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_gain = DEFAULT_CAPTURE_GAIN;
exp_state->capture_gain_target = DEFAULT_CAPTURE_GAIN;
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->min_capture_gain = DEFAULT_MIN_CAPTURE_GAIN;
exp_state->max_capture_gain = DEFAULT_MAX_CAPTURE_GAIN;
exp_state->num_streams_attached = 0;
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 blacklist.
* Device blacklist is common to all boards so we do not need
* to change device blacklist at run time. */
state.device_config_dir = device_config_dir;
state.internal_ucm_suffix = NULL;
state.tm = cras_tm_init();
if (!state.tm) {
syslog(LOG_ERR, "Fatal: system state timer init");
exit(-ENOMEM);
}
/* Read config file for blacklisted devices. */
state.device_blacklist =
cras_device_blacklist_create(CRAS_CONFIG_FILE_DIR);
}
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_blacklist_destroy(state.device_blacklist);
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);
}
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_set_capture_gain(long gain)
{
/* Adjust targeted gain to be in supported range. */
state.exp_state->capture_gain_target = gain;
gain = MAX(gain, state.exp_state->min_capture_gain);
gain = MIN(gain, state.exp_state->max_capture_gain);
state.exp_state->capture_gain = gain;
cras_observer_notify_capture_gain(state.exp_state->capture_gain);
}
long cras_system_get_capture_gain()
{
return state.exp_state->capture_gain;
}
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)
{
if (state.exp_state->user_mute == !!mute)
return;
state.exp_state->user_mute = !!mute;
cras_system_notify_mute();
}
void cras_system_set_mute(int mute)
{
if (state.exp_state->mute_locked)
return;
if (state.exp_state->mute == !!mute)
return;
state.exp_state->mute = !!mute;
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;
cras_system_notify_mute();
}
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);
}
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;
}
void cras_system_set_capture_gain_limits(long min, long max)
{
state.exp_state->min_capture_gain = MAX(min, DEFAULT_MIN_CAPTURE_GAIN);
state.exp_state->max_capture_gain = max;
/* Re-apply target gain subjected to the new supported range. */
cras_system_set_capture_gain(state.exp_state->capture_gain_target);
}
long cras_system_get_min_capture_gain()
{
return state.exp_state->min_capture_gain;
}
long cras_system_get_max_capture_gain()
{
return state.exp_state->max_capture_gain;
}
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 -EINVAL;
}
alsa_card = cras_alsa_card_create(
alsa_card_info,
state.device_config_dir,
state.device_blacklist,
(alsa_card_info->card_type == ALSA_CARD_TYPE_INTERNAL)
? state.internal_ucm_suffix
: NULL);
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),
void *callback_data,
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),
void *callback_data)
{
if (state.fd_add == NULL)
return -EINVAL;
return state.fd_add(fd, callback, callback_data,
state.select_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)
{
struct cras_server_state *s;
s = cras_system_state_update_begin();
if (!s)
return;
s->num_active_streams[direction]++;
s->num_streams_attached++;
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)
{
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]--;
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_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;
}
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;
}