// SPDX-License-Identifier: GPL-2.0 /* * AVIRT - ALSA Virtual Soundcard * * Copyright (c) 2010-2018 Fiberdyne Systems Pty Ltd * * configfs.c - AVIRT configfs support */ #include #include "core.h" #define D_LOGNAME "configfs" #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) /** * 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; \ snd_avirt_##type##_try_complete(s); \ 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); /* * Check PCM hw params between a source and a sink */ #define CHK_ROUTE_ERR(source_hw, sink_hw, field) \ do { \ if (source_hw->field != sink_hw->field) { \ D_ERRORK( \ "Route HW mismatch: ##field (src:%d, sink:%d)", \ source_hw->field, sink_hw->field); \ return -1; \ } \ } while (0) /* * Check the a route's source and sink Audio Path's hardware params, to ensure * compatibility */ static int cfg_snd_avirt_route_verify_hw(struct snd_avirt_audiopath *source_ap, struct snd_avirt_audiopath *sink_ap) { const struct snd_pcm_hardware *source_hw = source_ap->hw; const struct snd_pcm_hardware *sink_hw = sink_ap->hw; CHK_ROUTE_ERR(source_hw, sink_hw, channels_max); CHK_ROUTE_ERR(source_hw, sink_hw, channels_min); CHK_ROUTE_ERR(source_hw, sink_hw, rate_max); CHK_ROUTE_ERR(source_hw, sink_hw, rate_min); CHK_ROUTE_ERR(source_hw, sink_hw, rates); return 0; } /* * Store the Audio Path endpoint (source or sink), and check compatiblity */ static int cfg_snd_avirt_route_ap_store(struct snd_avirt_route *route, struct snd_avirt_audiopath *ap, snd_avirt_route_endpoint endpoint) { /* If other endpoint is set, we want to check that the two Audio Path * endpoints are compatible before we set this endpoint */ if (route->endpoint_ap[!endpoint]) { if (!cfg_snd_avirt_route_verify_hw( route->endpoint_ap[!endpoint], ap)) { route->endpoint_ap[endpoint] = ap; D_INFOK("Route successfully created: '%s' [%s -> %s]", route->uid, ap->uid, route->endpoint_ap[!endpoint]->uid); return 0; } else { D_ERRORK("Route could not be created: %s", route->uid); return -1; } } else { route->endpoint_ap[endpoint] = ap; } return 0; } static ssize_t cfg_snd_avirt_stream_map_show(struct config_item *item, char *page) { struct snd_avirt_stream *stream = snd_avirt_stream_from_config_item(item); return sprintf(page, "%s\n", stream->map); } static ssize_t cfg_snd_avirt_stream_map_store(struct config_item *item, const char *page, size_t count) { char *map; struct snd_avirt_audiopath *audiopath; struct snd_avirt_stream *stream = snd_avirt_stream_from_config_item(item); map = strsep((char **)&page, "\n"); /* If already configured, we cannot create the stream */ if (snd_avirt_streams_configured()) { D_ERRORK("Streams already configured. Cannot set map: '%s'", map); return -EPERM; } if (!strcmp(stream->map, map)) return -1; audiopath = snd_avirt_audiopath_get(map); if (!audiopath) { D_ERRORK("Cannot find Audio Path uid: '%s'!", stream->map); } memcpy(stream->map, (char *)map, strlen(map)); snd_avirt_stream_try_complete(stream); return count; } CONFIGFS_ATTR(cfg_snd_avirt_stream_, map); 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_sink_ap_show(struct config_item *item, char *page) { struct snd_avirt_route *route = snd_avirt_route_from_config_item(item); if (!route) { D_ERRORK("Cannot get route!"); goto exit_err; } if (route->endpoint_ap[SND_AVIRT_ROUTE_SINK]) return sprintf(page, "%s\n", route->endpoint_ap[SND_AVIRT_ROUTE_SINK]->uid); exit_err: return sprintf(page, "\n"); } static ssize_t cfg_snd_avirt_route_sink_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 *sink_ap; if (!route) { D_ERRORK("Cannot get route!"); goto exit; } uid_ap = strsep((char **)&page, "\n"); sink_ap = snd_avirt_audiopath_get(uid_ap); if (!sink_ap) { D_ERRORK("Audio Path '%s' does not exist!", uid_ap); D_ERRORK("Cannot set 'route'->'sink_ap'"); goto exit; } cfg_snd_avirt_route_ap_store(route, sink_ap, SND_AVIRT_ROUTE_SINK); exit: return count; } CONFIGFS_ATTR(cfg_snd_avirt_route_, sink_ap); static ssize_t cfg_snd_avirt_route_source_ap_show(struct config_item *item, char *page) { struct snd_avirt_route *route = snd_avirt_route_from_config_item(item); if (!route) { D_ERRORK("Cannot get route!"); goto exit_err; } if (route->endpoint_ap[SND_AVIRT_ROUTE_SOURCE]) return sprintf(page, "%s\n", route->endpoint_ap[SND_AVIRT_ROUTE_SOURCE]->uid); exit_err: return sprintf(page, "\n"); } static ssize_t cfg_snd_avirt_route_source_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 *source_ap; if (!route) { D_ERRORK("Cannot get route!"); goto exit; } uid_ap = strsep((char **)&page, "\n"); source_ap = snd_avirt_audiopath_get(uid_ap); if (!source_ap) { D_ERRORK("Audio Path '%s' does not exist!", uid_ap); D_ERRORK("Cannot set 'route'->'source_ap'"); return count; } cfg_snd_avirt_route_ap_store(route, source_ap, SND_AVIRT_ROUTE_SOURCE); exit: return count; } CONFIGFS_ATTR(cfg_snd_avirt_route_, source_ap); 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_source_ap, &cfg_snd_avirt_route_attr_sink_ap, NULL, }; static void cfg_snd_avirt_stream_release(struct 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); snd_avirt_stream_try_destroy(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; } kfree(route); D_INFOK("Release route: %s", route->uid); } 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) { char *split, *stream_name; bool internal = false; int direction; struct snd_avirt_stream *stream; // Get prefix (playback_ or capture_) split = strsep((char **)&name, "_"); if (!split) { D_ERRORK("Stream 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("Stream name: '%s' invalid!", split); D_ERRORK("Must begin with playback_ * or capture_ "); return ERR_PTR(-EINVAL); } // Get stream name, and create PCM for stream stream_name = strsep((char **)&name, "\n"); // If internal, get internal split = strstr(stream_name, "__internal"); if (split) { stream_name = strsep((char **)&stream_name, "__"); internal = true; } // Finally, create stream stream = snd_avirt_stream_create(stream_name, direction, internal); if (IS_ERR(stream)) return ERR_PTR(PTR_ERR(stream)); config_item_init_type_name(&stream->item, name, &cfg_snd_avirt_stream_type); 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); return &route->item; } static ssize_t cfg_snd_avirt_stream_group_configured_show(struct config_item *item, char *page) { return snprintf(page, PAGE_SIZE, "%d\n", snd_avirt_streams_configured()); } static ssize_t cfg_snd_avirt_stream_group_configured_store(struct config_item *item, const char *page, size_t count) { unsigned long tmp; char *p = (char *)page; CHK_ERR(kstrtoul(p, 10, &tmp)); if (tmp > 1) { D_ERRORK("Configure streams must be 0 or 1!"); return -ERANGE; } (tmp) ? snd_avirt_streams_configure() : snd_avirt_streams_unconfigure(); return count; } CONFIGFS_ATTR(cfg_snd_avirt_stream_group_, configured); static struct configfs_attribute *cfg_snd_avirt_stream_group_attrs[] = { &cfg_snd_avirt_stream_group_attr_configured, NULL, }; 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, }; static struct configfs_subsystem cfg_subsys = { .su_group = { .cg_item = { .ci_namebuf = "snd-avirt", .ci_type = &cfg_avirt_group_type, }, }, }; int __init snd_avirt_configfs_init(struct snd_avirt_core *core) { int err; config_group_init(&cfg_subsys.su_group); mutex_init(&cfg_subsys.su_mutex); err = configfs_register_subsystem(&cfg_subsys); if (err) { 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)) { err = PTR_ERR(core->stream_group); D_ERRORK("Cannot register configfs default group 'streams'!"); 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: configfs_unregister_subsystem(&cfg_subsys); return err; } void __exit snd_avirt_configfs_exit(struct snd_avirt_core *core) { configfs_unregister_default_group(core->stream_group); configfs_unregister_subsystem(&cfg_subsys); }