// SPDX-License-Identifier: GPL-2.0 /* * AVIRT - ALSA Virtual Soundcard * * Copyright (c) 2010-2018 Fiberdyne Systems Pty Ltd * * core.c - AVIRT core internals */ #include #include #include #include #include #include "core.h" MODULE_AUTHOR("James O'Shannessy "); MODULE_AUTHOR("Mark Farrugia "); MODULE_DESCRIPTION("ALSA virtual, dynamic soundcard"); MODULE_LICENSE("GPL v2"); #define D_LOGNAME "core" #define D_INFOK(fmt, args...) DINFO(D_LOGNAME, fmt, ##args) #define D_PRINTK(fmt, args...) DDEBUG(D_LOGNAME, fmt, ##args) #define D_ERRORK(fmt, args...) DERROR(D_LOGNAME, fmt, ##args) #define PCM_OPS_SET(pcm_ops_ap, pcm_ops, cb) \ ((pcm_ops_ap->cb) ? ((*pcm_ops)->cb = pcm_ops_ap->cb) : \ ((*pcm_ops)->cb = NULL)); #define SND_AVIRT_DRIVER "snd_avirt" static struct snd_avirt_core core = { .version = { 0, 0, 1 }, .stream_count = 0, .streams_configured = false, }; static LIST_HEAD(audiopath_list); /** * pcm_private_free - callback function to free private data allocated to pcm * @pcm: the PCM object */ static void pcm_private_free(struct snd_pcm *pcm) { struct snd_avirt_private_data *avirt_private_data; /* Free Audio Path private data */ avirt_private_data = (struct snd_avirt_private_data *)pcm->private_data; if (avirt_private_data) { if (avirt_private_data->ap_private_data[0] && avirt_private_data->ap_private_free) avirt_private_data->ap_private_free(pcm); } kfree(pcm->private_data); } static struct snd_pcm *snd_avirt_pcm_create(struct snd_avirt_stream *stream) { struct snd_avirt_private_data *avirt_private_data; struct snd_pcm *pcm; bool playback = false, capture = false; int err; if (!stream->direction) playback = true; else capture = true; /** Special case: loopback */ if (!strcmp(stream->map, "ap_loopback")) { playback = true; capture = true; } if (stream->internal) { err = snd_pcm_new_internal(core.card, stream->name, stream->device, playback, capture, &pcm); } else { err = snd_pcm_new(core.card, stream->name, stream->device, playback, capture, &pcm); } if (err < 0) { D_ERRORK("Failed to create PCM device for stream: '%s'", stream->name); return ERR_PTR(err); } /** Register driver callbacks */ if (playback) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, stream->pcm_ops); if (capture) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, stream->pcm_ops); pcm->info_flags = 0; strncpy(pcm->name, stream->name, MAX_NAME_LEN); avirt_private_data = kzalloc(sizeof(*avirt_private_data), GFP_KERNEL); pcm->private_data = avirt_private_data; // Set the private free function for the private user data pcm->private_free = pcm_private_free; snd_device_register(core.card, pcm); return pcm; } void snd_avirt_stream_try_destroy(struct snd_avirt_stream *stream) { unsigned long _flags; struct snd_pcm_substream *substream = stream->pcm->streams[stream->direction].substream; snd_pcm_stream_lock_irqsave(substream, _flags); if (substream->runtime) { if (snd_pcm_running(substream)) { if (0 != snd_pcm_stop(substream, SNDRV_PCM_STATE_SUSPENDED)) D_ERRORK("Could not stop PCM '%s'", stream->name); } } snd_pcm_stream_unlock_irqrestore(substream, _flags); snd_device_free(core.card, stream->pcm); kfree(stream->pcm_ops); kfree(stream); core.stream_count--; } static struct snd_avirt_route *snd_avirt_route_get(const char *uid) { struct list_head *entry; struct config_item *item; struct snd_avirt_route *route; list_for_each (entry, &core.route_group->cg_children) { item = container_of(entry, struct config_item, ci_entry); route = snd_avirt_route_from_config_item(item); if (!strcmp(route->uid, uid)) return route; } return NULL; } /** * int snd_avirt_route_try_complete - Set up remaining parameters for a route. * Channels, sink, and source Audio Paths * should be set when calling this function. * @stream: The route to attempt to finalize parameters for. * @return: 0 on success, negative ERRNO on failure */ int snd_avirt_route_try_complete(struct snd_avirt_route *route) { return 0; } /** * int snd_avirt_stream_try_complete - Set up remaining parameters for a stream. * Channels and map should be set when * calling this function. * @stream: The stream to attempt to finalize parameters for. * @return: 0 on success, negative ERRNO on failure */ int snd_avirt_stream_try_complete(struct snd_avirt_stream *stream) { struct snd_avirt_audiopath *audiopath; struct snd_avirt_route *route; struct snd_pcm_ops *pcm_ops_ap; if (snd_avirt_streams_configured()) return -EPERM; if ((stream->channels == 0) || (!strcmp(stream->map, "none"))) return -EPERM; audiopath = snd_avirt_audiopath_get(stream->map); if (!audiopath) { D_ERRORK("Cannot find Audio Path uid: '%s'!", stream->map); } /* Check for any routes that have been created for this stream */ route = snd_avirt_route_get(stream->name); if (route) { if (audiopath == route->endpoint_ap[SND_AVIRT_ROUTE_SOURCE]) route->endpoint_stream[SND_AVIRT_ROUTE_SOURCE] = stream; else if (audiopath == route->endpoint_ap[SND_AVIRT_ROUTE_SINK]) route->endpoint_stream[SND_AVIRT_ROUTE_SINK] = stream; else { D_INFOK("Cannot set route. Audio Path not compatible"); return -EPERM; } stream->route = route; } /* Set up PCM ops */ if (!stream->direction) pcm_ops_ap = (struct snd_pcm_ops *)audiopath->pcm_playback_ops; else pcm_ops_ap = (struct snd_pcm_ops *)audiopath->pcm_capture_ops; if (!pcm_ops_ap) { D_ERRORK("No PCM ops for direction '%s' for Audio Path: %s", (stream->direction) ? "capture" : "playback", stream->map); return -EFAULT; } /* Set PCM ops for the Audio Path*/ PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, pointer); PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, get_time_info); PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, silence); PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, copy); PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, mmap); PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, ack); /* If not created, create the PCM device now */ if (!stream->pcm) { stream->pcm = snd_avirt_pcm_create(stream); if (IS_ERR_OR_NULL(stream->pcm)) return -EFAULT; } return 0; } /** * snd_avirt_streams_get - Get AVIRT streams for a given Audio Path map * @map: The Audio Path UID whose streams to find. * @stream_array: To be populated with streams. * @return: The number of streams found for the Audio Path. */ static int snd_avirt_streams_get(const char *map, struct snd_avirt_stream_array *stream_array) { struct list_head *entry; struct config_item *item; struct snd_avirt_stream *stream; list_for_each (entry, &core.stream_group->cg_children) { item = container_of(entry, struct config_item, ci_entry); stream = snd_avirt_stream_from_config_item(item); if (!strcmp(map, stream->map)) { stream_array->streams[stream_array->count++] = stream; } } return stream_array->count; } /** * snd_avirt_route_endpoint_pos - get route endpoint position for Audio Path * @pcm: The PCM whose route to inspect. * @ap_uid: The Audio Path UID to get * @endpoint: The route position (SND_AVIRT_ROUTE_SOURCE or SND_AVIRT_ROUTE_SINK) * of the Audio Path in it's route (if any). * @return: 0 if an Audio Path is found in the route, -1 if there is no route, * or -2 otherwise. */ int snd_avirt_route_endpoint_pos(struct snd_pcm *pcm, const char *ap_uid, snd_avirt_route_endpoint *endpoint) { struct snd_avirt_audiopath *endpoint_ap; struct snd_avirt_stream *stream; int i; stream = snd_avirt_stream_find_by_device(pcm->device); if (IS_ERR_VALUE(stream) || !stream) goto exit_err; if (!stream->route) return -1; for (i = 0; i < 2; i++) { endpoint_ap = stream->route->endpoint_ap[i]; if (endpoint_ap) if (!strcmp(endpoint_ap->uid, ap_uid)) { *endpoint = i; return 0; } } exit_err: D_ERRORK("Could not find Audio Path '%s' in route '%s'", ap_uid, stream->route->uid); return -2; } /** * snd_avirt_route_endpoint_copy - get endpoint copy function for a given * Audio Path's source/sink. * @ap: The Audio Path whose endpoint copy function to find. * @endpoint: The endpoint (SND_AVIRT_ROUTE_SOURCE or SND_AVIRT_ROUTE_SINK). * @return: A snd_pcm_copy_kernel function pointer that can be used to either: * 1. Source PCM data into the Audio Path, or, * 2. Sink PCM data out of the Audio Path. * If no Audio Path endpoint is routed for 'endpoint', NULL is returned. */ snd_pcm_copy_kernel snd_avirt_route_endpoint_copy(struct snd_pcm_substream *substream, snd_avirt_route_endpoint endpoint) { struct snd_avirt_audiopath *endpoint_ap; struct snd_avirt_stream *stream; if (endpoint < 0 || endpoint > 1) { D_ERRORK("Route endpoint must be 0 or 1"); return NULL; } stream = snd_avirt_stream_find_by_device(substream->pcm->device); if (IS_ERR_VALUE(stream) || !stream) return NULL; if (!stream->route) return NULL; endpoint_ap = stream->route->endpoint_ap[endpoint]; if (!endpoint_ap) return NULL; switch (endpoint) { case SND_AVIRT_ROUTE_SOURCE: return endpoint_ap->pcm_capture_ops->copy_kernel; case SND_AVIRT_ROUTE_SINK: return endpoint_ap->pcm_playback_ops->copy_kernel; } return NULL; } EXPORT_SYMBOL_GPL(snd_avirt_route_endpoint_copy); /** * snd_avirt_route_endpoint_trigger - Trigger an Audio Path's endpoint * (sink/source). * @uid: The Audio Path whose endpoint trigger function to call. * @endpoint: The endpoint (SND_AVIRT_ROUTE_SOURCE or SND_AVIRT_ROUTE_SINK). * @return: 0 on success or -1 on failure. */ int snd_avirt_route_endpoint_trigger(struct snd_pcm_substream *substream, snd_avirt_route_endpoint endpoint) { struct snd_avirt_audiopath *endpoint_ap; struct snd_avirt_stream *stream; if (endpoint < 0 || endpoint > 1) { D_ERRORK("Route endpoint must be 0 or 1"); return -1; } stream = snd_avirt_stream_find_by_device(substream->pcm->device); if (IS_ERR_VALUE(stream) || !stream) return -1; if (!stream->route) return -1; endpoint_ap = stream->route->endpoint_ap[endpoint]; if (!endpoint_ap) return -1; endpoint_ap->pcm_exttrigger(); return 0; } EXPORT_SYMBOL_GPL(snd_avirt_route_endpoint_trigger); /** * snd_avirt_audiopath_get - get Audio Path by it's UID * @uid: The Audio Path UID to get * @return: The Audio Path if it exists, NULL otherwise. */ struct snd_avirt_audiopath *snd_avirt_audiopath_get(const char *uid) { struct snd_avirt_audiopath_obj *ap_obj; list_for_each_entry (ap_obj, &audiopath_list, list) { if (!strcmp(ap_obj->path->uid, uid)) return ap_obj->path; } return NULL; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_get); /* * snd_avirt_audiopath_set_private_data - set PCM private data for an Audio Path * @ap: The Audio Path whose private data to set. * @pcm: The PCM where the private data is stored. * @ap_private_data: The value to set to the private data. * @return: 0 on success, -1 on failure. */ int snd_avirt_audiopath_set_private_data(struct snd_avirt_audiopath *ap, struct snd_pcm *pcm, void *ap_private_data) { int err = 0; snd_avirt_route_endpoint endpoint = SND_AVIRT_ROUTE_SOURCE; struct snd_avirt_stream *stream; struct snd_avirt_private_data *avirt_private_data; stream = snd_avirt_stream_find_by_device(pcm->device); if (IS_ERR_VALUE(stream) || !stream) goto exit_err; err = snd_avirt_route_endpoint_pos(pcm, ap->uid, &endpoint); if (err == -2) goto exit_err; if (stream->internal && stream->route) pcm = stream->route->endpoint_stream[!endpoint]->pcm; avirt_private_data = pcm->private_data; if (!avirt_private_data) goto exit_err; avirt_private_data->ap_private_data[endpoint] = ap_private_data; return 0; exit_err: D_ERRORK("Error setting private data for ap:%s, stream:%s, endpoint:%d", ap->uid, pcm->name, endpoint); return -1; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_set_private_data); /* * snd_avirt_audiopath_get_private_data - get PCM private data for an Audio Path * @ap: The Audio Path whose private data to get. * @pcm: The PCM where the private data is stored. * @return: The value assigned to the private data. */ void *snd_avirt_audiopath_get_private_data(struct snd_avirt_audiopath *ap, struct snd_pcm *pcm) { int err = 0; snd_avirt_route_endpoint endpoint = SND_AVIRT_ROUTE_SOURCE; struct snd_avirt_stream *stream; struct snd_avirt_private_data *avirt_private_data; stream = snd_avirt_stream_find_by_device(pcm->device); if (IS_ERR_VALUE(stream) || !stream) return NULL; err = snd_avirt_route_endpoint_pos(pcm, ap->uid, &endpoint); if (err == -2) goto exit_err; if (stream->internal && stream->route) pcm = stream->route->endpoint_stream[!endpoint]->pcm; avirt_private_data = pcm->private_data; if (!avirt_private_data) goto exit_err; return avirt_private_data->ap_private_data[endpoint]; exit_err: D_ERRORK("Error getting private data for ap:%s, stream:%s, endpoint:%d", ap->uid, pcm->name, endpoint); return NULL; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_get_private_data); /** * snd_avirt_audiopath_register - register Audio Path with AVIRT * @audiopath: Audio Path to be registered * @return: 0 on success or error code otherwise */ int snd_avirt_audiopath_register(struct snd_avirt_audiopath *audiopath) { struct snd_avirt_audiopath_obj *audiopath_obj; struct snd_avirt_stream_array stream_array; if (!audiopath) { D_ERRORK("Audio Path is NULL!"); return -EINVAL; } audiopath_obj = snd_avirt_audiopath_create_obj(audiopath->uid); if (!audiopath_obj) { D_INFOK("Failed to alloc driver object"); return -ENOMEM; } audiopath_obj->path = audiopath; audiopath->context = audiopath_obj; D_INFOK("Registered new Audio Path: %s", audiopath->name); list_add_tail(&audiopath_obj->list, &audiopath_list); // If we have already configured the streams, configure this AP if (core.streams_configured) { stream_array.count = 0; if (snd_avirt_streams_get(audiopath->uid, &stream_array) > 0) audiopath->configure(core.card, &stream_array); } return 0; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_register); /** * snd_avirt_audiopath_deregister - deregister Audio Path with AVIRT * @audiopath: Audio Path to be deregistered * @return: 0 on success or error code otherwise */ int snd_avirt_audiopath_deregister(struct snd_avirt_audiopath *audiopath) { struct snd_avirt_audiopath_obj *audiopath_obj; // Check if audio path is registered if (!audiopath) { D_ERRORK("Bad Audio Path Driver"); return -EINVAL; } audiopath_obj = audiopath->context; if (!audiopath_obj) { D_INFOK("driver not registered"); return -EINVAL; } list_del(&audiopath_obj->list); snd_avirt_audiopath_destroy_obj(audiopath_obj); D_INFOK("Deregistered Audio Path %s", audiopath->uid); return 0; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_deregister); /** * snd_avirt_route_create - Create audio route * @uid: The unique ID designated to the audio route. * @direction: The PCM direction (SNDRV_PCM_STREAM_PLAYBACK or * SNDRV_PCM_STREAM_CAPTURE) * @return: The newly created audio route if successful, or an error pointer */ struct snd_avirt_route *snd_avirt_route_create(const char *uid, int direction) { struct snd_avirt_route *route; route = kzalloc(sizeof(*route), GFP_KERNEL); if (!route) return ERR_PTR(-ENOMEM); strncpy(route->uid, uid, MAX_NAME_LEN); route->channels = 0; route->direction = direction; return route; } /** * snd_avirt_stream_create - Create audio stream, including it's ALSA PCM device * @name: The name designated to the audio stream * @direction: The PCM direction (SNDRV_PCM_STREAM_PLAYBACK or * SNDRV_PCM_STREAM_CAPTURE) * @internal: Whether the PCM should be internal or not * @return: The newly created audio stream if successful, or an error pointer */ struct snd_avirt_stream *snd_avirt_stream_create(const char *name, int direction, bool internal) { struct snd_avirt_stream *stream; if ((core.stream_count + 1) > MAX_STREAMS) { D_ERRORK("Cannot add stream %s, PCMs are maxxed out!", name); return ERR_PTR(-EPERM); } stream = kzalloc(sizeof(*stream), GFP_KERNEL); if (!stream) return ERR_PTR(-ENOMEM); strncpy(stream->name, name, MAX_NAME_LEN); strncpy(stream->map, "none", MAX_NAME_LEN); stream->channels = 0; stream->direction = direction; stream->internal = internal; stream->device = core.stream_count++; /* Initialize PCM ops table for this stream. * Will be populated once map is known */ stream->pcm_ops = kzalloc(sizeof(struct snd_pcm_ops), GFP_KERNEL); if (!stream->pcm_ops) { D_ERRORK("Failed to allocate PCM ops table"); return ERR_PTR(-EFAULT); } memcpy(stream->pcm_ops, &pcm_ops_avirt, sizeof(struct snd_pcm_ops)); if (stream->internal) { D_INFOK("name:%s device:%d internal:%d", name, stream->device, stream->internal); } else { D_INFOK("name:%s device:%d", name, stream->device); } return stream; } int snd_avirt_streams_configure(void) { int err = 0, i = 0; struct snd_avirt_audiopath_obj *ap_obj; struct snd_avirt_stream_array stream_array; if (core.streams_configured) { D_ERRORK("streams are already configured!"); return -1; } list_for_each_entry (ap_obj, &audiopath_list, list) { for (i = 0; i < MAX_STREAMS; i++) stream_array.streams[i] = NULL; stream_array.count = 0; if (snd_avirt_streams_get(ap_obj->path->uid, &stream_array) <= 0) continue; if (!ap_obj->path->configure) { D_ERRORK("Cannot do 'configure' for AP: %s", ap_obj->path->uid); return -EFAULT; } D_INFOK("Do 'configure' for AP: %s streams:%d", ap_obj->path->uid, stream_array.count); ap_obj->path->configure(core.card, &stream_array); } core.streams_configured = true; return err; } int snd_avirt_streams_unconfigure(void) { int i = 0, err = 0; struct snd_avirt_audiopath_obj *ap_obj; struct snd_avirt_stream_array stream_array; if (!core.streams_configured) { D_ERRORK("streams are already unconfigured!"); return -1; } list_for_each_entry (ap_obj, &audiopath_list, list) { if (!ap_obj->path->unconfigure) { D_ERRORK("Cannot do 'unconfigure' for AP: %s", ap_obj->path->uid); return -EFAULT; } D_INFOK("Do 'unconfigure' for AP: %s", ap_obj->path->uid); ap_obj->path->unconfigure(); } core.streams_configured = false; return 0; } bool snd_avirt_streams_configured(void) { return core.streams_configured; } struct snd_avirt_stream *snd_avirt_stream_find_by_device(unsigned int device) { struct snd_avirt_stream *stream; struct config_item *item; struct list_head *entry; if (device >= core.stream_count) { D_ERRORK("Stream device number is larger than stream count"); return ERR_PTR(-EINVAL); } list_for_each (entry, &core.stream_group->cg_children) { item = container_of(entry, struct config_item, ci_entry); stream = snd_avirt_stream_from_config_item(item); if (!stream) return ERR_PTR(-EFAULT); if (stream->device == device) return stream; } return NULL; } static int snd_avirt_core_probe(struct platform_device *devptr) { int err; // Create the card instance err = snd_card_new(&devptr->dev, SNDRV_DEFAULT_IDX1, "avirt", THIS_MODULE, 0, &core.card); if (err < 0) { D_ERRORK("Failed to create sound card"); return err; } strncpy(core.card->driver, "avirt-alsa-dev", 16); strncpy(core.card->shortname, "avirt", 32); strncpy(core.card->longname, "A virtual sound card driver for ALSA", 80); return 0; } static int snd_avirt_core_remove(struct platform_device *devptr) { snd_card_free(core.card); return 0; } static struct platform_driver snd_avirt_driver = { .probe = snd_avirt_core_probe, .remove = snd_avirt_core_remove, .driver = { .name = SND_AVIRT_DRIVER, }, }; /** * snd_avirt_core_init - Initialize the kernel module */ static int __init snd_avirt_core_init(void) { int err; D_INFOK("Alsa Virtual Sound Driver avirt-%d.%d.%d", core.version[0], core.version[1], core.version[2]); err = platform_driver_register(&snd_avirt_driver); if (err < 0) return err; core.plat_dev = platform_device_register_simple(SND_AVIRT_DRIVER, 0, NULL, 0); if (IS_ERR(core.plat_dev)) { err = PTR_ERR(core.plat_dev); goto exit_platform_device; } core.class = class_create(THIS_MODULE, SND_AVIRT_DRIVER); if (IS_ERR(core.class)) { D_ERRORK("No udev support"); return PTR_ERR(core.class); } core.dev = device_create(core.class, NULL, 0, NULL, "core"); if (IS_ERR(core.dev)) { err = PTR_ERR(core.dev); goto exit_class; } err = snd_avirt_sysfs_init(&core); if (err < 0) goto exit_class_container; err = snd_card_register(core.card); if (err < 0) { D_ERRORK("Sound card registration failed!"); snd_card_free(core.card); } err = snd_avirt_configfs_init(&core); if (err < 0) goto exit_sysfs; return 0; exit_sysfs: snd_avirt_sysfs_exit(&core); exit_class_container: device_destroy(core.class, 0); exit_class: class_destroy(core.class); exit_platform_device: platform_device_unregister(core.plat_dev); platform_driver_unregister(&snd_avirt_driver); return err; } /** * snd_avirt_core_exit - Destroy the kernel module */ static void __exit snd_avirt_core_exit(void) { snd_avirt_configfs_exit(&core); snd_avirt_sysfs_exit(&core); device_destroy(core.class, 0); class_destroy(core.class); platform_device_unregister(core.plat_dev); platform_driver_unregister(&snd_avirt_driver); } module_init(snd_avirt_core_init); module_exit(snd_avirt_core_exit);