// 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_sealed = false, }; static LIST_HEAD(audiopath_list); struct snd_avirt_audiopath_obj { struct kobject kobj; struct list_head list; struct snd_avirt_audiopath *path; }; static struct kset *snd_avirt_audiopath_kset; static struct kobject *kobj; #define to_audiopath_obj(d) \ container_of(d, struct snd_avirt_audiopath_obj, kobj) #define to_audiopath_attr(a) \ container_of(a, struct snd_avirt_audiopath_attribute, attr) /** * struct snd_avirt_audiopath_attribute - access the attributes of Audio Path * @attr: attributes of an Audio Path * @show: pointer to the show function * @store: pointer to the store function */ struct snd_avirt_audiopath_attribute { struct attribute attr; ssize_t (*show)(struct snd_avirt_audiopath_obj *d, struct snd_avirt_audiopath_attribute *attr, char *buf); ssize_t (*store)(struct snd_avirt_audiopath_obj *d, struct snd_avirt_audiopath_attribute *attr, const char *buf, size_t count); }; /** * audiopath_attr_show - show function of an Audio Path * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer */ static ssize_t audiopath_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct snd_avirt_audiopath_attribute *audiopath_attr; struct snd_avirt_audiopath_obj *audiopath_obj; audiopath_attr = to_audiopath_attr(attr); audiopath_obj = to_audiopath_obj(kobj); if (!audiopath_attr->show) return -EIO; return audiopath_attr->show(audiopath_obj, audiopath_attr, buf); } /** * audiopath_attr_store - attribute store function of Audio Path object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer * @len: length of buffer */ static ssize_t audiopath_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct snd_avirt_audiopath_attribute *audiopath_attr; struct snd_avirt_audiopath_obj *audiopath_obj; audiopath_attr = to_audiopath_attr(attr); audiopath_obj = to_audiopath_obj(kobj); if (!audiopath_attr->store) return -EIO; return audiopath_attr->store(audiopath_obj, audiopath_attr, buf, len); } static const struct sysfs_ops snd_avirt_audiopath_sysfs_ops = { .show = audiopath_attr_show, .store = audiopath_attr_store, }; /** * snd_avirt_audiopath_release - Audio Path release function * @kobj: pointer to Audio Paths's kobject */ static void snd_avirt_audiopath_release(struct kobject *kobj) { struct snd_avirt_audiopath_obj *audiopath_obj = to_audiopath_obj(kobj); kfree(audiopath_obj); } static ssize_t audiopath_name_show(struct snd_avirt_audiopath_obj *audiopath_obj, struct snd_avirt_audiopath_attribute *attr, char *buf) { return sprintf(buf, "%s\n", audiopath_obj->path->name); } static ssize_t audiopath_version_show(struct snd_avirt_audiopath_obj *audiopath_obj, struct snd_avirt_audiopath_attribute *attr, char *buf) { struct snd_avirt_audiopath *audiopath = audiopath_obj->path; return sprintf(buf, "%d.%d.%d\n", audiopath->version[0], audiopath->version[1], audiopath->version[2]); } static struct snd_avirt_audiopath_attribute snd_avirt_audiopath_attrs[] = { __ATTR_RO(audiopath_name), __ATTR_RO(audiopath_version), }; static struct attribute *snd_avirt_audiopath_def_attrs[] = { &snd_avirt_audiopath_attrs[0].attr, &snd_avirt_audiopath_attrs[1].attr, NULL, }; static struct kobj_type snd_avirt_audiopath_ktype = { .sysfs_ops = &snd_avirt_audiopath_sysfs_ops, .release = snd_avirt_audiopath_release, .default_attrs = snd_avirt_audiopath_def_attrs, }; /** * create_snd_avirt_audiopath_obj - creates an Audio Path object * @uid: Unique ID of the Audio Path * * This creates an Audio Path object and assigns the kset and registers * it with sysfs. * @return: Pointer to the Audio Path object or NULL if it failed. */ static struct snd_avirt_audiopath_obj * create_snd_avirt_audiopath_obj(const char *uid) { struct snd_avirt_audiopath_obj *snd_avirt_audiopath; int retval; snd_avirt_audiopath = kzalloc(sizeof(*snd_avirt_audiopath), GFP_KERNEL); if (!snd_avirt_audiopath) return NULL; snd_avirt_audiopath->kobj.kset = snd_avirt_audiopath_kset; retval = kobject_init_and_add(&snd_avirt_audiopath->kobj, &snd_avirt_audiopath_ktype, kobj, "%s", uid); if (retval) { kobject_put(&snd_avirt_audiopath->kobj); return NULL; } kobject_uevent(&snd_avirt_audiopath->kobj, KOBJ_ADD); return snd_avirt_audiopath; } /** * destroy_snd_avirt_audiopath_obj - destroys an Audio Path object * @name: the Audio Path object */ static void destroy_snd_avirt_audiopath_obj(struct snd_avirt_audiopath_obj *p) { kobject_put(&p->kobj); } /** * pcm_private_data_free - callback function to free private data allocated to pcm * @pcm: the PCM with private data */ static void pcm_private_data_free(struct snd_pcm *pcm) { struct snd_avirt_private_data *avirt_private_data; avirt_private_data = (struct snd_avirt_private_data *)pcm->private_data; if (avirt_private_data) { if (avirt_private_data->ap_private_data && avirt_private_data->ap_private_free) avirt_private_data->ap_private_free(pcm); } kfree(pcm->private_data); } struct snd_pcm *snd_avirt_pcm_create(struct snd_avirt_stream *stream) { struct snd_avirt_private_data *avirt_private_data; struct snd_avirt_audiopath *audiopath; struct snd_pcm_ops *pcm_ops, *pcm_ops_ap; struct snd_pcm *pcm; bool playback = false, capture = false; int err; audiopath = snd_avirt_audiopath_get(stream->map); if (!audiopath) { D_ERRORK("Cannot find Audio Path uid: '%s'!", stream->map); return ERR_PTR(-EFAULT); } if (!stream->direction) { pcm_ops_ap = (struct snd_pcm_ops *)audiopath->pcm_playback_ops; playback = true; } else { pcm_ops_ap = (struct snd_pcm_ops *)audiopath->pcm_capture_ops; capture = true; } if (!pcm_ops_ap) { D_ERRORK("No PCM ops for direction '%s' for Audio Path: %s", (stream->direction) ? "capture" : "playback", stream->map); return ERR_PTR(-EFAULT); } pcm_ops = &pcm_ops_avirt; /* Set PCM ops for the Audio Path*/ PCM_OPS_SET(pcm_ops_ap, &pcm_ops, prepare); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, trigger); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, pointer); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, get_time_info); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, fill_silence); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, copy_user); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, copy_kernel); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, mmap); PCM_OPS_SET(pcm_ops_ap, &pcm_ops, ack); /** Special case: loopback */ if (!strcmp(stream->map, "ap_loopback")) { playback = true; capture = true; } 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, pcm_ops); if (capture) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, pcm_ops); pcm->info_flags = 0; strcpy(pcm->name, stream->name); 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_data_free; snd_device_register(core.card, pcm); return pcm; } 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_audiopath_get - retrieves the Audio Path by its UID * @uid: Unique ID for the Audio Path * @return: Corresponding Audio Path */ 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) { //D_INFOK("snd_avirt_audiopath_get, map:%s", uid); if (!strcmp(ap_obj->path->uid, uid)) return ap_obj->path; } return NULL; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_get); /** * 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 = create_snd_avirt_audiopath_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 sealed the streams, configure this AP if (core.streams_sealed) { 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); destroy_snd_avirt_audiopath_obj(audiopath_obj); D_INFOK("Deregistered Audio Path %s", audiopath->uid); return 0; } EXPORT_SYMBOL_GPL(snd_avirt_audiopath_deregister); /** * 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) * @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) { 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); strcpy(stream->name, name); strcpy(stream->map, "none"); stream->channels = 0; stream->direction = direction; stream->device = core.stream_count++; D_INFOK("name: %s device:%d", name, stream->device); return stream; } int snd_avirt_streams_seal(void) { int err = 0, i = 0; struct snd_avirt_audiopath_obj *ap_obj; struct snd_avirt_stream *stream; struct snd_avirt_stream_array stream_array; struct config_item *item; struct list_head *entry; if (core.streams_sealed) { D_ERRORK("streams are already sealed!"); return -1; } list_for_each_entry(ap_obj, &audiopath_list, list) { D_INFOK("configure() AP uid: %s", ap_obj->path->uid); 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) ap_obj->path->configure(core.card, &stream_array); } core.streams_sealed = true; return err; } bool snd_avirt_streams_sealed(void) { return core.streams_sealed; } 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, "avirtcore"); if (IS_ERR(core.dev)) { err = PTR_ERR(core.dev); goto exit_class; } snd_avirt_audiopath_kset = kset_create_and_add("audiopaths", NULL, &core.dev->kobj); if (!snd_avirt_audiopath_kset) { err = -ENOMEM; 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_kset; return 0; exit_kset: kset_unregister(snd_avirt_audiopath_kset); 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); kset_unregister(snd_avirt_audiopath_kset); 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);