blob: 32c1ae11720ad7ca09f84ceb2c4e6f2195802849 [file] [log] [blame]
/* Copyright 2020 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 <alsa/asoundlib.h>
#include <alsa/use-case.h>
#include <stdio.h>
#include <sys/select.h>
#include <syslog.h>
#include "cras_alsa_io.h"
#include "cras_alsa_jack.h"
#include "cras_alsa_mixer.h"
#include "cras_alsa_ucm.h"
#include "cras_iodev.h"
#include "cras_system_state.h"
#include "iniparser_wrapper.h"
#include "utlist.h"
#define PLUGINS_INI "plugins.ini"
#define PLUGIN_KEY_CTL "ctl"
#define PLUGIN_KEY_DIR "dir"
#define PLUGIN_KEY_PCM "pcm"
#define PLUGIN_KEY_CARD "card"
#define NULL_USB_VID 0x00
#define NULL_USB_PID 0x00
#define NULL_USB_SERIAL_NUMBER "serial-number-not-used"
struct hctl_poll_fd {
int fd;
struct hctl_poll_fd *prev, *next;
};
struct alsa_plugin {
snd_hctl_t *hctl;
struct cras_alsa_mixer *mixer;
struct hctl_poll_fd *hctl_poll_fds;
struct cras_use_case_mgr *ucm;
struct cras_iodev *iodev;
struct alsa_plugin *next, *prev;
};
static struct alsa_plugin *plugins;
static char ini_name[MAX_INI_NAME_LENGTH + 1];
static char key_name[MAX_INI_NAME_LENGTH + 1];
static dictionary *plugins_ini = NULL;
static void hctl_event_pending(void *arg, int revents)
{
struct alsa_plugin *plugin;
plugin = (struct alsa_plugin *)arg;
if (plugin->hctl == NULL)
return;
/* handle_events will trigger the callback registered with each control
* that has changed. */
snd_hctl_handle_events(plugin->hctl);
}
/* hctl poll descritpor */
static void collect_poll_descriptors(struct alsa_plugin *plugin)
{
struct hctl_poll_fd *registered_fd;
struct pollfd *pollfds;
int i, n, rc;
n = snd_hctl_poll_descriptors_count(plugin->hctl);
if (n == 0) {
syslog(LOG_DEBUG, "No hctl descritpor to poll");
return;
}
pollfds = malloc(n * sizeof(*pollfds));
if (pollfds == NULL)
return;
n = snd_hctl_poll_descriptors(plugin->hctl, pollfds, n);
for (i = 0; i < n; i++) {
registered_fd = calloc(1, sizeof(*registered_fd));
if (registered_fd == NULL) {
free(pollfds);
return;
}
registered_fd->fd = pollfds[i].fd;
DL_APPEND(plugin->hctl_poll_fds, registered_fd);
rc = cras_system_add_select_fd(
registered_fd->fd, hctl_event_pending, plugin, POLLIN);
if (rc < 0) {
DL_DELETE(plugin->hctl_poll_fds, registered_fd);
free(pollfds);
return;
}
}
free(pollfds);
}
static void cleanup_poll_descriptors(struct alsa_plugin *plugin)
{
struct hctl_poll_fd *poll_fd;
DL_FOREACH (plugin->hctl_poll_fds, poll_fd) {
cras_system_rm_select_fd(poll_fd->fd);
DL_DELETE(plugin->hctl_poll_fds, poll_fd);
free(poll_fd);
}
}
static void destroy_plugin(struct alsa_plugin *plugin);
void alsa_plugin_io_create(enum CRAS_STREAM_DIRECTION direction,
const char *pcm_name, const char *ctl_name,
const char *card_name)
{
struct alsa_plugin *plugin;
struct ucm_section *section;
struct ucm_section *ucm_sections;
int rc;
plugin = (struct alsa_plugin *)calloc(1, sizeof(*plugin));
if (!plugin) {
syslog(LOG_ERR, "No memory to create alsa plugin");
return;
}
rc = snd_hctl_open(&plugin->hctl, ctl_name, SND_CTL_NONBLOCK);
if (rc < 0) {
syslog(LOG_ERR, "open hctl fail for plugin %s", ctl_name);
goto cleanup;
}
rc = snd_hctl_nonblock(plugin->hctl, 1);
if (rc < 0) {
syslog(LOG_ERR, "Failed to nonblock hctl for %s", ctl_name);
goto cleanup;
}
rc = snd_hctl_load(plugin->hctl);
if (rc < 0) {
syslog(LOG_ERR, "Failed to load hctl for %s", ctl_name);
goto cleanup;
}
collect_poll_descriptors(plugin);
plugin->mixer = cras_alsa_mixer_create(ctl_name);
plugin->ucm = ucm_create(card_name);
DL_APPEND(plugins, plugin);
ucm_sections = ucm_get_sections(plugin->ucm);
DL_FOREACH (ucm_sections, section) {
rc = cras_alsa_mixer_add_controls_in_section(plugin->mixer,
section);
if (rc)
syslog(LOG_ERR,
"Failed adding control to plugin,"
"section %s mixer_name %s",
section->name, section->mixer_name);
}
plugin->iodev = alsa_iodev_create(0, card_name, 0, pcm_name, "", "",
ALSA_CARD_TYPE_USB, 1, /* is first */
plugin->mixer, NULL, plugin->ucm,
plugin->hctl, direction, NULL_USB_VID,
NULL_USB_PID, NULL_USB_SERIAL_NUMBER);
DL_FOREACH (ucm_sections, section) {
if (section->dir != plugin->iodev->direction)
continue;
section->dev_idx = 0;
alsa_iodev_ucm_add_nodes_and_jacks(plugin->iodev, section);
}
alsa_iodev_ucm_complete_init(plugin->iodev);
return;
cleanup:
if (plugin)
destroy_plugin(plugin);
}
static void destroy_plugin(struct alsa_plugin *plugin)
{
cleanup_poll_descriptors(plugin);
if (plugin->hctl)
snd_hctl_close(plugin->hctl);
if (plugin->iodev)
alsa_iodev_destroy(plugin->iodev);
if (plugin->mixer)
cras_alsa_mixer_destroy(plugin->mixer);
free(plugin);
}
void alsa_pluigin_io_destroy_all()
{
struct alsa_plugin *plugin;
DL_FOREACH (plugins, plugin)
destroy_plugin(plugin);
}
void cras_alsa_plugin_io_init(const char *device_config_dir)
{
int nsec, i;
enum CRAS_STREAM_DIRECTION direction;
const char *sec_name;
const char *tmp, *pcm_name, *ctl_name, *card_name;
snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", device_config_dir,
PLUGINS_INI);
ini_name[MAX_INI_NAME_LENGTH] = '\0';
plugins_ini = iniparser_load_wrapper(ini_name);
if (!plugins_ini)
return;
nsec = iniparser_getnsec(plugins_ini);
for (i = 0; i < nsec; i++) {
sec_name = iniparser_getsecname(plugins_ini, i);
/* Parse dir=output or dir=input */
snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
PLUGIN_KEY_DIR);
tmp = iniparser_getstring(plugins_ini, key_name, NULL);
if (strcmp(tmp, "output") == 0)
direction = CRAS_STREAM_OUTPUT;
else if (strcmp(tmp, "input") == 0)
direction = CRAS_STREAM_INPUT;
else
continue;
/* pcm=<plugin-pcm-name> this name will be used with
* snd_pcm_open. */
snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
PLUGIN_KEY_PCM);
pcm_name = iniparser_getstring(plugins_ini, key_name, NULL);
if (!pcm_name)
continue;
/* ctl=<plugin-ctl-name> this name will be used with
* snd_hctl_open. */
snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
PLUGIN_KEY_CTL);
ctl_name = iniparser_getstring(plugins_ini, key_name, NULL);
if (!ctl_name)
continue;
/* card=<card-name> this name will be used with
* snd_use_case_mgr_open. */
snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
PLUGIN_KEY_CARD);
card_name = iniparser_getstring(plugins_ini, key_name, NULL);
if (!card_name)
continue;
syslog(LOG_DEBUG,
"Creating plugin for direction %s, pcm %s, ctl %s, card %s",
direction == CRAS_STREAM_OUTPUT ? "output" : "input",
pcm_name, ctl_name, card_name);
alsa_plugin_io_create(direction, pcm_name, ctl_name, card_name);
}
}