1 /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
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 asprintf */
8 #endif
9 
10 #include <alsa/asoundlib.h>
11 #include <syslog.h>
12 
13 #include "cras_alsa_card.h"
14 #include "cras_alsa_io.h"
15 #include "cras_alsa_mixer.h"
16 #include "cras_alsa_ucm.h"
17 #include "cras_device_blacklist.h"
18 #include "cras_card_config.h"
19 #include "cras_config.h"
20 #include "cras_iodev.h"
21 #include "cras_iodev_list.h"
22 #include "cras_system_state.h"
23 #include "cras_types.h"
24 #include "cras_util.h"
25 #include "utlist.h"
26 
27 #define MAX_ALSA_CARDS 32 /* Alsa limit on number of cards. */
28 #define MAX_ALSA_PCM_NAME_LENGTH 6 /* Alsa names "hw:XX" + 1 for null. */
29 #define MAX_INI_NAME_LENGTH 63 /* 63 chars + 1 for null where declared. */
30 #define MAX_COUPLED_OUTPUT_SIZE 4
31 
32 struct iodev_list_node {
33 	struct cras_iodev *iodev;
34 	enum CRAS_STREAM_DIRECTION direction;
35 	struct iodev_list_node *prev, *next;
36 };
37 
38 /* Keeps an fd that is registered with system state.  A list of fds must be
39  * kept so that they can be removed when the card is destroyed. */
40 struct hctl_poll_fd {
41 	int fd;
42 	struct hctl_poll_fd *prev, *next;
43 };
44 
45 /* Holds information about each sound card on the system.
46  * name - of the form hw:XX,YY.
47  * card_index - 0 based index, value of "XX" in the name.
48  * iodevs - Input and output devices for this card.
49  * mixer - Controls the mixer controls for this card.
50  * ucm - CRAS use case manager if available.
51  * hctl - ALSA high-level control interface.
52  * hctl_poll_fds - List of fds registered with cras_system_state.
53  * config - Config info for this card, can be NULL if none found.
54  */
55 struct cras_alsa_card {
56 	char name[MAX_ALSA_PCM_NAME_LENGTH];
57 	size_t card_index;
58 	struct iodev_list_node *iodevs;
59 	struct cras_alsa_mixer *mixer;
60 	struct cras_use_case_mgr *ucm;
61 	snd_hctl_t *hctl;
62 	struct hctl_poll_fd *hctl_poll_fds;
63 	struct cras_card_config *config;
64 };
65 
66 /* Creates an iodev for the given device.
67  * Args:
68  *    alsa_card - the alsa_card the device will be added to.
69  *    info - Information about the card type and priority.
70  *    card_name - The name of the card.
71  *    dev_name - The name of the device.
72  *    dev_id - The id string of the device.
73  *    device_index - 0 based index, value of "YY" in "hw:XX,YY".
74  *    direction - Input or output.
75  * Returns:
76  *    Pointer to the created iodev, or NULL on error.
77  *    other negative error code otherwise.
78  */
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)79 struct cras_iodev *create_iodev_for_device(
80 		struct cras_alsa_card *alsa_card,
81 		struct cras_alsa_card_info *info,
82 		const char *card_name,
83 		const char *dev_name,
84 		const char *dev_id,
85 		unsigned device_index,
86 		enum CRAS_STREAM_DIRECTION direction)
87 {
88 	struct iodev_list_node *new_dev;
89 	struct iodev_list_node *node;
90 	int first = 1;
91 
92 	/* Find whether this is the first device in this direction, and
93 	 * avoid duplicate device indexes. */
94 	DL_FOREACH(alsa_card->iodevs, node) {
95 		if (node->direction != direction)
96 			continue;
97 		first = 0;
98 		if (alsa_iodev_index(node->iodev) == device_index) {
99 			syslog(LOG_DEBUG,
100 			       "Skipping duplicate device for %s:%s:%s [%u]",
101 			       card_name, dev_name, dev_id, device_index);
102 			return node->iodev;
103 		}
104 	}
105 
106 	new_dev = calloc(1, sizeof(*new_dev));
107 	if (new_dev == NULL)
108 		return NULL;
109 
110 	new_dev->direction = direction;
111 	new_dev->iodev = alsa_iodev_create(info->card_index,
112 					   card_name,
113 					   device_index,
114 					   dev_name,
115 					   dev_id,
116 					   info->card_type,
117 					   first,
118 					   alsa_card->mixer,
119 					   alsa_card->config,
120 					   alsa_card->ucm,
121 					   alsa_card->hctl,
122 					   direction,
123 					   info->usb_vendor_id,
124 					   info->usb_product_id,
125 					   info->usb_serial_number);
126 	if (new_dev->iodev == NULL) {
127 		syslog(LOG_ERR, "Couldn't create alsa_iodev for %u:%u\n",
128 		       info->card_index, device_index);
129 		free(new_dev);
130 		return NULL;
131 	}
132 
133 	syslog(LOG_DEBUG, "New %s device %u:%d",
134 	       direction == CRAS_STREAM_OUTPUT ? "playback" : "capture",
135 	       info->card_index,
136 	       device_index);
137 
138 	DL_APPEND(alsa_card->iodevs, new_dev);
139 	return new_dev->iodev;
140 }
141 
142 /* Returns non-zero if this card has hctl jacks.
143  */
card_has_hctl_jack(struct cras_alsa_card * alsa_card)144 static int card_has_hctl_jack(struct cras_alsa_card *alsa_card)
145 {
146 	struct iodev_list_node *node;
147 
148 	/* Find the first device that has an hctl jack. */
149 	DL_FOREACH(alsa_card->iodevs, node) {
150 		if (alsa_iodev_has_hctl_jacks(node->iodev))
151 			return 1;
152 	}
153 	return 0;
154 }
155 
156 /* Check if a device should be ignored for this card. Returns non-zero if the
157  * device is in the blacklist and should be ignored.
158  */
should_ignore_dev(struct cras_alsa_card_info * info,struct cras_device_blacklist * blacklist,size_t device_index)159 static int should_ignore_dev(struct cras_alsa_card_info *info,
160 			     struct cras_device_blacklist *blacklist,
161 			     size_t device_index)
162 {
163 	if (info->card_type == ALSA_CARD_TYPE_USB)
164 		return cras_device_blacklist_check(blacklist,
165 						   info->usb_vendor_id,
166 						   info->usb_product_id,
167 						   info->usb_desc_checksum,
168 						   device_index);
169 	return 0;
170 }
171 
172 /* Filters an array of mixer control names. Keep a name if it is
173  * specified in the ucm config. */
filter_controls(struct cras_use_case_mgr * ucm,struct mixer_name * controls)174 static struct mixer_name *filter_controls(struct cras_use_case_mgr *ucm,
175 					  struct mixer_name *controls)
176 {
177 	struct mixer_name *control;
178 	DL_FOREACH(controls, control) {
179 		char *dev = ucm_get_dev_for_mixer(ucm, control->name,
180 						  CRAS_STREAM_OUTPUT);
181 		if (!dev)
182 			DL_DELETE(controls, control);
183 		else
184 			free(dev);
185 	}
186 	return controls;
187 }
188 
189 /* Handles notifications from alsa controls.  Called by main thread when a poll
190  * fd provided by alsa signals there is an event available. */
alsa_control_event_pending(void * arg)191 static void alsa_control_event_pending(void *arg)
192 {
193 	struct cras_alsa_card *card;
194 
195 	card = (struct cras_alsa_card *)arg;
196 	if (card == NULL) {
197 		syslog(LOG_ERR, "Invalid card from control event.");
198 		return;
199 	}
200 
201 	/* handle_events will trigger the callback registered with each control
202 	 * that has changed. */
203 	snd_hctl_handle_events(card->hctl);
204 }
205 
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)206 static int add_controls_and_iodevs_by_matching(
207 		struct cras_alsa_card_info *info,
208 		struct cras_device_blacklist *blacklist,
209 		struct cras_alsa_card *alsa_card,
210 		const char *card_name,
211 		snd_ctl_t *handle)
212 {
213 	struct mixer_name *coupled_controls = NULL;
214 	int dev_idx;
215 	snd_pcm_info_t *dev_info;
216 	struct mixer_name *extra_controls = NULL;
217 	int rc = 0;
218 
219 	snd_pcm_info_alloca(&dev_info);
220 
221 	if (alsa_card->ucm) {
222 		char *extra_main_volume;
223 
224 		/* Filter the extra output mixer names */
225 		extra_controls =
226 			filter_controls(alsa_card->ucm,
227 				mixer_name_add(extra_controls, "IEC958",
228 					       CRAS_STREAM_OUTPUT,
229 					       MIXER_NAME_VOLUME));
230 
231 		/* Get the extra main volume control. */
232 		extra_main_volume = ucm_get_flag(alsa_card->ucm,
233 						 "ExtraMainVolume");
234 		if (extra_main_volume) {
235 			extra_controls =
236 				mixer_name_add(extra_controls,
237 					       extra_main_volume,
238 					       CRAS_STREAM_OUTPUT,
239 					       MIXER_NAME_MAIN_VOLUME);
240 			free(extra_main_volume);
241 		}
242 		mixer_name_dump(extra_controls, "extra controls");
243 
244 		/* Check if coupled controls has been specified for speaker. */
245 		coupled_controls = ucm_get_coupled_mixer_names(
246 					alsa_card->ucm, "Speaker");
247 		mixer_name_dump(coupled_controls, "coupled controls");
248 	}
249 
250 	/* Add controls to mixer by name matching. */
251 	rc = cras_alsa_mixer_add_controls_by_name_matching(
252 			alsa_card->mixer,
253 			extra_controls,
254 			coupled_controls);
255 	if (rc) {
256 		syslog(LOG_ERR, "Fail adding controls to mixer for %s.",
257 		       alsa_card->name);
258 		goto error;
259 	}
260 
261 	/* Go through every device. */
262 	dev_idx = -1;
263 	while (1) {
264 		rc = snd_ctl_pcm_next_device(handle, &dev_idx);
265 		if (rc < 0)
266 			goto error;
267 		if (dev_idx < 0)
268 			break;
269 
270 		snd_pcm_info_set_device(dev_info, dev_idx);
271 		snd_pcm_info_set_subdevice(dev_info, 0);
272 
273 		/* Check for playback devices. */
274 		snd_pcm_info_set_stream(
275 			dev_info, SND_PCM_STREAM_PLAYBACK);
276 		if (snd_ctl_pcm_info(handle, dev_info) == 0 &&
277 		    !should_ignore_dev(info, blacklist, dev_idx)) {
278 			struct cras_iodev *iodev =
279 				create_iodev_for_device(
280 					alsa_card,
281 					info,
282 					card_name,
283 					snd_pcm_info_get_name(dev_info),
284 					snd_pcm_info_get_id(dev_info),
285 					dev_idx,
286 					CRAS_STREAM_OUTPUT);
287 			if (iodev) {
288 				rc = alsa_iodev_legacy_complete_init(
289 					iodev);
290 				if (rc < 0)
291 					goto error;
292 			}
293 		}
294 
295 		/* Check for capture devices. */
296 		snd_pcm_info_set_stream(
297 			dev_info, SND_PCM_STREAM_CAPTURE);
298 		if (snd_ctl_pcm_info(handle, dev_info) == 0) {
299 			struct cras_iodev *iodev =
300 				create_iodev_for_device(
301 					alsa_card,
302 					info,
303 					card_name,
304 					snd_pcm_info_get_name(dev_info),
305 					snd_pcm_info_get_id(dev_info),
306 					dev_idx,
307 					CRAS_STREAM_INPUT);
308 			if (iodev) {
309 				rc = alsa_iodev_legacy_complete_init(
310 					iodev);
311 				if (rc < 0)
312 					goto error;
313 			}
314 		}
315 	}
316 error:
317 	mixer_name_free(coupled_controls);
318 	mixer_name_free(extra_controls);
319 	return rc;
320 }
321 
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)322 static int add_controls_and_iodevs_with_ucm(
323 		struct cras_alsa_card_info *info,
324 		struct cras_alsa_card *alsa_card,
325 		const char *card_name,
326 		snd_ctl_t *handle)
327 {
328 	snd_pcm_info_t *dev_info;
329 	struct iodev_list_node *node;
330 	int rc = 0;
331 	struct ucm_section *section;
332 	struct ucm_section *ucm_sections;
333 
334 	snd_pcm_info_alloca(&dev_info);
335 
336 	/* Get info on the devices specified in the UCM config. */
337 	ucm_sections = ucm_get_sections(alsa_card->ucm);
338 	if (!ucm_sections) {
339 		syslog(LOG_ERR,
340 		       "Could not retrieve any UCM SectionDevice"
341 		       " info for '%s'.", card_name);
342 		rc = -ENOENT;
343 		goto error;
344 	}
345 
346 	/* Create all of the controls first. */
347 	DL_FOREACH(ucm_sections, section) {
348 		rc = cras_alsa_mixer_add_controls_in_section(
349 				alsa_card->mixer, section);
350 		if (rc) {
351 			syslog(LOG_ERR, "Failed adding controls to"
352 					" mixer for '%s:%s'",
353 					card_name,
354 					section->name);
355 			goto error;
356 		}
357 	}
358 
359 	/* Create all of the devices. */
360 	DL_FOREACH(ucm_sections, section) {
361 		snd_pcm_info_set_device(dev_info, section->dev_idx);
362 		snd_pcm_info_set_subdevice(dev_info, 0);
363 		if (section->dir == CRAS_STREAM_OUTPUT)
364 			snd_pcm_info_set_stream(
365 				dev_info, SND_PCM_STREAM_PLAYBACK);
366 		else if (section->dir == CRAS_STREAM_INPUT)
367 			snd_pcm_info_set_stream(
368 				dev_info, SND_PCM_STREAM_CAPTURE);
369 		else {
370 			syslog(LOG_ERR, "Unexpected direction: %d",
371 			       section->dir);
372 			rc = -EINVAL;
373 			goto error;
374 		}
375 
376 		if (snd_ctl_pcm_info(handle, dev_info)) {
377 			syslog(LOG_ERR,
378 			       "Could not get info for device: %s",
379 			       section->name);
380 			continue;
381 		}
382 
383 		create_iodev_for_device(
384 			alsa_card, info, card_name,
385 			snd_pcm_info_get_name(dev_info),
386 			snd_pcm_info_get_id(dev_info),
387 			section->dev_idx, section->dir);
388 	}
389 
390 	/* Setup jacks and controls for the devices. */
391 	DL_FOREACH(ucm_sections, section) {
392 		DL_FOREACH(alsa_card->iodevs, node) {
393 			if (node->direction == section->dir &&
394 			    alsa_iodev_index(node->iodev) ==
395 			    section->dev_idx)
396 				break;
397 		}
398 		if (node) {
399 			rc = alsa_iodev_ucm_add_nodes_and_jacks(
400 				node->iodev, section);
401 			if (rc < 0)
402 				goto error;
403 		}
404 	}
405 
406 	DL_FOREACH(alsa_card->iodevs, node) {
407 		alsa_iodev_ucm_complete_init(node->iodev);
408 	}
409 
410 error:
411 	ucm_section_free_list(ucm_sections);
412 	return rc;
413 }
414 
configure_echo_reference_dev(struct cras_alsa_card * alsa_card)415 static void configure_echo_reference_dev(struct cras_alsa_card *alsa_card)
416 {
417 	struct iodev_list_node *dev_node, *echo_ref_node;
418 	const char *echo_ref_name;
419 
420 	if (!alsa_card->ucm)
421 		return;
422 
423 	DL_FOREACH(alsa_card->iodevs, dev_node) {
424 		if (!dev_node->iodev->nodes)
425 			continue;
426 
427 		echo_ref_name = ucm_get_echo_reference_dev_name_for_dev(
428 				alsa_card->ucm,
429 				dev_node->iodev->nodes->name);
430 		if (!echo_ref_name)
431 			continue;
432 		DL_FOREACH(alsa_card->iodevs, echo_ref_node) {
433 			if (echo_ref_node->iodev->nodes == NULL)
434 				continue;
435 			if (!strcmp(echo_ref_name,
436 				    echo_ref_node->iodev->nodes->name))
437 				break;
438 		}
439 		if (echo_ref_node)
440 			dev_node->iodev->echo_reference_dev =
441 					echo_ref_node->iodev;
442 		else
443 			syslog(LOG_ERR,
444 			       "Echo ref dev %s doesn't exist on card %s",
445 			       echo_ref_name, alsa_card->name);
446 		free((void *)echo_ref_name);
447 	}
448 }
449 
450 /*
451  * Exported Interface.
452  */
453 
cras_alsa_card_create(struct cras_alsa_card_info * info,const char * device_config_dir,struct cras_device_blacklist * blacklist,const char * ucm_suffix)454 struct cras_alsa_card *cras_alsa_card_create(
455 		struct cras_alsa_card_info *info,
456 		const char *device_config_dir,
457 		struct cras_device_blacklist *blacklist,
458 		const char *ucm_suffix)
459 {
460 	snd_ctl_t *handle = NULL;
461 	int rc, n;
462 	snd_ctl_card_info_t *card_info;
463 	const char *card_name;
464 	struct cras_alsa_card *alsa_card;
465 
466 	if (info->card_index >= MAX_ALSA_CARDS) {
467 		syslog(LOG_ERR,
468 		       "Invalid alsa card index %u",
469 		       info->card_index);
470 		return NULL;
471 	}
472 
473 	snd_ctl_card_info_alloca(&card_info);
474 
475 	alsa_card = calloc(1, sizeof(*alsa_card));
476 	if (alsa_card == NULL)
477 		return NULL;
478 	alsa_card->card_index = info->card_index;
479 
480 	snprintf(alsa_card->name,
481 		 MAX_ALSA_PCM_NAME_LENGTH,
482 		 "hw:%u",
483 		 info->card_index);
484 
485 	rc = snd_ctl_open(&handle, alsa_card->name, 0);
486 	if (rc < 0) {
487 		syslog(LOG_ERR, "Fail opening control %s.", alsa_card->name);
488 		goto error_bail;
489 	}
490 
491 	rc = snd_ctl_card_info(handle, card_info);
492 	if (rc < 0) {
493 		syslog(LOG_ERR, "Error getting card info.");
494 		goto error_bail;
495 	}
496 
497 	card_name = snd_ctl_card_info_get_name(card_info);
498 	if (card_name == NULL) {
499 		syslog(LOG_ERR, "Error getting card name.");
500 		goto error_bail;
501 	}
502 
503 	/* Read config file for this card if it exists. */
504 	alsa_card->config = cras_card_config_create(device_config_dir,
505 						    card_name);
506 	if (alsa_card->config == NULL)
507 		syslog(LOG_DEBUG, "No config file for %s", alsa_card->name);
508 
509 	/* Create a use case manager if a configuration is available. */
510 	if (ucm_suffix) {
511 		char *ucm_name;
512 		if (asprintf(&ucm_name, "%s.%s", card_name, ucm_suffix) == -1) {
513 			syslog(LOG_ERR, "Error creating ucm name");
514 			goto error_bail;
515 		}
516 		alsa_card->ucm = ucm_create(ucm_name);
517 		syslog(LOG_INFO, "Card %s (%s) has UCM: %s",
518 		       alsa_card->name, ucm_name,
519 		       alsa_card->ucm ? "yes" : "no");
520 		free(ucm_name);
521 	} else {
522 		alsa_card->ucm = ucm_create(card_name);
523 		syslog(LOG_INFO, "Card %s (%s) has UCM: %s",
524 		       alsa_card->name, card_name,
525 		       alsa_card->ucm ? "yes" : "no");
526 	}
527 
528 	rc = snd_hctl_open(&alsa_card->hctl,
529 			   alsa_card->name,
530 			   SND_CTL_NONBLOCK);
531 	if (rc < 0) {
532 		syslog(LOG_DEBUG,
533 		       "failed to get hctl for %s", alsa_card->name);
534 		alsa_card->hctl = NULL;
535 	} else {
536 		rc = snd_hctl_nonblock(alsa_card->hctl, 1);
537 		if (rc < 0) {
538 			syslog(LOG_ERR,
539 			    "failed to nonblock hctl for %s", alsa_card->name);
540 			goto error_bail;
541 		}
542 
543 		rc = snd_hctl_load(alsa_card->hctl);
544 		if (rc < 0) {
545 			syslog(LOG_ERR,
546 			       "failed to load hctl for %s", alsa_card->name);
547 			goto error_bail;
548 		}
549 	}
550 
551 	/* Create one mixer per card. */
552 	alsa_card->mixer = cras_alsa_mixer_create(alsa_card->name);
553 
554 	if (alsa_card->mixer == NULL) {
555 		syslog(LOG_ERR, "Fail opening mixer for %s.", alsa_card->name);
556 		goto error_bail;
557 	}
558 
559 	if (alsa_card->ucm && ucm_has_fully_specified_ucm_flag(alsa_card->ucm))
560 		rc = add_controls_and_iodevs_with_ucm(
561 				info, alsa_card, card_name, handle);
562 	else
563 		rc = add_controls_and_iodevs_by_matching(
564 				info, blacklist, alsa_card, card_name, handle);
565 	if (rc)
566 		goto error_bail;
567 
568 	configure_echo_reference_dev(alsa_card);
569 
570 	n = alsa_card->hctl ?
571 		snd_hctl_poll_descriptors_count(alsa_card->hctl) : 0;
572 	if (n != 0 && card_has_hctl_jack(alsa_card)) {
573 		struct hctl_poll_fd *registered_fd;
574 		struct pollfd *pollfds;
575 		int i;
576 
577 		pollfds = malloc(n * sizeof(*pollfds));
578 		if (pollfds == NULL) {
579 			rc = -ENOMEM;
580 			goto error_bail;
581 		}
582 
583 		n = snd_hctl_poll_descriptors(alsa_card->hctl, pollfds, n);
584 		for (i = 0; i < n; i++) {
585 			registered_fd = calloc(1, sizeof(*registered_fd));
586 			if (registered_fd == NULL) {
587 				free(pollfds);
588 				rc = -ENOMEM;
589 				goto error_bail;
590 			}
591 			registered_fd->fd = pollfds[i].fd;
592 			DL_APPEND(alsa_card->hctl_poll_fds, registered_fd);
593 			rc = cras_system_add_select_fd(
594 					registered_fd->fd,
595 					alsa_control_event_pending,
596 					alsa_card);
597 			if (rc < 0) {
598 				DL_DELETE(alsa_card->hctl_poll_fds,
599 					  registered_fd);
600 				free(pollfds);
601 				goto error_bail;
602 			}
603 		}
604 		free(pollfds);
605 	}
606 
607 	snd_ctl_close(handle);
608 	return alsa_card;
609 
610 error_bail:
611 	if (handle != NULL)
612 		snd_ctl_close(handle);
613 	cras_alsa_card_destroy(alsa_card);
614 	return NULL;
615 }
616 
cras_alsa_card_destroy(struct cras_alsa_card * alsa_card)617 void cras_alsa_card_destroy(struct cras_alsa_card *alsa_card)
618 {
619 	struct iodev_list_node *curr;
620 	struct hctl_poll_fd *poll_fd;
621 
622 	if (alsa_card == NULL)
623 		return;
624 
625 	DL_FOREACH(alsa_card->iodevs, curr) {
626 		alsa_iodev_destroy(curr->iodev);
627 		DL_DELETE(alsa_card->iodevs, curr);
628 		free(curr);
629 	}
630 	DL_FOREACH(alsa_card->hctl_poll_fds, poll_fd) {
631 		cras_system_rm_select_fd(poll_fd->fd);
632 		DL_DELETE(alsa_card->hctl_poll_fds, poll_fd);
633 		free(poll_fd);
634 	}
635 	if (alsa_card->hctl)
636 		snd_hctl_close(alsa_card->hctl);
637 	if (alsa_card->ucm)
638 		ucm_destroy(alsa_card->ucm);
639 	if (alsa_card->mixer)
640 		cras_alsa_mixer_destroy(alsa_card->mixer);
641 	if (alsa_card->config)
642 		cras_card_config_destroy(alsa_card->config);
643 	free(alsa_card);
644 }
645 
cras_alsa_card_get_index(const struct cras_alsa_card * alsa_card)646 size_t cras_alsa_card_get_index(const struct cras_alsa_card *alsa_card)
647 {
648 	assert(alsa_card);
649 	return alsa_card->card_index;
650 }
651