sof/tools/plugin/alsaplug/ctl.c

856 lines
23 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2022 Intel Corporation. All rights reserved.
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
#include <stdio.h>
#include <sys/poll.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <assert.h>
#include <errno.h>
// TODO remove parsing and read ctls from sof-pipe SHM glb context
#include <ipc/control.h>
#include <alsa/asoundlib.h>
#include <alsa/control_external.h>
#include "plugin.h"
#include "common.h"
typedef struct snd_sof_ctl {
struct plug_shm_glb_state *glb;
snd_ctl_ext_t ext;
struct plug_mq_desc ipc_tx;
struct plug_mq_desc ipc_rx;
struct plug_shm_desc shm_ctx;
int subscribed;
int updated[MAX_CTLS];
} snd_sof_ctl_t;
#define CTL_GET_TPLG_HDR(_ctl, _key) \
(&_ctl->glb->ctl[_key].mixer_ctl.hdr)
#define CTL_GET_TPLG_MIXER(_ctl, _key) \
(&_ctl->glb->ctl[_key].mixer_ctl)
#define CTL_GET_TPLG_ENUM(_ctl, _key) \
(&_ctl->glb->ctl[_key].enum_ctl)
#define CTL_GET_TPLG_BYTES(_ctl, _key) \
(&_ctl->glb->ctl[_key].bytes_ctl)
static uint32_t mixer_to_ipc(unsigned int value, uint32_t *volume_table, int size)
{
if (value >= size)
return volume_table[size - 1];
return volume_table[value];
}
static uint32_t ipc_to_mixer(uint32_t value, uint32_t *volume_table, int size)
{
int i;
for (i = 0; i < size; i++) {
if (volume_table[i] >= value)
return i;
}
return i - 1;
}
/* number of ctls */
static int plug_ctl_elem_count(snd_ctl_ext_t *ext)
{
snd_sof_ctl_t *ctl = ext->private_data;
/* TODO: get count of elems from topology */
return ctl->glb->num_ctls;
}
static int plug_ctl_elem_list(snd_ctl_ext_t *ext, unsigned int offset, snd_ctl_elem_id_t *id)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_ctl_hdr *hdr;
if (offset >= ctl->glb->num_ctls)
return -EINVAL;
hdr = CTL_GET_TPLG_HDR(ctl, offset);
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, hdr->name);
return 0;
}
static snd_ctl_ext_key_t plug_ctl_find_elem(snd_ctl_ext_t *ext, const snd_ctl_elem_id_t *id)
{
snd_sof_ctl_t *ctl = ext->private_data;
unsigned int numid;
numid = snd_ctl_elem_id_get_numid(id);
if (numid > ctl->glb->num_ctls)
return SND_CTL_EXT_KEY_NOT_FOUND;
return numid - 1;
}
static int plug_ctl_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
int *type, unsigned int *acc, unsigned int *count)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key);
struct snd_soc_tplg_mixer_control *mixer_ctl;
struct snd_soc_tplg_enum_control *enum_ctl;
struct snd_soc_tplg_bytes_control *bytes_ctl;
int err = 0;
switch (hdr->ops.info) {
case SND_SOC_TPLG_CTL_VOLSW:
case SND_SOC_TPLG_CTL_VOLSW_SX:
case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
mixer_ctl = (struct snd_soc_tplg_mixer_control *)hdr;
/* check for type - boolean should be binary values */
if (mixer_ctl->max == 1 && mixer_ctl->min == 0)
*type = SND_CTL_ELEM_TYPE_BOOLEAN;
else
*type = SND_CTL_ELEM_TYPE_INTEGER;
*count = 2;//mixer_ctl->num_channels; ///// WRONG is 0 !!!
//printf("mixer %d %d\n", __LINE__, mixer_ctl->num_channels);
break;
case SND_SOC_TPLG_CTL_ENUM:
case SND_SOC_TPLG_CTL_ENUM_VALUE:
enum_ctl = (struct snd_soc_tplg_enum_control *)hdr;
*type = SND_CTL_ELEM_TYPE_ENUMERATED;
*count = enum_ctl->items;
break;
case SND_SOC_TPLG_CTL_RANGE:
case SND_SOC_TPLG_CTL_STROBE:
// TODO: ??
break;
case SND_SOC_TPLG_CTL_BYTES:
bytes_ctl = (struct snd_soc_tplg_bytes_control *)hdr;
*type = SND_CTL_ELEM_TYPE_BYTES;
*count = bytes_ctl->max;
break;
}
*acc = hdr->access;
/* access needs the callback to decode the data */
if ((hdr->access & SND_CTL_EXT_ACCESS_TLV_READ) ||
(hdr->access & SND_CTL_EXT_ACCESS_TLV_WRITE))
*acc |= SND_CTL_EXT_ACCESS_TLV_CALLBACK;
return err;
}
/*
* Integer ops
*/
static int plug_ctl_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *imin,
long *imax, long *istep)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key);
struct snd_soc_tplg_mixer_control *mixer_ctl =
CTL_GET_TPLG_MIXER(ctl, key);
int err = 0;
switch (hdr->type) {
case SND_SOC_TPLG_CTL_VOLSW:
case SND_SOC_TPLG_CTL_VOLSW_SX:
case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
/* TLV uses the fields differently */
if ((hdr->access & SND_CTL_EXT_ACCESS_TLV_READ) ||
(hdr->access & SND_CTL_EXT_ACCESS_TLV_WRITE)) {
*istep = mixer_ctl->hdr.tlv.scale.step;
*imin = (int32_t)mixer_ctl->hdr.tlv.scale.min;
*imax = mixer_ctl->max;
} else {
*istep = 1;
*imin = mixer_ctl->min;
*imax = mixer_ctl->max;
}
break;
default:
SNDERR("invalid ctl type for integer using key %d", key);
err = -EINVAL;
break;
}
return err;
}
static int plug_ctl_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_mixer_control *mixer_ctl = CTL_GET_TPLG_MIXER(ctl, key);
struct ipc4_module_large_config config = {{ 0 }};
struct ipc4_peak_volume_config *volume;
struct ipc4_module_large_config_reply *reply;
char *reply_data;
void *msg;
int reply_data_size, size;
int num_data_items;
int i, err;
/* configure the IPC message */
plug_ctl_ipc_message(&config, IPC4_VOLUME, sizeof(volume), ctl->glb->ctl[key].module_id,
ctl->glb->ctl[key].instance_id, SOF_IPC4_MOD_LARGE_CONFIG_GET);
config.extension.r.final_block = 1;
config.extension.r.init_block = 1;
size = sizeof(config);
msg = calloc(size, 1);
if (!msg)
return -ENOMEM;
/* reply contains both the requested data and the reply status */
reply_data_size = sizeof(*reply) + mixer_ctl->num_channels * sizeof(*volume);
reply_data = calloc(reply_data_size, 1);
if (!reply_data_size) {
free(msg);
return -ENOMEM;
}
/* send the IPC message */
memcpy(msg, &config, sizeof(config));
err = plug_mq_cmd_tx_rx(&ctl->ipc_tx, &ctl->ipc_rx,
msg, size, reply_data, reply_data_size);
free(msg);
if (err < 0) {
SNDERR("failed to set volume for control %s\n", mixer_ctl->hdr.name);
goto out;
}
reply = (struct ipc4_module_large_config_reply *)reply_data;
if (reply->primary.r.status != IPC4_SUCCESS) {
SNDERR("volume control %s set failed with status %d\n",
mixer_ctl->hdr.name, reply->primary.r.status);
err = -EINVAL;
goto out;
}
/* check data sanity */
num_data_items = reply->extension.r.data_off_size / sizeof(*volume);
if (num_data_items != mixer_ctl->num_channels) {
SNDERR("Channel count %d doesn't match the expected value %d\n",
num_data_items, mixer_ctl->num_channels);
err = -EINVAL;
goto out;
}
/* set the mixer values based on the received data */
volume = (struct ipc4_peak_volume_config *)(reply_data + sizeof(*reply));
for (i = 0; i < mixer_ctl->num_channels; i++)
value[i] = ipc_to_mixer(volume[i].target_volume, ctl->glb->ctl[key].volume_table,
mixer_ctl->max + 1);
out:
free(reply_data);
return err;
}
static int plug_ctl_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_mixer_control *mixer_ctl = CTL_GET_TPLG_MIXER(ctl, key);
struct ipc4_module_large_config config = {{ 0 }};
struct ipc4_peak_volume_config volume;
struct ipc4_message_reply reply;
bool all_channels_equal = true;
uint32_t val;
void *msg;
int size, err;
int i;
/* set data for IPC */
val = value[0];
for (i = 1; i < mixer_ctl->num_channels; i++) {
if (value[i] != val) {
all_channels_equal = false;
break;
}
}
/*
* if all channels have the same volume, send a single IPC, else, send individual IPCs
* for each channel
*/
for (i = 0; i < mixer_ctl->num_channels; i++) {
if (all_channels_equal) {
volume.channel_id = IPC4_ALL_CHANNELS_MASK;
volume.target_volume = mixer_to_ipc(val, ctl->glb->ctl[key].volume_table,
mixer_ctl->max + 1);
} else {
volume.channel_id = i;
volume.target_volume = mixer_to_ipc(value[i],
ctl->glb->ctl[key].volume_table,
mixer_ctl->max + 1);
}
/* TODO: get curve duration and type from topology */
volume.curve_type = 1;
volume.curve_duration = 200000;
/* configure the IPC message */
plug_ctl_ipc_message(&config, IPC4_VOLUME, sizeof(volume),
ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id,
SOF_IPC4_MOD_LARGE_CONFIG_SET);
config.extension.r.final_block = 1;
config.extension.r.init_block = 1;
size = sizeof(config) + sizeof(volume);
msg = calloc(size, 1);
if (!msg)
return -ENOMEM;
memcpy(msg, &config, sizeof(config));
memcpy(msg + sizeof(config), &volume, sizeof(volume));
/* send the message and check status */
err = plug_mq_cmd_tx_rx(&ctl->ipc_tx, &ctl->ipc_rx,
msg, size, &reply, sizeof(reply));
free(msg);
if (err < 0) {
SNDERR("failed to set volume control %s\n", mixer_ctl->hdr.name);
return err;
}
if (reply.primary.r.status != IPC4_SUCCESS) {
SNDERR("volume control %s set failed with status %d\n",
mixer_ctl->hdr.name, reply.primary.r.status);
return -EINVAL;
}
if (all_channels_equal)
break;
}
return 0;
}
/*
* Enum ops
*/
static int plug_ctl_get_enumerated_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
unsigned int *items)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key);
struct snd_soc_tplg_enum_control *enum_ctl =
(struct snd_soc_tplg_enum_control *)hdr;
int err = 0;
switch (hdr->ops.info) {
case SND_SOC_TPLG_CTL_ENUM:
case SND_SOC_TPLG_CTL_ENUM_VALUE:
*items = enum_ctl->items;
break;
default:
SNDERR("invalid ctl type for enum using key %d", key);
err = -EINVAL;
break;
}
return err;
}
static int plug_ctl_get_enumerated_name(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
unsigned int item, char *name, size_t name_max_len)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_ctl_hdr *hdr = CTL_GET_TPLG_HDR(ctl, key);
struct snd_soc_tplg_enum_control *enum_ctl =
(struct snd_soc_tplg_enum_control *)hdr;
if (item >= enum_ctl->items) {
SNDERR("invalid item %d for enum using key %d\n", item, key);
return -EINVAL;
}
strncpy(name, enum_ctl->texts[item], name_max_len);
return 0;
}
static int plug_ctl_read_enumerated(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
unsigned int *items)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_enum_control *enum_ctl = CTL_GET_TPLG_ENUM(ctl, key);
struct ipc4_module_large_config config = {{ 0 }};
struct ipc4_module_large_config_reply *reply;
struct sof_ipc4_control_msg_payload *data;
char *reply_data;
void *msg;
int size, reply_data_size;
int i, err;
/* configure the IPC message */
plug_ctl_ipc_message(&config, SOF_IPC4_ENUM_CONTROL_PARAM_ID, 0,
ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id,
SOF_IPC4_MOD_LARGE_CONFIG_GET);
config.extension.r.final_block = 1;
config.extension.r.init_block = 1;
size = sizeof(config);
msg = calloc(size, 1);
if (!msg)
return -ENOMEM;
/* reply contains both the requested data and the reply status */
reply_data_size = sizeof(*reply) + sizeof(*data) +
enum_ctl->num_channels * sizeof(data->chanv[0]);
reply_data = calloc(reply_data_size, 1);
if (!reply_data_size) {
free(msg);
return -ENOMEM;
}
/* send the IPC message */
memcpy(msg, &config, sizeof(config));
err = plug_mq_cmd_tx_rx(&ctl->ipc_tx, &ctl->ipc_rx,
msg, size, reply_data, reply_data_size);
free(msg);
if (err < 0) {
SNDERR("failed to get enum items for control %s\n", enum_ctl->hdr.name);
goto out;
}
reply = (struct ipc4_module_large_config_reply *)reply_data;
if (reply->primary.r.status != IPC4_SUCCESS) {
SNDERR("enum control %s get failed with status %d\n",
enum_ctl->hdr.name, reply->primary.r.status);
err = -EINVAL;
goto out;
}
/* check data sanity */
data = (struct sof_ipc4_control_msg_payload *)(reply_data + sizeof(*reply));
if (data->num_elems != enum_ctl->num_channels) {
SNDERR("Channel count %d doesn't match the expected value %d for enum ctl %s\n",
data->num_elems, enum_ctl->num_channels, enum_ctl->hdr.name);
err = -EINVAL;
goto out;
}
/* set the enum items based on the received data */
for (i = 0; i < enum_ctl->num_channels; i++)
items[i] = data->chanv[i].value;
out:
free(reply_data);
return 0;
}
static int plug_ctl_write_enumerated(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
unsigned int *items)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_enum_control *enum_ctl = CTL_GET_TPLG_ENUM(ctl, key);
struct ipc4_module_large_config config = {{ 0 }};
struct sof_ipc4_control_msg_payload *data;
struct ipc4_message_reply reply;
void *msg;
int data_size, msg_size;
int err, i;
/* size of control data */
data_size = enum_ctl->num_channels * sizeof(struct sof_ipc4_ctrl_value_chan) +
sizeof(*data);
/* allocate memory for control data */
data = calloc(data_size, 1);
if (!data)
return -ENOMEM;
/* set param ID and number of channels */
data->id = ctl->glb->ctl[key].index;
data->num_elems = enum_ctl->num_channels;
/* set the enum values */
for (i = 0; i < data->num_elems; i++) {
data->chanv[i].channel = i;
data->chanv[i].value = items[i];
}
/* configure the IPC message */
plug_ctl_ipc_message(&config, SOF_IPC4_ENUM_CONTROL_PARAM_ID, data_size,
ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id,
SOF_IPC4_MOD_LARGE_CONFIG_SET);
/*
* enum controls can have a maximum of 16 texts/values. So the entire data can be sent
* in a single IPC message
*/
config.extension.r.final_block = 1;
config.extension.r.init_block = 1;
/* allocate memory for IPC message */
msg_size = sizeof(config) + data_size;
msg = calloc(msg_size, 1);
if (!msg) {
free(data);
return -ENOMEM;
}
/* set the IPC message data */
memcpy(msg, &config, sizeof(config));
memcpy(msg + sizeof(config), data, data_size);
free(data);
/* send the message and check status */
err = plug_mq_cmd_tx_rx(&ctl->ipc_tx, &ctl->ipc_rx, msg, msg_size, &reply, sizeof(reply));
free(msg);
if (err < 0) {
SNDERR("failed to set enum control %s\n", enum_ctl->hdr.name);
return err;
}
if (reply.primary.r.status != IPC4_SUCCESS) {
SNDERR("enum control %s set failed with status %d\n",
enum_ctl->hdr.name, reply.primary.r.status);
return -EINVAL;
}
return 0;
}
/*
* Bytes ops
*/
static int plug_ctl_get_bytes_data(snd_sof_ctl_t *ctl, snd_ctl_ext_key_t key,
struct sof_abi_hdr *abi, unsigned int max_bytes)
{
struct snd_soc_tplg_bytes_control *bytes_ctl = CTL_GET_TPLG_BYTES(ctl, key);
struct ipc4_module_large_config config = {{ 0 }};
struct ipc4_module_large_config_reply *reply;
char *reply_data, *data;
void *msg;
uint32_t data_size;
int size, reply_data_size;
int err;
/* configure the IPC message */
plug_ctl_ipc_message(&config, abi->type, 0,
ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id,
SOF_IPC4_MOD_LARGE_CONFIG_GET);
config.extension.r.final_block = 1;
config.extension.r.init_block = 1;
size = sizeof(config);
msg = calloc(size, 1);
if (!msg)
return -ENOMEM;
/*
* reply contains both the requested data and the reply status. Allocate enough memory
* for max data
*/
reply_data_size = sizeof(*reply) + sizeof(*data) + bytes_ctl->max;
reply_data = calloc(reply_data_size, 1);
if (!reply_data_size) {
free(msg);
return -ENOMEM;
}
/* send the IPC message */
memcpy(msg, &config, sizeof(config));
err = plug_mq_cmd_tx_rx(&ctl->ipc_tx, &ctl->ipc_rx,
msg, size, reply_data, reply_data_size);
free(msg);
if (err < 0) {
SNDERR("failed to get bytes data for control %s\n", bytes_ctl->hdr.name);
goto out;
}
reply = (struct ipc4_module_large_config_reply *)reply_data;
if (reply->primary.r.status != IPC4_SUCCESS) {
SNDERR("bytes control %s get failed with status %d\n",
bytes_ctl->hdr.name, reply->primary.r.status);
err = -EINVAL;
goto out;
}
/* check data sanity */
data = (char *)(reply_data + sizeof(*reply));
data_size = reply->extension.r.data_off_size;
if (data_size > bytes_ctl->max) {
SNDERR("received data size %d is larger than max %d for bytes control %s\n",
data_size, bytes_ctl->max, bytes_ctl->hdr.name);
err = -EINVAL;
goto out;
}
abi->size = data_size;
if (data_size)
memcpy(abi->data, data, MIN(data_size, max_bytes));
err = data_size;
out:
free(reply_data);
return err;
}
static int plug_ctl_read_bytes(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
unsigned char *data, size_t max_bytes)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct sof_abi_hdr *abi = (struct sof_abi_hdr *)data;
int data_size;
data_size = plug_ctl_get_bytes_data(ctl, key, abi, max_bytes);
if (data_size < 0)
return data_size;
return 0;
}
static int plug_ctl_write_bytes(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
unsigned char *data, size_t max_bytes)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_bytes_control *bytes_ctl = CTL_GET_TPLG_BYTES(ctl, key);
struct sof_abi_hdr *abi = (struct sof_abi_hdr *)data;
int err;
/* send IPC with kcontrol data */
err = plug_send_bytes_data(&ctl->ipc_tx, &ctl->ipc_rx,
ctl->glb->ctl[key].module_id, ctl->glb->ctl[key].instance_id,
abi);
if (err < 0) {
SNDERR("failed to set bytes data for control %s\n", bytes_ctl->hdr.name);
return err;
}
return 0;
}
/* TLV ops used for TLV bytes control callback */
static int plug_tlv_rw(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, int op_flag,
unsigned int numid, unsigned int *tlv, unsigned int tlv_size)
{
snd_sof_ctl_t *ctl = ext->private_data;
struct snd_soc_tplg_bytes_control *bytes_ctl = CTL_GET_TPLG_BYTES(ctl, key);
struct sof_abi_hdr *abi = (struct sof_abi_hdr *)(tlv + 2); /* skip TLV header */
int data_size;
/* send IPC with kcontrol data if op_flag is > 0 else send IPC to get kcontrol data */
if (op_flag) {
int err;
err = plug_send_bytes_data(&ctl->ipc_tx, &ctl->ipc_rx,
ctl->glb->ctl[key].module_id,
ctl->glb->ctl[key].instance_id, abi);
if (err < 0) {
SNDERR("failed to set bytes data for control %s\n", bytes_ctl->hdr.name);
return err;
}
return 0;
}
/* read kcontrol data */
data_size = plug_ctl_get_bytes_data(ctl, key, abi, tlv_size);
if (data_size < 0)
return data_size;
/* set data size and numid */
tlv[0] = numid;
tlv[1] = data_size + sizeof(*abi);
return 0;
}
static void plug_ctl_subscribe_events(snd_ctl_ext_t *ext, int subscribe)
{
snd_sof_ctl_t *ctl = ext->private_data;
ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE);
}
static int plug_ctl_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id,
unsigned int *event_mask)
{
snd_sof_ctl_t *ctl = ext->private_data;
int numid;
int err = 0;
numid = snd_ctl_elem_id_get_numid(id);
// TODO: we need a notify() or listening thread to take async/volatile ctl
// notifications from sof-pipe and notify userspace via events of the ctl change.
if (!ctl->updated[numid - 1] || !ctl->subscribed) {
err = -EAGAIN;
goto out;
}
*event_mask = SND_CTL_EVENT_MASK_VALUE;
out:
return err;
}
static int plug_ctl_poll_revents(snd_ctl_ext_t *ext, struct pollfd *pfd,
unsigned int nfds, unsigned short *revents)
{
snd_sof_ctl_t *ctl = ext->private_data;
int i;
*revents = 0;
for (i = 0; i < ctl->glb->num_ctls; i++) {
if (ctl->updated[i]) {
*revents = POLLIN;
break;
}
}
return 0;
}
static void plug_ctl_close(snd_ctl_ext_t *ext)
{
snd_sof_ctl_t *ctl = ext->private_data;
/* TODO: munmap */
free(ctl);
}
static const snd_ctl_ext_callback_t sof_ext_callback = {
.elem_count = plug_ctl_elem_count,
.elem_list = plug_ctl_elem_list,
.find_elem = plug_ctl_find_elem,
.get_attribute = plug_ctl_get_attribute,
.get_integer_info = plug_ctl_get_integer_info,
.read_integer = plug_ctl_read_integer,
.write_integer = plug_ctl_write_integer,
.get_enumerated_info = plug_ctl_get_enumerated_info,
.get_enumerated_name = plug_ctl_get_enumerated_name,
.read_enumerated = plug_ctl_read_enumerated,
.write_enumerated = plug_ctl_write_enumerated,
.read_bytes = plug_ctl_read_bytes,
.write_bytes = plug_ctl_write_bytes,
.subscribe_events = plug_ctl_subscribe_events,
.read_event = plug_ctl_read_event,
.poll_revents = plug_ctl_poll_revents,
.close = plug_ctl_close,
};
SND_CTL_PLUGIN_DEFINE_FUNC(sof)
{
snd_sof_plug_t *plug;
int err;
snd_sof_ctl_t *ctl;
/* create context */
plug = calloc(1, sizeof(*plug));
if (!plug)
return -ENOMEM;
ctl = calloc(1, sizeof(*ctl));
if (!ctl)
return -ENOMEM;
plug->module_prv = ctl;
/* parse the ALSA configuration file for sof plugin */
err = plug_parse_conf(plug, name, root, conf, true);
if (err < 0) {
SNDERR("failed to parse config: %s", strerror(err));
goto error;
}
/* init IPC tx message queue name */
err = plug_mq_init(&ctl->ipc_tx, "sof", "ipc-tx", 0);
if (err < 0) {
SNDERR("error: invalid name for IPC tx mq %s\n", plug->tplg_file);
goto error;
}
/* open the sof-pipe IPC tx message queue */
err = plug_mq_open(&ctl->ipc_tx);
if (err < 0) {
SNDERR("error: failed to open sof-pipe IPC mq %s: %s",
ctl->ipc_tx.queue_name, strerror(err));
goto error;
}
/* init IPC rx message queue name */
err = plug_mq_init(&ctl->ipc_rx, "sof", "ipc-rx", 0);
if (err < 0) {
SNDERR("error: invalid name for IPC rx mq %s\n", plug->tplg_file);
goto error;
}
/* open the sof-pipe IPC rx message queue */
err = plug_mq_open(&ctl->ipc_rx);
if (err < 0) {
SNDERR("error: failed to open sof-pipe IPC mq %s: %s",
ctl->ipc_rx.queue_name, strerror(err));
goto error;
}
/* create a SHM mapping for low latency stream position */
err = plug_shm_init(&ctl->shm_ctx, plug->tplg_file, "ctx", 0);
if (err < 0)
goto error;
// TODO: make this open/close per operation for shared access
/* create a SHM mapping for low latency stream position */
err = plug_shm_open(&ctl->shm_ctx);
if (err < 0)
goto error;
/* get global context for kcontrol lookup */
ctl->glb = ctl->shm_ctx.addr;
/* TODO: add some flavour to the names based on the topology */
ctl->ext.version = SND_CTL_EXT_VERSION;
ctl->ext.card_idx = 0;
strncpy(ctl->ext.id, "sof", sizeof(ctl->ext.id) - 1);
strncpy(ctl->ext.driver, "SOF plugin",
sizeof(ctl->ext.driver) - 1);
strncpy(ctl->ext.name, "SOF", sizeof(ctl->ext.name) - 1);
strncpy(ctl->ext.mixername, "SOF",
sizeof(ctl->ext.mixername) - 1);
/* polling on message queue - supported on Linux but not portable */
ctl->ext.poll_fd = ctl->ipc_tx.mq;
ctl->ext.callback = &sof_ext_callback;
ctl->ext.private_data = ctl;
ctl->ext.tlv.c = plug_tlv_rw;
err = snd_ctl_ext_create(&ctl->ext, name, mode);
if (err < 0)
goto error;
*handlep = ctl->ext.handle;
return 0;
error:
free(ctl);
return err;
}
SND_CTL_PLUGIN_SYMBOL(sof);