From 8ce739b235362ca810a5e25fef58e7400ba679b4 Mon Sep 17 00:00:00 2001
From: Mark Farrugia <mark.farrugia@fiberdyne.com.au>
Date: Fri, 1 Mar 2019 17:32:37 +1100
Subject: Add ability to route audio between audio paths

Configfs interface for adding 'routes' is added, and allows two audio paths to chain together particular inputs/outputs, allowing for multistage audio driver handling.

A particular use-case is that of an ADSP driver. We can feed in audio to it, and capture the processed, and/or mixed, audio at it's output, and direct to another audio path driver, such as the UNICENS audio driver.

Signed-off-by: Mark Farrugia <mark.farrugia@fiberdyne.com.au>
---
 configfs.c    | 254 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 core.c        |  22 +++++
 core.h        |  21 +++++
 sound/avirt.h |  21 ++++-
 sysfs.c       |  27 +++++++
 5 files changed, 305 insertions(+), 40 deletions(-)

diff --git a/configfs.c b/configfs.c
index 4792f87..58e523f 100644
--- a/configfs.c
+++ b/configfs.c
@@ -17,18 +17,56 @@
 #define D_PRINTK(fmt, args...) DDEBUG(D_LOGNAME, fmt, ##args)
 #define D_ERRORK(fmt, args...) DERROR(D_LOGNAME, fmt, ##args)
 
