/* * Application interface library for the AVIRT driver * * avirt-config.c - Main AVIRT configuration via configfs * * Copyright (C) 2018 Fiberdyne Systems Pty Ltd * Author: Mark Farrugia * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . */ #include #include #include #include #include #include #include #include #define AVIRT_CONFIGFS_PATH_STREAMS "/config/snd-avirt/streams/" #define AVIRT_CONFIGFS_PATH_ROUTES "/config/snd-avirt/routes/" #define AVIRT_SYSFS_PATH_AUDIOPATHS "/sys/class/snd_avirt/core/audiopaths/" #define AVIRT_DEVICE_PATH "/dev/snd/by-path/platform-snd_avirt" #define AVIRT_CONFIGFS_PATH_MAXLEN 64 #define AVIRT_SYSFS_PATH_MAXLEN 80 /** * Logging macros */ #define AVIRT_ERROR(errmsg) \ fprintf(stderr, "AVIRT ERROR: %s\n", errmsg); #define AVIRT_ERROR_V(fmt, args...) \ fprintf(stderr, "AVIRT ERROR: " fmt "\n", ##args); #define AVIRT_DEBUG_ON #ifdef AVIRT_DEBUG_ON # define AVIRT_DEBUG(debugmsg) \ fprintf(stderr, "AVIRT DEBUG: %s\n", debugmsg); # define AVIRT_DEBUG_V(fmt, args...) \ fprintf(stderr, "[%s]: AVIRT DEBUG: " fmt "\n", __func__, ##args); #endif /** * Error checking macros */ #define CHK_ERR(err, fmt, args...) \ if (err < 0) { \ AVIRT_ERROR_V("Returned error:%d, "fmt, err, ##args); \ return err; \ } #define CHK_ERR_GOTO(err, goto_label, fmt, args...) \ if (err < 0) { \ AVIRT_ERROR_V("Returned error:%d, "fmt, err, ##args); \ retval = err; \ goto goto_label; \ } /** * extracted IOCTLs from */ #define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) #define SNDRV_CTL_IOCTL_CARD_INFO(size) _IOR_HACKED('U', 0x01, size) /** * Checks whether the configfs filesystem is mounted */ #define IS_CONFIGFS_MOUNTED() \ do { \ int err; \ if (!configfs_mounted) { \ err = mount_configfs(); \ if (err < 0) return err; \ } \ } while (0) /** * Write the given formatted string to a file given by path */ #define WRITE_ATTR_TO_DIR(path_dir, attr, fmt, args...) \ ({ \ int __err = 0; \ char __path_attr[AVIRT_CONFIGFS_PATH_MAXLEN]; \ strcpy(__path_attr, path_dir); \ strcat(__path_attr, "/"); \ strcat(__path_attr, attr); \ FILE *__fd = fopen(__path_attr, "w"); \ if (!__fd) { \ AVIRT_ERROR_V("Failed to open file at '%s'", \ __path_attr); \ return -EPERM; \ } \ __err = fprintf(__fd, fmt, ##args); \ __err = fclose(__fd); \ (__err); \ }) /** * Read the given formatted string from a file given by path */ #define READ_ATTR_FROM_DIR(path_dir, attr, fmt, args...) \ ({ \ int __err = 0; \ char __path_attr[AVIRT_CONFIGFS_PATH_MAXLEN]; \ strcpy(__path_attr, path_dir); \ strcat(__path_attr, "/"); \ strcat(__path_attr, attr); \ FILE *__fd = fopen(__path_attr, "r"); \ if (!__fd) { \ AVIRT_ERROR_V("Failed to open file at '%s'", \ __path_attr); \ return -EPERM; \ } \ __err = fscanf(__fd, fmt, ##args); \ __err = fclose(__fd); \ (__err); \ }) static bool configfs_mounted = false; static bool card_configured = false; static int card_index = -1; bool check_dir_empty(char *dirname) { int n = 0; struct dirent *d; DIR *dir = opendir(dirname); if (dir == NULL) // Not a directory or doesn't exist return 1; while ((d = readdir(dir)) != NULL) { if (++n > 2) // Ignore the '.' and '..' directories break; } closedir(dir); if (n <= 2) // Directory Empty return true; return false; } static int mount_configfs() { int err = 0; char fsline[100]; bool configfs_supported = false; FILE *procfs; struct stat st = {0}; // Check for /proc/filesystems for configfs support procfs = fopen("/proc/filesystems", "r"); if (!procfs) return -1; while (fgets(fsline, 100, procfs)) { if (!strstr(fsline, "configfs")) continue; configfs_supported = true; } if (!configfs_supported) { AVIRT_ERROR("configfs is not supported !"); return -1; } // Check whether /config dir exists, if not, create it if (stat("/config", &st) == -1) mkdir("/config", S_IRWXU | S_IRWXG | S_IRWXO); // Check whether configfs is mounted, if not, mount it if (check_dir_empty("/config")) { err = mount("none", "/config", "configfs", 0, NULL); if (!err) { AVIRT_DEBUG("Successfully mounted configfs"); configfs_mounted = true; } else AVIRT_ERROR("Failed to mount configfs filesystem!"); } return err; } static int audiopath_exists(const char *uid) { DIR *dir; char path[AVIRT_SYSFS_PATH_MAXLEN]; // Check that the Audio Paths exist strcpy(path, AVIRT_SYSFS_PATH_AUDIOPATHS); strcat(path, uid); dir = opendir(path); if (dir) { closedir(dir); return 0; } else if (errno == ENOENT) { AVIRT_ERROR_V("Audio Path '%s' does not exist", uid); } else { AVIRT_ERROR("Could not check for Audio Path existence"); } return -errno; } static int find_mixer_selem(const char *name, snd_mixer_t **handle, snd_mixer_elem_t **selem) { int retval = 0; snd_mixer_selem_id_t *sid; char devpath[32]; if (card_index < 0) card_index = snd_avirt_card_index_get(0); sprintf(devpath, "hw:%d", card_index); CHK_ERR(snd_mixer_open(handle, 0), "Couldn't open ctl handle"); CHK_ERR_GOTO(snd_mixer_attach(*handle, devpath), close_handle, "Couldn't attach ctl card '%s'", devpath); CHK_ERR_GOTO(snd_mixer_selem_register(*handle, NULL, NULL), close_handle, "Couldn't register ctl handle"); CHK_ERR_GOTO(snd_mixer_load(*handle), close_handle, "Couldn't load ctl handle"); snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_index(sid, 0); snd_mixer_selem_id_set_name(sid, name); *selem = snd_mixer_find_selem(*handle, sid); if (!(*selem)) { AVIRT_ERROR_V("Cannot open ctl '%s'", name); retval = -1; } return retval; close_handle: retval = snd_mixer_close(*handle); return retval; } static int snd_avirt_configfs_item_new(const char *name, unsigned int direction, char *path, bool internal) { int err; IS_CONFIGFS_MOUNTED(); // Check if card is already configured if (card_configured) { AVIRT_ERROR("Card is already configured!"); return -EPERM; } // This indicates to AVIRT the direction of the item switch (direction) { case SND_PCM_STREAM_PLAYBACK: strcat(path, "playback_"); break; case SND_PCM_STREAM_CAPTURE: strcat(path, "capture_"); break; default: return -EINVAL; } if ((AVIRT_CONFIGFS_PATH_MAXLEN - strlen(path)) < strlen(name)) { AVIRT_ERROR_V("Cannot create config item '%s' since name is too long!", name); return -ENOMEM; } strcat(path, name); if (internal) strcat(path, "__internal"); err = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO); if (err < 0) { AVIRT_ERROR_V("Cannot create config item '%s' at directory '%s'", name, path); return err; } return 0; } int snd_avirt_card_index_get(int idx) { int open_dev, err = 0; snd_ctl_card_info_t *card_info; char path[64]; sprintf(path, AVIRT_DEVICE_PATH ".%d", idx); open_dev = open(path, O_RDONLY); if (open_dev < 0) { AVIRT_ERROR_V("Could not open device with path: %s", path); err = -ENODEV; goto exit_dev; } snd_ctl_card_info_alloca(&card_info); err = ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO(snd_ctl_card_info_sizeof()), card_info); if (err < 0) { AVIRT_ERROR("Could not ioctl card info for AVIRT"); goto exit_dev; } return snd_ctl_card_info_get_card(card_info); exit_dev: close(open_dev); return err; } int snd_avirt_pcm_info(const char *pcm_name, snd_pcm_info_t *pcm_info) { int pcm_dev = -1, err = 0; snd_ctl_t *handle; char name[32]; if (card_index < 0) card_index = snd_avirt_card_index_get(0); if (card_index < 0) return card_index; sprintf(name, "hw:%d", card_index); if ((err = snd_ctl_open(&handle, name, 0)) < 0) { AVIRT_ERROR_V("control open (%i): %s", card_index, snd_strerror(err)); return err; } while (1) { if (snd_ctl_pcm_next_device(handle, &pcm_dev) < 0) AVIRT_ERROR("snd_ctl_pcm_next_device"); if (pcm_dev < 0) { AVIRT_ERROR_V("Cannot find AVIRT device with name: %s", pcm_name) err = -ENODEV; goto exit_ctl; } snd_pcm_info_set_device(pcm_info, pcm_dev); snd_pcm_info_set_subdevice(pcm_info, 0); if ((err = snd_ctl_pcm_info(handle, pcm_info)) < 0) { if (err != -ENOENT) { AVIRT_ERROR_V("control digital audio info (%i): %s", card_index, snd_strerror(err)); } continue; } if (!strcmp(pcm_name, snd_pcm_info_get_name(pcm_info))) break; } exit_ctl: snd_ctl_close(handle); return err; } int snd_avirt_ctl_set_volume(const char *name, long volume) { long min, max; int retval = 0; snd_mixer_t *handle; snd_mixer_elem_t *selem; CHK_ERR(find_mixer_selem(name, &handle, &selem), ""); CHK_ERR_GOTO(snd_mixer_selem_get_playback_volume_range(selem, &min, &max), close_handle, ""); CHK_ERR_GOTO(snd_mixer_selem_set_playback_volume(selem, 0, volume), close_handle, "Couldn't set playback volume for ctl '%s'", name); AVIRT_DEBUG_V("CTLSET: ctl:%s volume:%ld", name, volume * max / 100); close_handle: snd_mixer_close(handle); return retval; } int snd_avirt_ctl_get_volume(const char *name, long *volume) { long min, max; int retval = 0; snd_mixer_t *handle; snd_mixer_elem_t *selem; CHK_ERR(find_mixer_selem(name, &handle, &selem), ""); CHK_ERR_GOTO(snd_mixer_selem_get_playback_volume_range(selem, &min, &max), close_handle, ""); CHK_ERR_GOTO(snd_mixer_selem_get_playback_volume(selem, 0, volume), close_handle, "Couldn't get playback volume for ctl '%s'", name); AVIRT_DEBUG_V("CTLGET: ctl:%s volume:%ld", name, *volume * max / 100); close_handle: snd_mixer_close(handle); return retval; } static int snd_avirt_stream_reset_all() { int err; DIR *d; struct dirent *dir; char path[AVIRT_CONFIGFS_PATH_MAXLEN]; d = opendir(AVIRT_CONFIGFS_PATH_STREAMS); if (d) { while ((dir = readdir(d)) != NULL) { // Ignore the directory . and .. if ((!strcmp(dir->d_name, ".")) || (!strcmp(dir->d_name, ".."))) continue; memset(path, 0, AVIRT_CONFIGFS_PATH_MAXLEN); strcpy(path, AVIRT_CONFIGFS_PATH_STREAMS); strcat(path, dir->d_name); // If not a directory, continue struct stat path_stat; stat(path, &path_stat); if (S_ISREG(path_stat.st_mode)) continue; err = rmdir(path); if (err < 0) { AVIRT_ERROR_V("Cannot remove config item '%s' at directory '%s'", dir->d_name, path); return err; } } AVIRT_DEBUG("Reset streams!"); return 0; } return -EPERM; } int snd_avirt_stream_new(const char *name, unsigned int channels, int direction, const char *map, bool internal) { char path[AVIRT_CONFIGFS_PATH_MAXLEN]; if ((channels > __INT_MAX__) || (channels == 0)) { AVIRT_ERROR_V("Channels '%d' is out of range!", channels); return -ERANGE; } strcpy(path, AVIRT_CONFIGFS_PATH_STREAMS); CHK_ERR(snd_avirt_configfs_item_new(name, direction, path, internal), ""); // Write channels WRITE_ATTR_TO_DIR(path, "channels", "%d", channels); if (map) { // Write mapping WRITE_ATTR_TO_DIR(path, "map", "%s", map); } else { AVIRT_DEBUG("No map specified!"); } AVIRT_DEBUG_V("Created stream: %s, map: %s, chans: %d", name, map, channels); return 0; } int snd_avirt_route_new(const char *name, int channels, int direction, const char *source_ap, const char *sink_ap) { int err; char path[AVIRT_CONFIGFS_PATH_MAXLEN]; if ((channels > __INT_MAX__) || (channels == 0)) { AVIRT_ERROR_V("Channels '%d' is out of range!", channels); return -ERANGE; } // Check that the Audio Paths exist err = audiopath_exists(source_ap); if (err < 0) return err; err = audiopath_exists(sink_ap); if (err < 0) return err; strcpy(path, AVIRT_CONFIGFS_PATH_ROUTES); CHK_ERR(snd_avirt_configfs_item_new(name, direction, path, false), ""); // Write channels WRITE_ATTR_TO_DIR(path, "channels", "%d", channels); // Write route_sink_ap into route_source_ap's 'sink' path WRITE_ATTR_TO_DIR(path, "sink_ap", "%s", sink_ap); // Write route_source_ap into route_sink_ap's 'source' path WRITE_ATTR_TO_DIR(path, "source_ap", "%s", source_ap); CHK_ERR(snd_avirt_stream_new(name, channels, !direction, source_ap, true), "Couldn't create stream: %s", name); CHK_ERR(snd_avirt_stream_new(name, channels, direction, sink_ap, false), "Couldn't create stream: %s", name); AVIRT_DEBUG_V("Created route: %s -> %s", source_ap, sink_ap); return 0; } int snd_avirt_card_configure() { char cmd[128]; snd_pcm_info_t *route_pcm_info; struct snd_avirt_route *routes = NULL; int route_count = 0, i; // Check if card is already configured if (card_configured) { AVIRT_ERROR("Card is already configured!"); return -EPERM; } IS_CONFIGFS_MOUNTED(); WRITE_ATTR_TO_DIR(AVIRT_CONFIGFS_PATH_STREAMS, "configured", "%d", 1); AVIRT_DEBUG("Card configured!"); card_configured = true; if (card_index < 0) card_index = snd_avirt_card_index_get(0); if (card_index < 0) return card_index; // Get any routes, and run the router for them CHK_ERR(snd_avirt_routes(&routes, &route_count), "Get AVIRT routes failed"); for (i = 0; i < route_count; i++) { snd_pcm_info_alloca(&route_pcm_info); CHK_ERR(snd_avirt_pcm_info(routes[i].name, route_pcm_info), "PCM info failed"); sprintf(cmd, "speaker-test -Dhw:%d,%d -c6 >/dev/null &", card_index, snd_pcm_info_get_device(route_pcm_info)); CHK_ERR(system(cmd), "Running router failed: '%s'", cmd); AVIRT_DEBUG_V("Running router: '%s'", cmd); } return 0; } int snd_avirt_card_unconfigure() { // Check if card is already configured if (!card_configured) { AVIRT_ERROR("Card is already unconfigured!"); return -EPERM; } IS_CONFIGFS_MOUNTED(); snd_avirt_stream_reset_all(); WRITE_ATTR_TO_DIR(AVIRT_CONFIGFS_PATH_STREAMS, "configured", "%d", 0); AVIRT_DEBUG("Card unconfigured!"); card_configured = false; if (card_index < 0) card_index = snd_avirt_card_index_get(0); if (card_index < 0) return card_index; return 0; } static int snd_avirt_route_get(const char *path_dir, struct snd_avirt_route *route) { DIR *d; struct dirent *dir; AVIRT_DEBUG_V("Opening route dir: %s", path_dir); d = opendir(path_dir); if (d) { while ((dir = readdir(d)) != NULL) { if ((!strcmp(dir->d_name, ".")) || (!strcmp(dir->d_name, ".."))) continue; if (!strcmp(dir->d_name, "channels")) READ_ATTR_FROM_DIR(path_dir, dir->d_name, "%d", &route->channels); else if (!strcmp(dir->d_name, "direction")) READ_ATTR_FROM_DIR(path_dir, dir->d_name, "%d", &route->direction); else if (!strcmp(dir->d_name, "source_ap")) READ_ATTR_FROM_DIR(path_dir, dir->d_name, "%s", route->source_ap); else if (!strcmp(dir->d_name, "sink_ap")) READ_ATTR_FROM_DIR(path_dir, dir->d_name, "%s", route->sink_ap); } char *name = basename((char *)path_dir); strsep(&name, "_"); strcpy(route->name, name); return 0; } return -ENOENT; } int snd_avirt_routes(struct snd_avirt_route **routes, int *count) { int i; DIR *d; struct dirent *dir; char path_dir[AVIRT_CONFIGFS_PATH_MAXLEN]; struct snd_avirt_route routes_temp[4]; IS_CONFIGFS_MOUNTED(); strcpy(path_dir, AVIRT_CONFIGFS_PATH_ROUTES); d = opendir(path_dir); if (d) { *count = 0; while (((dir = readdir(d)) != NULL) && (*count <= 4)) { if ((!strcmp(dir->d_name, ".")) || (!strcmp(dir->d_name, ".."))) continue; char path_route_dir[AVIRT_CONFIGFS_PATH_MAXLEN]; strcpy(path_route_dir, AVIRT_CONFIGFS_PATH_ROUTES); strcat(path_route_dir, dir->d_name); snd_avirt_route_get(path_route_dir, &routes_temp[*count]); (*count)++; } *routes = malloc(sizeof(struct snd_avirt_route) * (*count)); if (!(*routes)) { AVIRT_ERROR("Failed to alloc memory for snd_avirt_route"); return -EFAULT; } for (i = 0; i < (*count); i++) { strcpy((*routes)[i].name, routes_temp[i].name); strcpy((*routes)[i].sink_ap, routes_temp[i].sink_ap); strcpy((*routes)[i].source_ap, routes_temp[i].source_ap); (*routes)[i].channels = routes_temp[i].channels; (*routes)[i].direction = routes_temp[i].direction; } return 0; } return -ENOENT; }