// SPDX-License-Identifier: BSD-3-Clause // // Copyright(c) 2022 Intel Corporation. All rights reserved. // // Author: Liam Girdwood #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO remove parsing and read ctls from sof-pipe SHM glb context #include #include #include #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);