From 8ce739b235362ca810a5e25fef58e7400ba679b4 Mon Sep 17 00:00:00 2001 From: Mark Farrugia 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 --- 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 1.2.3-korg