summaryrefslogtreecommitdiffstats
path: root/core.c
diff options
context:
space:
mode:
authorMark Farrugia <mark.farrugia@fiberdyne.com.au>2018-10-29 17:43:12 +1100
committerMark Farrugia <mark.farrugia@fiberdyne.com.au>2018-10-29 17:45:19 +1100
commitf55fa7657dd217a71af9198e0bc5754c0cd335ce (patch)
treea89858857e3e0ec7672866d086e12b3c58cf84e3 /core.c
parent96aa81e1426e64875850616e12c3415231ed14f8 (diff)
parent188712f58b309d94d26d7468a792326c464a7eda (diff)
Merge remote-tracking branch 'agl/master' into flounder
Signed-off-by: Mark Farrugia <mark.farrugia@fiberdyne.com.au>
Diffstat (limited to 'core.c')
-rw-r--r--core.c519
1 files changed, 519 insertions, 0 deletions
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..aacc0a1
--- /dev/null
+++ b/core.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AVIRT - ALSA Virtual Soundcard
+ *
+ * Copyright (c) 2010-2018 Fiberdyne Systems Pty Ltd
+ *
+ * core.c - AVIRT core internals
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "core.h"
+
+MODULE_AUTHOR("James O'Shannessy <james.oshannessy@fiberdyne.com.au>");
+MODULE_AUTHOR("Mark Farrugia <mark.farrugia@fiberdyne.com.au>");
+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 SND_AVIRTUAL_DRIVER "snd_avirt"
+
+static struct snd_avirt_core core = {
+ .stream_count = 0,
+ .streams_sealed = false,
+};
+
+struct snd_avirt_coreinfo coreinfo = {
+ .version = { 0, 0, 1 },
+};
+
+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;
+}
+
+static struct snd_pcm *pcm_create(struct snd_avirt_stream *stream)
+{
+ bool playback = false, capture = false;
+ struct snd_pcm *pcm;
+ int err;
+
+ /** Special case: loopback */
+ if (!strcmp(stream->map, "ap_loopback")) {
+ playback = true;
+ capture = true;
+ (&pcm_ops)->copy_user = NULL;
+ } else if (!stream->direction) {
+ playback = true;
+ } else {
+ capture = true;
+ }
+
+ err = snd_pcm_new(core.card, stream->name, stream->device, playback,
+ capture, &pcm);
+
+ if (err < 0)
+ 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);
+
+ return pcm;
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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) {
+ // pr_info("get_ap %s\n", ap_obj->path->uid);
+ if (!strcmp(ap_obj->path->uid, uid))
+ return ap_obj->path;
+ }
+
+ return NULL;
+}
+
+/**
+ * snd_avirt_audiopath_register - register Audio Path with AVIRT
+ * @audiopath: Audio Path to be registered
+ * @core: ALSA virtual driver core info
+ * @return: 0 on success or error code otherwise
+ */
+int snd_avirt_audiopath_register(struct snd_avirt_audiopath *audiopath,
+ struct snd_avirt_coreinfo **info)
+{
+ struct snd_avirt_audiopath_obj *audiopath_obj;
+
+ 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)
+ audiopath->configure(core.card, core.stream_group,
+ core.stream_count);
+
+ *info = &coreinfo;
+
+ 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_count - get the stream count for the given direction
+ * @direction: The direction to get the stream count for
+ * @return: The stream count
+ */
+int snd_avirt_stream_count(unsigned int direction)
+{
+ struct list_head *entry;
+ struct config_item *item;
+ struct snd_avirt_stream *stream;
+ unsigned int count = 0;
+
+ if (direction > 1)
+ return -ERANGE;
+
+ 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 -EFAULT;
+ if (stream->direction == direction)
+ count++;
+ }
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(snd_avirt_stream_count);
+
+/**
+ * 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;
+
+ 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;
+ struct snd_avirt_audiopath_obj *ap_obj;
+ struct snd_avirt_stream *stream;
+ struct config_item *item;
+ struct list_head *entry;
+
+ if (core.streams_sealed) {
+ D_ERRORK("streams are already sealed!");
+ return -1;
+ }
+
+ 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 -EFAULT;
+ stream->pcm = pcm_create(stream);
+ if (IS_ERR_OR_NULL(stream->pcm))
+ return (PTR_ERR(stream->pcm));
+ }
+
+ list_for_each_entry(ap_obj, &audiopath_list, list) {
+ D_INFOK("configure() AP uid: %s", ap_obj->path->uid);
+ ap_obj->path->configure(core.card, core.stream_group,
+ core.stream_count);
+ }
+
+ err = snd_card_register(core.card);
+ if (err < 0) {
+ D_ERRORK("Sound card registration failed!");
+ snd_card_free(core.card);
+ }
+
+ 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;
+}
+
+/**
+ * core_init - Initialize the kernel module
+ */
+static int __init core_init(void)
+{
+ int err;
+
+ D_INFOK("Alsa Virtual Sound Driver avirt-%d.%d.%d", coreinfo.version[0],
+ coreinfo.version[1], coreinfo.version[2]);
+
+ core.avirt_class = class_create(THIS_MODULE, SND_AVIRTUAL_DRIVER);
+ if (IS_ERR(core.avirt_class)) {
+ D_ERRORK("No udev support");
+ return PTR_ERR(core.avirt_class);
+ }
+
+ core.dev = device_create(core.avirt_class, NULL, 0, NULL, "avirtcore");
+ if (IS_ERR(core.dev)) {
+ err = PTR_ERR(core.dev);
+ goto exit_class;
+ }
+
+ // Create the card instance
+ err = snd_card_new(core.dev, SNDRV_DEFAULT_IDX1, "avirt", THIS_MODULE,
+ 0, &core.card);
+ if (err < 0) {
+ D_ERRORK("Failed to create sound card");
+ goto exit_class_container;
+ }
+
+ 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);
+
+ snd_avirt_audiopath_kset =
+ kset_create_and_add("audiopaths", NULL, &core.dev->kobj);
+ if (!snd_avirt_audiopath_kset) {
+ err = -ENOMEM;
+ goto exit_snd_card;
+ }
+
+ err = snd_avirt_configfs_init(&core);
+ if (err < 0)
+ goto exit_snd_card;
+
+ return 0;
+
+exit_snd_card:
+ snd_card_free(core.card);
+exit_class_container:
+ device_destroy(core.avirt_class, 0);
+exit_class:
+ class_destroy(core.avirt_class);
+
+ return err;
+}
+
+/**
+ * core_exit - Destroy the kernel module
+ */
+static void __exit core_exit(void)
+{
+ snd_avirt_configfs_exit(&core);
+
+ kset_unregister(snd_avirt_audiopath_kset);
+ snd_card_free(core.card);
+ device_destroy(core.avirt_class, 0);
+ class_destroy(core.avirt_class);
+}
+
+module_init(core_init);
+module_exit(core_exit);