// 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; \ 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) { 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 *split; struct snd_avirt_stream *stream = snd_avirt_stream_from_config_item(item); split = strsep((char **)&page, "\n"); snd_avirt_stream_set_map(stream, split); 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_to_ap_show(struct config_item *item, char *page) { 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; } 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_route_from_ap_show(struct config_item *item, char *page) { struct snd_avirt_route *route = snd_avirt_route_from_config_item(item); if (route) if (route->from_ap) return sprintf(page, "%s\n", route->from_ap->uid); return sprintf(page, "\n"); } 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_route_, from_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_from_ap, &cfg_snd_avirt_route_attr_to_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); 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) { char *split; 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 split = strsep((char **)&name, "\n"); stream = snd_avirt_stream_create(split, direction); if (IS_ERR(stream)) return ERR_PTR(PTR_ERR(stream)); 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) { return snprintf(page, PAGE_SIZE, "%d\n", snd_avirt_streams_sealed()); } static ssize_t cfg_snd_avirt_stream_group_sealed_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("streams can only be sealed, not unsealed!"); return -ERANGE; } snd_avirt_streams_seal(); return count; } CONFIGFS_ATTR(cfg_snd_avirt_stream_group_, sealed); static struct configfs_attribute *cfg_snd_avirt_stream_group_attrs[] = { &cfg_snd_avirt_stream_group_attr_sealed, 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); }