-static ssize_t cfg_snd_avirt_stream_direction_show(struct config_item *item,
-						   char *page)
-{
-	ssize_t count;
-	struct snd_avirt_stream *stream =
-		snd_avirt_stream_from_config_item(item);
-
-	count = sprintf(page, "%d\n", stream->direction);
-
-	return count;
-}
-CONFIGFS_ATTR_RO(cfg_snd_avirt_stream_, direction);
+/**
+ * Defines the configfs direction 'show' callback for a stream or route
+ */
+#define CFG_SND_AVIRT_DIRECTION_RO(type)                                       \
+	static ssize_t cfg_snd_avirt_##type##_direction_show(                  \
+		struct config_item *item, char *page)                          \
+	{                                                                      \
+		ssize_t count;                                                 \
+		struct snd_avirt_##type *s =                                   \
+			snd_avirt_##type##_from_config_item(item);             \
+		count = sprintf(page, "%d\n", s->direction);                   \
+		return count;                                                  \
+	}                                                                      \
+	CONFIGFS_ATTR_RO(cfg_snd_avirt_##type##_, direction)
+
+/**
+ * Defines the configfs channels 'show'/'store' callbacks for a stream or route
+ */
+#define CFG_SND_AVIRT_CHANNELS(type)                                           \
+	static ssize_t cfg_snd_avirt_##type##_channels_show(                   \
+		struct config_item *item, char *page)                          \
+	{                                                                      \
+		ssize_t count;                                                 \
+		struct snd_avirt_##type *s =                                   \
+			snd_avirt_##type##_from_config_item(item);             \
+		count = sprintf(page, "%d\n", s->channels);                    \
+		return count;                                                  \
+	}                                                                      \
+	static ssize_t cfg_snd_avirt_##type##_channels_store(                  \
+		struct config_item *item, const char *page, size_t count)      \
+	{                                                                      \
+		int err;                                                       \
+		struct snd_avirt_##type *s =                                   \
+			snd_avirt_##type##_from_config_item(item);             \
+		unsigned long tmp;                                             \
+		char *p = (char *)page;                                        \
+		err = kstrtoul(p, 10, &tmp);                                   \
+		if (err < 0)                                                   \
+			return err;                                            \
+		if ((tmp > INT_MAX) || (tmp == 0))                             \
+			return -ERANGE;                                        \
+		s->channels = tmp;                                             \
+		return count;                                                  \
+	}                                                                      \
+	CONFIGFS_ATTR(cfg_snd_avirt_##type##_, channels);
+
+CFG_SND_AVIRT_DIRECTION_RO(stream);
+CFG_SND_AVIRT_CHANNELS(stream);
+CFG_SND_AVIRT_DIRECTION_RO(route);
+CFG_SND_AVIRT_CHANNELS(route);
 
 static ssize_t cfg_snd_avirt_stream_map_show(struct config_item *item,
 					     char *page)
@@ -53,64 +91,141 @@ static ssize_t cfg_snd_avirt_stream_map_store(struct config_item *item,
 }
 CONFIGFS_ATTR(cfg_snd_avirt_stream_, map);
 
-static ssize_t cfg_snd_avirt_stream_channels_show(struct config_item *item,
-						  char *page)
+static struct configfs_attribute *cfg_snd_avirt_stream_attrs[] = {
+	&cfg_snd_avirt_stream_attr_channels,
+	&cfg_snd_avirt_stream_attr_map,
+	&cfg_snd_avirt_stream_attr_direction,
+	NULL,
+};
+
+static ssize_t cfg_snd_avirt_route_to_ap_show(struct config_item *item,
+					      char *page)
 {
-	ssize_t count;
-	struct snd_avirt_stream *stream =
-		snd_avirt_stream_from_config_item(item);
+	struct snd_avirt_route *route = snd_avirt_route_from_config_item(item);
+
+	if (route)
+		if (route->to_ap)
+			return sprintf(page, "%s\n", route->to_ap->uid);
+
+	return sprintf(page, "\n");
+}
+
+static ssize_t cfg_snd_avirt_route_to_ap_store(struct config_item *item,
+					       const char *page, size_t count)
+{
+	char *uid_ap;
+	struct snd_avirt_route *route = snd_avirt_route_from_config_item(item);
+	struct snd_avirt_audiopath *to_ap;
+
+	uid_ap = strsep((char **)&page, "\n");
+	to_ap = snd_avirt_audiopath_get(uid_ap);
+	if (!to_ap) {
+		D_ERRORK("Audio Path '%s' does not exist!", uid_ap);
+		D_ERRORK("Cannot set 'route'->'to_ap'");
+		return count;
+	}
 
-	count = sprintf(page, "%d\n", stream->channels);
+	route->to_ap = to_ap;
+	if (route->from_ap) {
+		route->from_ap->route_to_ap = to_ap;
+		route->to_ap->route_from_ap = route->from_ap;
+	}
 
 	return count;
 }
+CONFIGFS_ATTR(cfg_snd_avirt_route_, to_ap);
 
-static ssize_t cfg_snd_avirt_stream_channels_store(struct config_item *item,
-						   const char *page,
-						   size_t count)
+static ssize_t cfg_snd_avirt_route_from_ap_show(struct config_item *item,
+						char *page)
 {
-	int err;
-	struct snd_avirt_stream *stream =
-		snd_avirt_stream_from_config_item(item);
-	unsigned long tmp;
-	char *p = (char *)page;
+	struct snd_avirt_route *route = snd_avirt_route_from_config_item(item);
 
-	err = kstrtoul(p, 10, &tmp);
-	if (err < 0)
-		return err;
+	if (route)
+		if (route->from_ap)
+			return sprintf(page, "%s\n", route->from_ap->uid);
 
-	if ((tmp > INT_MAX) || (tmp == 0))
-		return -ERANGE;
+	return sprintf(page, "\n");
+}
 
-	stream->channels = tmp;
+static ssize_t cfg_snd_avirt_route_from_ap_store(struct config_item *item,
+						 const char *page, size_t count)
+{
+	char *uid_ap;
+	struct snd_avirt_route *route = snd_avirt_route_from_config_item(item);
+	struct snd_avirt_audiopath *from_ap;
+
+	uid_ap = strsep((char **)&page, "\n");
+	from_ap = snd_avirt_audiopath_get(uid_ap);
+	if (!from_ap) {
+		D_ERRORK("Audio Path '%s' does not exist!", uid_ap);
+		D_ERRORK("Cannot set 'route'->'from_ap'");
+		return count;
+	}
+
+	route->from_ap = from_ap;
+	if (route->to_ap) {
+		route->to_ap->route_from_ap = from_ap;
+		route->from_ap->route_to_ap = route->to_ap;
+	}
 
 	return count;
 }
-CONFIGFS_ATTR(cfg_snd_avirt_stream_, channels);
+CONFIGFS_ATTR(cfg_snd_avirt_route_, from_ap);
 
-static struct configfs_attribute *cfg_snd_avirt_stream_attrs[] = {
-	&cfg_snd_avirt_stream_attr_channels,
-	&cfg_snd_avirt_stream_attr_map,
-	&cfg_snd_avirt_stream_attr_direction,
+static struct configfs_attribute *cfg_snd_avirt_route_attrs[] = {
+	&cfg_snd_avirt_route_attr_channels,
+	&cfg_snd_avirt_route_attr_direction,
+	&cfg_snd_avirt_route_attr_from_ap,
+	&cfg_snd_avirt_route_attr_to_ap,
 	NULL,
 };
 
 static void cfg_snd_avirt_stream_release(struct config_item *item)
 {
-	D_INFOK("item->name:%s", item->ci_namebuf);
-	kfree(snd_avirt_stream_from_config_item(item));
+	struct snd_avirt_stream *stream =
+		snd_avirt_stream_from_config_item(item);
+	if (!stream) {
+		D_ERRORK("Cannot release stream!");
+		return;
+	}
+
+	D_INFOK("Release stream: %s", stream->name);
+	kfree(stream->pcm_ops);
+	kfree(stream);
+}
+
+static void cfg_snd_avirt_route_release(struct config_item *item)
+{
+	struct snd_avirt_route *route = snd_avirt_route_from_config_item(item);
+	if (!route) {
+		D_ERRORK("Cannot release route!");
+		return;
+	}
+
+	D_INFOK("Release route: %s", route->name);
+	kfree(route);
 }
 
 static struct configfs_item_operations cfg_snd_avirt_stream_ops = {
 	.release = cfg_snd_avirt_stream_release,
 };
 
+static struct configfs_item_operations cfg_snd_avirt_route_ops = {
+	.release = cfg_snd_avirt_route_release,
+};
+
 static struct config_item_type cfg_snd_avirt_stream_type = {
 	.ct_item_ops = &cfg_snd_avirt_stream_ops,
 	.ct_attrs = cfg_snd_avirt_stream_attrs,
 	.ct_owner = THIS_MODULE,
 };
 
+static struct config_item_type cfg_snd_avirt_route_type = {
+	.ct_item_ops = &cfg_snd_avirt_route_ops,
+	.ct_attrs = cfg_snd_avirt_route_attrs,
+	.ct_owner = THIS_MODULE,
+};
+
 static struct config_item *
 cfg_snd_avirt_stream_make_item(struct config_group *group, const char *name)
 {
@@ -144,9 +259,49 @@ cfg_snd_avirt_stream_make_item(struct config_group *group, const char *name)
 	config_item_init_type_name(&stream->item, name,
 				   &cfg_snd_avirt_stream_type);
 
+	D_INFOK("Make stream: %s", stream->name);
+
 	return &stream->item;
 }
 
+static struct config_item *
+cfg_snd_avirt_route_make_item(struct config_group *group, const char *name)
+{
+	char *split;
+	int direction;
+	struct snd_avirt_route *route;
+
+	// Get prefix (playback_ or capture_)
+	split = strsep((char **)&name, "_");
+	if (!split) {
+		D_ERRORK("Route name: '%s' invalid!", split);
+		D_ERRORK("Must begin with playback_ * or capture_ *");
+		return ERR_PTR(-EINVAL);
+	}
+	if (!strcmp(split, "playback")) {
+		direction = SNDRV_PCM_STREAM_PLAYBACK;
+	} else if (!strcmp(split, "capture")) {
+		direction = SNDRV_PCM_STREAM_CAPTURE;
+	} else {
+		D_ERRORK("Route name: '%s' invalid!", split);
+		D_ERRORK("Must begin with playback_ * or capture_ ");
+		return ERR_PTR(-EINVAL);
+	}
+
+	// Get route name, and create route
+	split = strsep((char **)&name, "\n");
+	route = snd_avirt_route_create(split, direction);
+	if (IS_ERR(route))
+		return ERR_PTR(PTR_ERR(route));
+
+	config_item_init_type_name(&route->item, name,
+				   &cfg_snd_avirt_route_type);
+
+	D_INFOK("Make route: %s", route->name);
+
+	return &route->item;
+}
+
 static ssize_t cfg_snd_avirt_stream_group_sealed_show(struct config_item *item,
 						      char *page)
 {
@@ -182,12 +337,22 @@ static struct configfs_group_operations cfg_snd_avirt_stream_group_ops = {
 	.make_item = cfg_snd_avirt_stream_make_item
 };
 
+static struct configfs_group_operations cfg_snd_avirt_route_group_ops = {
+	.make_item = cfg_snd_avirt_route_make_item
+};
+
 static struct config_item_type cfg_stream_group_type = {
 	.ct_group_ops = &cfg_snd_avirt_stream_group_ops,
 	.ct_attrs = cfg_snd_avirt_stream_group_attrs,
 	.ct_owner = THIS_MODULE
 };
 
+static struct config_item_type cfg_route_group_type = {
+	.ct_group_ops = &cfg_snd_avirt_route_group_ops,
+	.ct_attrs = NULL,
+	.ct_owner = THIS_MODULE
+};
+
 static struct config_item_type cfg_avirt_group_type = {
 	.ct_owner = THIS_MODULE,
 };
@@ -212,6 +377,8 @@ int __init snd_avirt_configfs_init(struct snd_avirt_core *core)
 		D_ERRORK("Cannot register configfs subsys!");
 		return err;
 	}
+
+	/* Create streams default group */
 	core->stream_group = configfs_register_default_group(
 		&cfg_subsys.su_group, "streams", &cfg_stream_group_type);
 	if (IS_ERR(core->stream_group)) {
@@ -220,6 +387,15 @@ int __init snd_avirt_configfs_init(struct snd_avirt_core *core)
 		goto exit_configfs;
 	}
 
+	/* Create routes default group */
+	core->route_group = configfs_register_default_group(
+		&cfg_subsys.su_group, "routes", &cfg_route_group_type);
+	if (IS_ERR(core->route_group)) {
+		err = PTR_ERR(core->route_group);
+		D_ERRORK("Cannot register configfs default group 'routes'!");
+		goto exit_configfs;
+	}
+
 	return 0;
 
 exit_configfs:
diff --git a/core.c b/core.c
index fd74b08..e1b056e 100644
--- a/core.c
+++ b/core.c
@@ -265,6 +265,28 @@ int snd_avirt_audiopath_deregister(struct snd_avirt_audiopath *audiopath)
 }
 EXPORT_SYMBOL_GPL(snd_avirt_audiopath_deregister);
 
+/**
+ * snd_avirt_route_create - Create audio route
+ * @name: The name 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 *name, int direction)
+{
+	struct snd_avirt_route *route;
+
+	route = kzalloc(sizeof(*route), GFP_KERNEL);
+	if (!route)
+		return ERR_PTR(-ENOMEM);
+
+	strcpy(route->name, name);
+	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
diff --git a/core.h b/core.h
index e83631f..e4fb7a4 100644
--- a/core.h
+++ b/core.h
@@ -23,6 +23,7 @@ struct snd_avirt_core {
 	struct class *class;
 	struct platform_device *plat_dev;
 	struct config_group *stream_group;
+	struct config_group *route_group;
 	unsigned int stream_count;
 	bool streams_sealed;
 };
@@ -117,6 +118,15 @@ int snd_avirt_stream_set_map(struct snd_avirt_stream *stream, const char *map);
  */
 struct snd_avirt_audiopath *snd_avirt_audiopath_get(const char *uid);
 
+/**
+ * snd_avirt_route_create - Create audio route
+ * @name: The name 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 *name, int direction);
+
 /**
  * snd_avirt_stream_from_config_item - Convert config_item to a snd_avirt_stream
  * @item: The config_item to convert from
@@ -128,4 +138,15 @@ snd_avirt_stream_from_config_item(struct config_item *item)
 	return item ? container_of(item, struct snd_avirt_stream, item) : NULL;
 }
 
+/**
+ * snd_avirt_route_from_config_item - Convert config_item to a snd_avirt_route
+ * @item: The config_item to convert from
+ * @return: The item's snd_avirt_stream if successful, NULL otherwise
+ */
+static inline struct snd_avirt_route *
+snd_avirt_route_from_config_item(struct config_item *item)
+{
+	return item ? container_of(item, struct snd_avirt_route, item) : NULL;
+}
+
 #endif /* __SOUND_AVIRT_CORE_H */
diff --git a/sound/avirt.h b/sound/avirt.h
index cb9a61e..138d0ca 100644
--- a/sound/avirt.h
+++ b/sound/avirt.h
@@ -36,9 +36,12 @@ struct snd_avirt_stream_array; /* Forward declaration */
 typedef int (*snd_avirt_audiopath_configure)(
 	struct snd_card *card, struct snd_avirt_stream_array *stream_array);
 
+typedef void (*snd_avirt_pcm_exttrigger)(void);
+
 /**
  * AVIRT Audio Path info
  */
+typedef struct snd_avirt_audiopath snd_avirt_audiopath;
 struct snd_avirt_audiopath {
 	const char *uid; /* Unique identifier */
 	const char *name; /* Pretty name */
@@ -47,8 +50,24 @@ struct snd_avirt_audiopath {
 	const struct snd_pcm_ops *pcm_playback_ops; /* ALSA PCM playback ops */
 	const struct snd_pcm_ops *pcm_capture_ops; /* ALSA PCM capture ops */
 	snd_avirt_audiopath_configure configure; /* Config callback function */
-
+	snd_avirt_pcm_exttrigger pcm_exttrigger;
 	void *context;
+
+	// MUST be at the end
+	struct snd_avirt_audiopath *route_from_ap;
+	struct snd_avirt_audiopath *route_to_ap;
+};
+
+/**
+ * Audio routing
+ */
+struct snd_avirt_route {
+	char name[MAX_NAME_LEN];
+	unsigned int channels;
+	unsigned int direction;
+	struct snd_avirt_audiopath *from_ap;
+	struct snd_avirt_audiopath *to_ap;
+	struct config_item item;
 };
 
 /**
diff --git a/sysfs.c b/sysfs.c
index b7d28f5..29f9b5d 100644
--- a/sysfs.c
+++ b/sysfs.c
@@ -115,15 +115,42 @@ static ssize_t version_show(struct snd_avirt_audiopath_obj *audiopath_obj,
 		       audiopath->version[1], audiopath->version[2]);
 }
 
+static ssize_t route_from_ap_show(struct snd_avirt_audiopath_obj *audiopath_obj,
+				  struct snd_avirt_audiopath_attribute *attr,
+				  char *buf)
+{
+	struct snd_avirt_audiopath *audiopath = audiopath_obj->path;
+
+	if (audiopath->route_from_ap)
+		return sprintf(buf, "%s\n", audiopath->route_from_ap->uid);
+
+	return 0;
+}
+
+static ssize_t route_to_ap_show(struct snd_avirt_audiopath_obj *audiopath_obj,
+				struct snd_avirt_audiopath_attribute *attr,
+				char *buf)
+{
+	struct snd_avirt_audiopath *audiopath = audiopath_obj->path;
+
+	if (audiopath->route_to_ap)
+		return sprintf(buf, "%s\n", audiopath->route_to_ap->uid);
+
+	return 0;
+}
 
 static struct snd_avirt_audiopath_attribute snd_avirt_audiopath_attrs[] = {
 	__ATTR_RO(name),
 	__ATTR_RO(version),
+	__ATTR_RO(route_from_ap),
+	__ATTR_RO(route_to_ap),
 };
 
 static struct attribute *snd_avirt_audiopath_def_attrs[] = {
 	&snd_avirt_audiopath_attrs[0].attr,
 	&snd_avirt_audiopath_attrs[1].attr,
+	&snd_avirt_audiopath_attrs[2].attr,
+	&snd_avirt_audiopath_attrs[3].attr,
 	NULL,
 };
 
-- 
cgit