/* 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. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* For asprintf */ #endif #include #include #include "cras_alsa_card.h" #include "cras_alsa_io.h" #include "cras_alsa_mixer.h" #include "cras_alsa_ucm.h" #include "cras_device_blacklist.h" #include "cras_card_config.h" #include "cras_config.h" #include "cras_iodev.h" #include "cras_iodev_list.h" #include "cras_system_state.h" #include "cras_types.h" #include "cras_util.h" #include "utlist.h" #define MAX_ALSA_CARDS 32 /* Alsa limit on number of cards. */ #define MAX_ALSA_PCM_NAME_LENGTH 6 /* Alsa names "hw:XX" + 1 for null. */ #define MAX_INI_NAME_LENGTH 63 /* 63 chars + 1 for null where declared. */ #define MAX_COUPLED_OUTPUT_SIZE 4 struct iodev_list_node { struct cras_iodev *iodev; enum CRAS_STREAM_DIRECTION direction; struct iodev_list_node *prev, *next; }; /* Keeps an fd that is registered with system state. A list of fds must be * kept so that they can be removed when the card is destroyed. */ struct hctl_poll_fd { int fd; struct hctl_poll_fd *prev, *next; }; /* Holds information about each sound card on the system. * name - of the form hw:XX,YY. * card_index - 0 based index, value of "XX" in the name. * iodevs - Input and output devices for this card. * mixer - Controls the mixer controls for this card. * ucm - CRAS use case manager if available. * hctl - ALSA high-level control interface. * hctl_poll_fds - List of fds registered with cras_system_state. * config - Config info for this card, can be NULL if none found. */ struct cras_alsa_card { char name[MAX_ALSA_PCM_NAME_LENGTH]; size_t card_index; struct iodev_list_node *iodevs; struct cras_alsa_mixer *mixer; struct cras_use_case_mgr *ucm; snd_hctl_t *hctl; struct hctl_poll_fd *hctl_poll_fds; struct cras_card_config *config; }; /* Creates an iodev for the given device. * Args: * alsa_card - the alsa_card the device will be added to. * info - Information about the card type and priority. * card_name - The name of the card. * dev_name - The name of the device. * dev_id - The id string of the device. * device_index - 0 based index, value of "YY" in "hw:XX,YY". * direction - Input or output. * Returns: * Pointer to the created iodev, or NULL on error. * other negative error code otherwise. */ struct cras_iodev *create_iodev_for_device( struct cras_alsa_card *alsa_card, struct cras_alsa_card_info *info, const char *card_name, const char *dev_name, const char *dev_id, unsigned device_index, enum CRAS_STREAM_DIRECTION direction) { struct iodev_list_node *new_dev; struct iodev_list_node *node; int first = 1; /* Find whether this is the first device in this direction, and * avoid duplicate device indexes. */ DL_FOREACH(alsa_card->iodevs, node) { if (node->direction != direction) continue; first = 0; if (alsa_iodev_index(node->iodev) == device_index) { syslog(LOG_DEBUG, "Skipping duplicate device for %s:%s:%s [%u]", card_name, dev_name, dev_id, device_index); return node->iodev; } } new_dev = calloc(1, sizeof(*new_dev)); if (new_dev == NULL) return NULL; new_dev->direction = direction; new_dev->iodev = alsa_iodev_create(info->card_index, card_name, device_index, dev_name, dev_id, info->card_type, first, alsa_card->mixer, alsa_card->config, alsa_card->ucm, alsa_card->hctl, direction, info->usb_vendor_id, info->usb_product_id, info->usb_serial_number); if (new_dev->iodev == NULL) { syslog(LOG_ERR, "Couldn't create alsa_iodev for %u:%u\n", info->card_index, device_index); free(new_dev); return NULL; } syslog(LOG_DEBUG, "New %s device %u:%d", direction == CRAS_STREAM_OUTPUT ? "playback" : "capture", info->card_index, device_index); DL_APPEND(alsa_card->iodevs, new_dev); return new_dev->iodev; } /* Returns non-zero if this card has hctl jacks. */ static int card_has_hctl_jack(struct cras_alsa_card *alsa_card) { struct iodev_list_node *node; /* Find the first device that has an hctl jack. */ DL_FOREACH(alsa_card->iodevs, node) { if (alsa_iodev_has_hctl_jacks(node->iodev)) return 1; } return 0; } /* Check if a device should be ignored for this card. Returns non-zero if the * device is in the blacklist and should be ignored. */ static int should_ignore_dev(struct cras_alsa_card_info *info, struct cras_device_blacklist *blacklist, size_t device_index) { if (info->card_type == ALSA_CARD_TYPE_USB) return cras_device_blacklist_check(blacklist, info->usb_vendor_id, info->usb_product_id, info->usb_desc_checksum, device_index); return 0; } /* Filters an array of mixer control names. Keep a name if it is * specified in the ucm config. */ static struct mixer_name *filter_controls(struct cras_use_case_mgr *ucm, struct mixer_name *controls) { struct mixer_name *control; DL_FOREACH(controls, control) { char *dev = ucm_get_dev_for_mixer(ucm, control->name, CRAS_STREAM_OUTPUT); if (!dev) DL_DELETE(controls, control); else free(dev); } return controls; } /* Handles notifications from alsa controls. Called by main thread when a poll * fd provided by alsa signals there is an event available. */ static void alsa_control_event_pending(void *arg) { struct cras_alsa_card *card; card = (struct cras_alsa_card *)arg; if (card == NULL) { syslog(LOG_ERR, "Invalid card from control event."); return; } /* handle_events will trigger the callback registered with each control * that has changed. */ snd_hctl_handle_events(card->hctl); } static int add_controls_and_iodevs_by_matching( struct cras_alsa_card_info *info, struct cras_device_blacklist *blacklist, struct cras_alsa_card *alsa_card, const char *card_name, snd_ctl_t *handle) { struct mixer_name *coupled_controls = NULL; int dev_idx; snd_pcm_info_t *dev_info; struct mixer_name *extra_controls = NULL; int rc = 0; snd_pcm_info_alloca(&dev_info); if (alsa_card->ucm) { char *extra_main_volume; /* Filter the extra output mixer names */ extra_controls = filter_controls(alsa_card->ucm, mixer_name_add(extra_controls, "IEC958", CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME)); /* Get the extra main volume control. */ extra_main_volume = ucm_get_flag(alsa_card->ucm, "ExtraMainVolume"); if (extra_main_volume) { extra_controls = mixer_name_add(extra_controls, extra_main_volume, CRAS_STREAM_OUTPUT, MIXER_NAME_MAIN_VOLUME); free(extra_main_volume); } mixer_name_dump(extra_controls, "extra controls"); /* Check if coupled controls has been specified for speaker. */ coupled_controls = ucm_get_coupled_mixer_names( alsa_card->ucm, "Speaker"); mixer_name_dump(coupled_controls, "coupled controls"); } /* Add controls to mixer by name matching. */ rc = cras_alsa_mixer_add_controls_by_name_matching( alsa_card->mixer, extra_controls, coupled_controls); if (rc) { syslog(LOG_ERR, "Fail adding controls to mixer for %s.", alsa_card->name); goto error; } /* Go through every device. */ dev_idx = -1; while (1) { rc = snd_ctl_pcm_next_device(handle, &dev_idx); if (rc < 0) goto error; if (dev_idx < 0) break; snd_pcm_info_set_device(dev_info, dev_idx); snd_pcm_info_set_subdevice(dev_info, 0); /* Check for playback devices. */ snd_pcm_info_set_stream( dev_info, SND_PCM_STREAM_PLAYBACK); if (snd_ctl_pcm_info(handle, dev_info) == 0 && !should_ignore_dev(info, blacklist, dev_idx)) { struct cras_iodev *iodev = create_iodev_for_device( alsa_card, info, card_name, snd_pcm_info_get_name(dev_info), snd_pcm_info_get_id(dev_info), dev_idx, CRAS_STREAM_OUTPUT); if (iodev) { rc = alsa_iodev_legacy_complete_init( iodev); if (rc < 0) goto error; } } /* Check for capture devices. */ snd_pcm_info_set_stream( dev_info, SND_PCM_STREAM_CAPTURE); if (snd_ctl_pcm_info(handle, dev_info) == 0) { struct cras_iodev *iodev = create_iodev_for_device( alsa_card, info, card_name, snd_pcm_info_get_name(dev_info), snd_pcm_info_get_id(dev_info), dev_idx, CRAS_STREAM_INPUT); if (iodev) { rc = alsa_iodev_legacy_complete_init( iodev); if (rc < 0) goto error; } } } error: mixer_name_free(coupled_controls); mixer_name_free(extra_controls); return rc; } static int add_controls_and_iodevs_with_ucm( struct cras_alsa_card_info *info, struct cras_alsa_card *alsa_card, const char *card_name, snd_ctl_t *handle) { snd_pcm_info_t *dev_info; struct iodev_list_node *node; int rc = 0; struct ucm_section *section; struct ucm_section *ucm_sections; snd_pcm_info_alloca(&dev_info); /* Get info on the devices specified in the UCM config. */ ucm_sections = ucm_get_sections(alsa_card->ucm); if (!ucm_sections) { syslog(LOG_ERR, "Could not retrieve any UCM SectionDevice" " info for '%s'.", card_name); rc = -ENOENT; goto error; } /* Create all of the controls first. */ DL_FOREACH(ucm_sections, section) { rc = cras_alsa_mixer_add_controls_in_section( alsa_card->mixer, section); if (rc) { syslog(LOG_ERR, "Failed adding controls to" " mixer for '%s:%s'", card_name, section->name); goto error; } } /* Create all of the devices. */ DL_FOREACH(ucm_sections, section) { snd_pcm_info_set_device(dev_info, section->dev_idx); snd_pcm_info_set_subdevice(dev_info, 0); if (section->dir == CRAS_STREAM_OUTPUT) snd_pcm_info_set_stream( dev_info, SND_PCM_STREAM_PLAYBACK); else if (section->dir == CRAS_STREAM_INPUT) snd_pcm_info_set_stream( dev_info, SND_PCM_STREAM_CAPTURE); else { syslog(LOG_ERR, "Unexpected direction: %d", section->dir); rc = -EINVAL; goto error; } if (snd_ctl_pcm_info(handle, dev_info)) { syslog(LOG_ERR, "Could not get info for device: %s", section->name); continue; } create_iodev_for_device( alsa_card, info, card_name, snd_pcm_info_get_name(dev_info), snd_pcm_info_get_id(dev_info), section->dev_idx, section->dir); } /* Setup jacks and controls for the devices. */ DL_FOREACH(ucm_sections, section) { DL_FOREACH(alsa_card->iodevs, node) { if (node->direction == section->dir && alsa_iodev_index(node->iodev) == section->dev_idx) break; } if (node) { rc = alsa_iodev_ucm_add_nodes_and_jacks( node->iodev, section); if (rc < 0) goto error; } } DL_FOREACH(alsa_card->iodevs, node) { alsa_iodev_ucm_complete_init(node->iodev); } error: ucm_section_free_list(ucm_sections); return rc; } static void configure_echo_reference_dev(struct cras_alsa_card *alsa_card) { struct iodev_list_node *dev_node, *echo_ref_node; const char *echo_ref_name; if (!alsa_card->ucm) return; DL_FOREACH(alsa_card->iodevs, dev_node) { if (!dev_node->iodev->nodes) continue; echo_ref_name = ucm_get_echo_reference_dev_name_for_dev( alsa_card->ucm, dev_node->iodev->nodes->name); if (!echo_ref_name) continue; DL_FOREACH(alsa_card->iodevs, echo_ref_node) { if (echo_ref_node->iodev->nodes == NULL) continue; if (!strcmp(echo_ref_name, echo_ref_node->iodev->nodes->name)) break; } if (echo_ref_node) dev_node->iodev->echo_reference_dev = echo_ref_node->iodev; else syslog(LOG_ERR, "Echo ref dev %s doesn't exist on card %s", echo_ref_name, alsa_card->name); free((void *)echo_ref_name); } } /* * Exported Interface. */ struct cras_alsa_card *cras_alsa_card_create( struct cras_alsa_card_info *info, const char *device_config_dir, struct cras_device_blacklist *blacklist, const char *ucm_suffix) { snd_ctl_t *handle = NULL; int rc, n; snd_ctl_card_info_t *card_info; const char *card_name; struct cras_alsa_card *alsa_card; if (info->card_index >= MAX_ALSA_CARDS) { syslog(LOG_ERR, "Invalid alsa card index %u", info->card_index); return NULL; } snd_ctl_card_info_alloca(&card_info); alsa_card = calloc(1, sizeof(*alsa_card)); if (alsa_card == NULL) return NULL; alsa_card->card_index = info->card_index; snprintf(alsa_card->name, MAX_ALSA_PCM_NAME_LENGTH, "hw:%u", info->card_index); rc = snd_ctl_open(&handle, alsa_card->name, 0); if (rc < 0) { syslog(LOG_ERR, "Fail opening control %s.", alsa_card->name); goto error_bail; } rc = snd_ctl_card_info(handle, card_info); if (rc < 0) { syslog(LOG_ERR, "Error getting card info."); goto error_bail; } card_name = snd_ctl_card_info_get_name(card_info); if (card_name == NULL) { syslog(LOG_ERR, "Error getting card name."); goto error_bail; } /* Read config file for this card if it exists. */ alsa_card->config = cras_card_config_create(device_config_dir, card_name); if (alsa_card->config == NULL) syslog(LOG_DEBUG, "No config file for %s", alsa_card->name); /* Create a use case manager if a configuration is available. */ if (ucm_suffix) { char *ucm_name; if (asprintf(&ucm_name, "%s.%s", card_name, ucm_suffix) == -1) { syslog(LOG_ERR, "Error creating ucm name"); goto error_bail; } alsa_card->ucm = ucm_create(ucm_name); syslog(LOG_INFO, "Card %s (%s) has UCM: %s", alsa_card->name, ucm_name, alsa_card->ucm ? "yes" : "no"); free(ucm_name); } else { alsa_card->ucm = ucm_create(card_name); syslog(LOG_INFO, "Card %s (%s) has UCM: %s", alsa_card->name, card_name, alsa_card->ucm ? "yes" : "no"); } rc = snd_hctl_open(&alsa_card->hctl, alsa_card->name, SND_CTL_NONBLOCK); if (rc < 0) { syslog(LOG_DEBUG, "failed to get hctl for %s", alsa_card->name); alsa_card->hctl = NULL; } else { rc = snd_hctl_nonblock(alsa_card->hctl, 1); if (rc < 0) { syslog(LOG_ERR, "failed to nonblock hctl for %s", alsa_card->name); goto error_bail; } rc = snd_hctl_load(alsa_card->hctl); if (rc < 0) { syslog(LOG_ERR, "failed to load hctl for %s", alsa_card->name); goto error_bail; } } /* Create one mixer per card. */ alsa_card->mixer = cras_alsa_mixer_create(alsa_card->name); if (alsa_card->mixer == NULL) { syslog(LOG_ERR, "Fail opening mixer for %s.", alsa_card->name); goto error_bail; } if (alsa_card->ucm && ucm_has_fully_specified_ucm_flag(alsa_card->ucm)) rc = add_controls_and_iodevs_with_ucm( info, alsa_card, card_name, handle); else rc = add_controls_and_iodevs_by_matching( info, blacklist, alsa_card, card_name, handle); if (rc) goto error_bail; configure_echo_reference_dev(alsa_card); n = alsa_card->hctl ? snd_hctl_poll_descriptors_count(alsa_card->hctl) : 0; if (n != 0 && card_has_hctl_jack(alsa_card)) { struct hctl_poll_fd *registered_fd; struct pollfd *pollfds; int i; pollfds = malloc(n * sizeof(*pollfds)); if (pollfds == NULL) { rc = -ENOMEM; goto error_bail; } n = snd_hctl_poll_descriptors(alsa_card->hctl, pollfds, n); for (i = 0; i < n; i++) { registered_fd = calloc(1, sizeof(*registered_fd)); if (registered_fd == NULL) { free(pollfds); rc = -ENOMEM; goto error_bail; } registered_fd->fd = pollfds[i].fd; DL_APPEND(alsa_card->hctl_poll_fds, registered_fd); rc = cras_system_add_select_fd( registered_fd->fd, alsa_control_event_pending, alsa_card); if (rc < 0) { DL_DELETE(alsa_card->hctl_poll_fds, registered_fd); free(pollfds); goto error_bail; } } free(pollfds); } snd_ctl_close(handle); return alsa_card; error_bail: if (handle != NULL) snd_ctl_close(handle); cras_alsa_card_destroy(alsa_card); return NULL; } void cras_alsa_card_destroy(struct cras_alsa_card *alsa_card) { struct iodev_list_node *curr; struct hctl_poll_fd *poll_fd; if (alsa_card == NULL) return; DL_FOREACH(alsa_card->iodevs, curr) { alsa_iodev_destroy(curr->iodev); DL_DELETE(alsa_card->iodevs, curr); free(curr); } DL_FOREACH(alsa_card->hctl_poll_fds, poll_fd) { cras_system_rm_select_fd(poll_fd->fd); DL_DELETE(alsa_card->hctl_poll_fds, poll_fd); free(poll_fd); } if (alsa_card->hctl) snd_hctl_close(alsa_card->hctl); if (alsa_card->ucm) ucm_destroy(alsa_card->ucm); if (alsa_card->mixer) cras_alsa_mixer_destroy(alsa_card->mixer); if (alsa_card->config) cras_card_config_destroy(alsa_card->config); free(alsa_card); } size_t cras_alsa_card_get_index(const struct cras_alsa_card *alsa_card) { assert(alsa_card); return alsa_card->card_index; }