aboutsummaryrefslogtreecommitdiffstats
path: root/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'core.c')
-rw-r--r--core.c495
1 files changed, 495 insertions, 0 deletions
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..d0a24ec
--- /dev/null
+++ b/core.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ALSA Virtual Soundcard
+ *
+ * core.c - Implementation of core module for virtual ALSA card
+ *
+ * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include "avirt/core.h"
+#include "alsa.h"
+
+MODULE_AUTHOR("JOSHANNE <james.oshannessy@fiberdyne.com.au>");
+MODULE_AUTHOR("MFARRUGI <mark.farrugia@fiberdyne.com.au>");
+MODULE_DESCRIPTION("A configurable virtual soundcard");
+MODULE_LICENSE("GPL v2");
+
+#define SND_AVIRTUAL_DRIVER "snd_avirt"
+#define MAX_PCM_DEVS 8
+#define MAX_AUDIOPATHS 4
+#define DEFAULT_FILE_PERMS 0644
+
+/* Number of playback devices to create (max = MAX_PCM_DEVS) */
+static unsigned playback_num = 0;
+/* Number of capture devices to create (max = MAX_PCM_DEVS) */
+static unsigned capture_num = 0;
+/* Names per playback device */
+static char *playback_names[MAX_PCM_DEVS];
+/* Names per capture device */
+static char *capture_names[MAX_PCM_DEVS];
+/* Channels per playback device */
+static unsigned playback_chans[MAX_PCM_DEVS];
+/* Channels per capture device */
+static unsigned capture_chans[MAX_PCM_DEVS];
+
+module_param(playback_num, int, DEFAULT_FILE_PERMS);
+MODULE_PARM_DESC(playback_num,
+ "Number of playback devices to create (max = MAX_PCM_DEVS)");
+module_param(capture_num, int, DEFAULT_FILE_PERMS);
+MODULE_PARM_DESC(capture_num,
+ "Number of capture devices to create (max = MAX_PCM_DEVS)");
+module_param_array(playback_names, charp, NULL, DEFAULT_FILE_PERMS);
+MODULE_PARM_DESC(playback_names, "Names per playback device");
+module_param_array(capture_names, charp, NULL, DEFAULT_FILE_PERMS);
+MODULE_PARM_DESC(capture_names, "Names per capture device");
+module_param_array(playback_chans, int, NULL, DEFAULT_FILE_PERMS);
+MODULE_PARM_DESC(playback_chans, "Channels per playback device");
+module_param_array(capture_chans, int, NULL, DEFAULT_FILE_PERMS);
+MODULE_PARM_DESC(capture_chans, "Channels per capture device");
+
+static struct avirt_core {
+ struct device *dev;
+
+ struct class *avirt_class;
+ struct platform_device *platform_dev;
+} core;
+
+static struct avirt_coreinfo coreinfo = {
+ .version = { 0, 0, 1 },
+ .pcm_buff_complete = pcm_buff_complete_cb,
+};
+
+static struct list_head audiopath_list;
+
+/**
+ * avirt_probe - Register ALSA soundcard
+ * @devptr: Platform driver device
+ * @return: 0 on success or error code otherwise
+ */
+static int avirt_probe(struct platform_device *devptr)
+{
+ // struct avirt_alsa_dev_config capture_config[MAX_PCM_DEVS];
+ struct avirt_alsa_dev_config playback_config[MAX_PCM_DEVS];
+ int err = 0, i = 0;
+
+ if (playback_num == 0 && capture_num == 0) {
+ pr_err("playback_num or capture_num must be greater than 0!\n");
+ return -EINVAL;
+ }
+
+ coreinfo.playback_num = playback_num;
+ coreinfo.capture_num = capture_num;
+
+ err = avirt_alsa_init();
+ if (err < 0)
+ return err;
+
+ // Set up playback
+ for (i = 0; i < playback_num; i++) {
+ if (!playback_names[i]) {
+ pr_err("Playback config devicename is NULL for idx=%d\n",
+ i);
+ return -EINVAL;
+ }
+ memcpy((char *)playback_config[i].devicename, playback_names[i],
+ MAX_NAME_LEN);
+ if (playback_chans[i] == 0) {
+ pr_err("Playback config channels is 0 for idx=%d\n", i);
+ return -EINVAL;
+ }
+ playback_config[i].channels = playback_chans[i];
+ }
+ err = avirt_alsa_configure_pcm(playback_config,
+ SNDRV_PCM_STREAM_PLAYBACK, playback_num);
+ if (err < 0)
+ return err;
+
+// Does not work yet!
+#if 0
+ // Set up capture
+ for (i = 0; i < capture_num; i++) {
+ if (!capture_names[i]) {
+ pr_err("Capture config devicename is NULL for idx=%d",
+ i);
+ return -EINVAL;
+ }
+ memcpy((char *)capture_config[i].devicename, capture_names[i],
+ 255);
+
+ if (capture_chans[i] == 0) {
+ pr_err("Capture config channels is 0 for idx=%d");
+ return -EINVAL;
+ }
+ capture_config[i].channels = capture_chans[i];
+ }
+ err = avirt_alsa_configure_pcm(capture_config, SNDRV_PCM_STREAM_CAPTURE, capture_num);
+ if (err < 0)
+ return err;
+#endif
+
+ // Register for ALSA
+ CHK_ERR(avirt_alsa_register(devptr));
+
+ return err;
+}
+
+/**
+ * avirt_remove - Deregister ALSA soundcard
+ * @devptr: Platform driver device
+ * @return: 0 on success or error code otherwise
+ */
+static int avirt_remove(struct platform_device *devptr)
+{
+ avirt_alsa_deregister();
+
+ return 0;
+}
+
+static struct platform_driver avirt_driver = {
+ .probe = avirt_probe,
+ .remove = avirt_remove,
+ .driver =
+ {
+ .name = SND_AVIRTUAL_DRIVER,
+ },
+};
+
+struct avirt_audiopath_obj {
+ struct kobject kobj;
+ struct list_head list;
+ struct avirt_audiopath *path;
+};
+
+static struct kset *avirt_audiopath_kset;
+static struct kobject *kobj;
+
+#define to_audiopath_obj(d) container_of(d, struct avirt_audiopath_obj, kobj)
+#define to_audiopath_attr(a) \
+ container_of(a, struct avirt_audiopath_attribute, attr)
+
+/**
+ * struct 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 avirt_audiopath_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct avirt_audiopath_obj *d,
+ struct avirt_audiopath_attribute *attr, char *buf);
+ ssize_t (*store)(struct avirt_audiopath_obj *d,
+ struct 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 avirt_audiopath_attribute *audiopath_attr;
+ struct 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 avirt_audiopath_attribute *audiopath_attr;
+ struct 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 avirt_audiopath_sysfs_ops = {
+ .show = audiopath_attr_show,
+ .store = audiopath_attr_store,
+};
+
+/**
+ * avirt_audiopath_release - Audio Path release function
+ * @kobj: pointer to Audio Paths's kobject
+ */
+static void avirt_audiopath_release(struct kobject *kobj)
+{
+ struct avirt_audiopath_obj *audiopath_obj = to_audiopath_obj(kobj);
+
+ kfree(audiopath_obj);
+}
+
+static ssize_t audiopath_name_show(struct avirt_audiopath_obj *audiopath_obj,
+ struct avirt_audiopath_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", audiopath_obj->path->name);
+}
+
+static ssize_t audiopath_version_show(struct avirt_audiopath_obj *audiopath_obj,
+ struct avirt_audiopath_attribute *attr,
+ char *buf)
+{
+ struct avirt_audiopath *audiopath = audiopath_obj->path;
+ return sprintf(buf, "%d.%d.%d\n", audiopath->version[0],
+ audiopath->version[1], audiopath->version[2]);
+}
+
+static struct avirt_audiopath_attribute avirt_audiopath_attrs[] = {
+ __ATTR_RO(audiopath_name),
+ __ATTR_RO(audiopath_version),
+};
+
+static struct attribute *avirt_audiopath_def_attrs[] = {
+ &avirt_audiopath_attrs[0].attr,
+ &avirt_audiopath_attrs[1].attr,
+ NULL,
+};
+
+static struct kobj_type avirt_audiopath_ktype = {
+ .sysfs_ops = &avirt_audiopath_sysfs_ops,
+ .release = avirt_audiopath_release,
+ .default_attrs = avirt_audiopath_def_attrs,
+};
+
+/**
+ * create_avirt_audiopath_obj - creates an Audio Path object
+ * @name: name 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 avirt_audiopath_obj *create_avirt_audiopath_obj(const char *name)
+{
+ struct avirt_audiopath_obj *avirt_audiopath;
+ int retval;
+
+ avirt_audiopath = kzalloc(sizeof(*avirt_audiopath), GFP_KERNEL);
+ if (!avirt_audiopath)
+ return NULL;
+ avirt_audiopath->kobj.kset = avirt_audiopath_kset;
+ retval = kobject_init_and_add(&avirt_audiopath->kobj,
+ &avirt_audiopath_ktype, kobj, "%s", name);
+ if (retval) {
+ kobject_put(&avirt_audiopath->kobj);
+ return NULL;
+ }
+ kobject_uevent(&avirt_audiopath->kobj, KOBJ_ADD);
+ return avirt_audiopath;
+}
+
+/**
+ * destroy_avirt_audiopath_obj - destroys an Audio Path object
+ * @name: the Audio Path object
+ */
+static void destroy_avirt_audiopath_obj(struct avirt_audiopath_obj *p)
+{
+ kobject_put(&p->kobj);
+}
+
+/**
+ * avirt_get_current_audiopath - retrieves the current Audio Path
+ * @return: Current Audio Path
+ */
+struct avirt_audiopath *avirt_get_current_audiopath()
+{
+ struct avirt_audiopath_obj *ap_obj = list_entry(
+ audiopath_list.next, struct avirt_audiopath_obj, list);
+ return ap_obj->path;
+}
+
+/**
+ * avirt_register_audiopath - register Audio Path with ALSA virtual driver
+ * @audiopath: Audio Path to be registered
+ * @core: ALSA virtual driver core info
+ * @return: 0 on success or error code otherwise
+ */
+int avirt_register_audiopath(struct avirt_audiopath *audiopath,
+ struct avirt_coreinfo **info)
+{
+ struct avirt_audiopath_obj *audiopath_obj;
+
+ if (!audiopath) {
+ pr_err("Bad audio path\n");
+ return -EINVAL;
+ }
+
+ audiopath_obj = create_avirt_audiopath_obj(audiopath->name);
+ if (!audiopath_obj) {
+ pr_info("failed to alloc driver object\n");
+ return -ENOMEM;
+ }
+ audiopath_obj->path = audiopath;
+
+ audiopath->context = audiopath_obj;
+ pr_info("Registered new Audio Path: %s\n", audiopath->name);
+ pr_info("\tBlocksize: %d, Periods: %d\n", audiopath->blocksize,
+ audiopath->hw->periods_max);
+ list_add_tail(&audiopath_obj->list, &audiopath_list);
+
+ *info = &coreinfo;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(avirt_register_audiopath);
+
+/**
+ * avirt_deregister_audiopath - deregister Audio Path with ALSA virtual driver
+ * @audiopath: Audio Path to be deregistered
+ * @return: 0 on success or error code otherwise
+ */
+int avirt_deregister_audiopath(struct avirt_audiopath *audiopath)
+{
+ struct avirt_audiopath_obj *audiopath_obj;
+
+ // Check if audio path is registered
+ if (!audiopath) {
+ pr_err("Bad Audio Path Driver\n");
+ return -EINVAL;
+ }
+
+ audiopath_obj = audiopath->context;
+ if (!audiopath_obj) {
+ pr_info("driver not registered.\n");
+ return -EINVAL;
+ }
+
+ list_del(&audiopath_obj->list);
+ destroy_avirt_audiopath_obj(audiopath_obj);
+ pr_info("Deregistered Audio Path %s\n", audiopath->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(avirt_deregister_audiopath);
+
+/**
+ * avirt_unregister_all - Unregister the platform device driver
+ */
+static void avirt_unregister_all(void)
+{
+ platform_device_unregister(core.platform_dev);
+ platform_driver_unregister(&avirt_driver);
+}
+
+/**
+ * core_init - Initialize the kernel module
+ */
+static int __init core_init(void)
+{
+ int err;
+
+ pr_info("Alsa Virtual Sound Driver avirt-%d.%d.%d\n",
+ coreinfo.version[0], coreinfo.version[1], coreinfo.version[2]);
+
+ // Initialize audiopaths linked list
+ INIT_LIST_HEAD(&audiopath_list);
+
+ err = platform_driver_register(&avirt_driver);
+ if (err < 0)
+ return err;
+
+ core.platform_dev = platform_device_register_simple(SND_AVIRTUAL_DRIVER,
+ 0, NULL, 0);
+ if (IS_ERR(core.platform_dev)) {
+ err = PTR_ERR(core.platform_dev);
+ pr_err("platform_dev error [%d]!\n", err);
+ goto exit_platform_device;
+ }
+
+ core.avirt_class = class_create(THIS_MODULE, SND_AVIRTUAL_DRIVER);
+ if (IS_ERR(core.avirt_class)) {
+ pr_err("No udev support\n");
+ err = PTR_ERR(core.avirt_class);
+ goto exit_bus;
+ }
+
+ core.dev = device_create(core.avirt_class, NULL, 0, NULL, "avirtcore");
+ if (IS_ERR(core.dev)) {
+ err = PTR_ERR(core.dev);
+ goto exit_class;
+ }
+
+ avirt_audiopath_kset =
+ kset_create_and_add("audiopaths", NULL, &core.dev->kobj);
+ if (!avirt_audiopath_kset) {
+ err = -ENOMEM;
+ goto exit_class_container;
+ }
+
+ return 0;
+
+exit_class_container:
+ device_destroy(core.avirt_class, 0);
+exit_class:
+ class_destroy(core.avirt_class);
+exit_bus:
+exit_platform_device:
+ avirt_unregister_all();
+ return err;
+}
+
+/**
+ * core_exit - Destroy the kernel module
+ */
+static void __exit core_exit(void)
+{
+ kset_unregister(avirt_audiopath_kset);
+ device_destroy(core.avirt_class, 0);
+ class_destroy(core.avirt_class);
+
+ avirt_unregister_all();
+
+ pr_info("playback_num: %d, capture_num: %d\n", playback_num,
+ capture_num);
+
+ pr_info("playback_chans: %d %d %d %d %d %d %d %d\n", playback_chans[0],
+ playback_chans[1], playback_chans[2], playback_chans[3],
+ playback_chans[4], playback_chans[5], playback_chans[6],
+ playback_chans[7]);
+
+ pr_info("capture_chans: %d %d %d %d %d %d %d %d\n", capture_chans[0],
+ capture_chans[1], capture_chans[2], capture_chans[3],
+ capture_chans[4], capture_chans[5], capture_chans[6],
+ capture_chans[7]);
+
+ pr_info("playback_names: %s %s %s %s %s %s %s %s\n", playback_names[0],
+ playback_names[1], playback_names[2], playback_names[3],
+ playback_names[4], playback_names[5], playback_names[6],
+ playback_names[7]);
+
+ pr_info("capture_names: %s %s %s %s %s %s %s %s\n", capture_names[0],
+ capture_names[1], capture_names[2], capture_names[3],
+ capture_names[4], capture_names[5], capture_names[6],
+ capture_names[7]);
+}
+
+module_init(core_init);
+module_exit(core_exit);