summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2025-12-19 16:13:48 +0300
committerMark Brown <broonie@kernel.org>2025-12-19 16:13:48 +0300
commit10929de75e6c99cfd91b6fdcd71e9ba5b7b8a572 (patch)
tree68a5960974d666de43e6289159caeb79cb0988f3
parent7a8447fc71a09000cee5a2372b6efde45735d2c8 (diff)
parent2a28b5240f2b328495c6565d277f438dbc583d61 (diff)
downloadlinux-10929de75e6c99cfd91b6fdcd71e9ba5b7b8a572.tar.xz
ASoC: SOF: ipoc4: Support for generic bytes
Merge series from Peter Ujfalusi <peter.ujfalusi@linux.intel.com>: We support bytes control type for set and get, but these are module specific controls and there is no way to handle notifications from them in a generic way. Each control have module specific param_id and this param_id is only valid in the module's scope, other modules might use the same id for different functions for example. This series will add a new generic control type, similar to the existing ones for ENUM and SWITCH, which can be used to create bytes controls which can send notifications from firmware on change. The new param_id is 202 and the sof_ipc4_control_msg_payload is updated to describe bytes payload also. On set, the payload must include the sof_ipc4_control_msg_payload struct with the control's ID and data size, followed by the data. On get, the kernel needs to send the sof_ipc4_control_msg_payload struct along with the LARGE_CONFIG_GET message as payload with the ID of the control that needs to be retrieved. The raw data is received back without additional header. A notification might contain data, in this case the num_elems reflects the size in bytes, or without data. If no data is received then the control is marked as dirty and on read the kernel will refresh the data from firmware. The series includes mandatory fixes for existing code and adds support for sending payload with LARGE_CONFIG_GET when the param_id is either generic ENUM, SWITCH or BYTES control.
-rw-r--r--sound/soc/sof/ipc4-control.c197
-rw-r--r--sound/soc/sof/ipc4-topology.c36
-rw-r--r--sound/soc/sof/ipc4-topology.h9
-rw-r--r--sound/soc/sof/ipc4.c45
4 files changed, 252 insertions, 35 deletions
diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c
index 976a4794d610..0500b690f9a3 100644
--- a/sound/soc/sof/ipc4-control.c
+++ b/sound/soc/sof/ipc4-control.c
@@ -66,7 +66,7 @@ static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol,
* configuration
*/
memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data,
- scontrol->max_size);
+ scontrol->size);
kfree(scontrol->old_ipc_control_data);
scontrol->old_ipc_control_data = NULL;
/* Send the last known good configuration to firmware */
@@ -284,6 +284,105 @@ static void sof_ipc4_refresh_generic_control(struct snd_sof_control *scontrol)
kfree(data);
}
+static int
+sof_ipc4_set_bytes_control_data(struct snd_sof_control *scontrol, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_ipc4_control_msg_payload *msg_data;
+ struct sof_abi_hdr *data = cdata->data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ size_t data_size;
+ int ret;
+
+ data_size = struct_size(msg_data, data, data->size);
+ msg_data = kzalloc(data_size, GFP_KERNEL);
+ if (!msg_data)
+ return -ENOMEM;
+
+ msg_data->id = cdata->index;
+ msg_data->num_elems = data->size;
+ memcpy(msg_data->data, data->data, data->size);
+
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(data->type);
+
+ msg->data_ptr = msg_data;
+ msg->data_size = data_size;
+
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, true, lock);
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+ if (ret < 0)
+ dev_err(scomp->dev, "%s: Failed to set control update for %s\n",
+ __func__, scontrol->name);
+
+ kfree(msg_data);
+
+ return ret;
+}
+
+static int
+sof_ipc4_refresh_bytes_control(struct snd_sof_control *scontrol, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_ipc4_control_msg_payload *msg_data;
+ struct sof_abi_hdr *data = cdata->data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ size_t data_size;
+ int ret = 0;
+
+ if (!scontrol->comp_data_dirty)
+ return 0;
+
+ if (!pm_runtime_active(scomp->dev))
+ return 0;
+
+ data_size = scontrol->max_size - sizeof(*data);
+ if (data_size < sizeof(*msg_data))
+ data_size = sizeof(*msg_data);
+
+ msg_data = kzalloc(data_size, GFP_KERNEL);
+ if (!msg_data)
+ return -ENOMEM;
+
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(data->type);
+
+ msg_data->id = cdata->index;
+ msg_data->num_elems = 0; /* ignored for bytes */
+
+ msg->data_ptr = msg_data;
+ msg->data_size = data_size;
+
+ scontrol->comp_data_dirty = false;
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, false, lock);
+ if (!ret) {
+ if (msg->data_size > scontrol->max_size - sizeof(*data)) {
+ dev_err(scomp->dev,
+ "%s: no space for data in %s (%zu, %zu)\n",
+ __func__, scontrol->name, msg->data_size,
+ scontrol->max_size - sizeof(*data));
+ goto out;
+ }
+
+ data->size = msg->data_size;
+ scontrol->size = sizeof(*cdata) + sizeof(*data) + data->size;
+ memcpy(data->data, msg->data_ptr, data->size);
+ } else {
+ dev_err(scomp->dev, "Failed to read control data for %s\n",
+ scontrol->name);
+ scontrol->comp_data_dirty = true;
+ }
+
+out:
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+
+ kfree(msg_data);
+
+ return ret;
+}
+
static bool sof_ipc4_switch_put(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
@@ -412,19 +511,42 @@ static int sof_ipc4_set_get_bytes_data(struct snd_sof_dev *sdev,
int ret = 0;
/* Send the new data to the firmware only if it is powered up */
- if (set && !pm_runtime_active(sdev->dev))
- return 0;
+ if (set) {
+ if (!pm_runtime_active(sdev->dev))
+ return 0;
+
+ if (!data->size) {
+ dev_dbg(sdev->dev, "%s: No data to be sent.\n",
+ scontrol->name);
+ return 0;
+ }
+ }
+
+ if (data->type == SOF_IPC4_BYTES_CONTROL_PARAM_ID) {
+ if (set)
+ return sof_ipc4_set_bytes_control_data(scontrol, lock);
+ else
+ return sof_ipc4_refresh_bytes_control(scontrol, lock);
+ }
msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(data->type);
msg->data_ptr = data->data;
- msg->data_size = data->size;
+ if (set)
+ msg->data_size = data->size;
+ else
+ msg->data_size = scontrol->max_size - sizeof(*data);
ret = sof_ipc4_set_get_kcontrol_data(scontrol, set, lock);
- if (ret < 0)
+ if (ret < 0) {
dev_err(sdev->dev, "Failed to %s for %s\n",
set ? "set bytes update" : "get bytes",
scontrol->name);
+ } else if (!set) {
+ /* Update the sizes according to the received payload data */
+ data->size = msg->data_size;
+ scontrol->size = sizeof(*cdata) + sizeof(*data) + data->size;
+ }
msg->data_ptr = NULL;
msg->data_size = 0;
@@ -440,6 +562,7 @@ static int sof_ipc4_bytes_put(struct snd_sof_control *scontrol,
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_abi_hdr *data = cdata->data;
size_t size;
+ int ret;
if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
dev_err_ratelimited(scomp->dev,
@@ -461,9 +584,12 @@ static int sof_ipc4_bytes_put(struct snd_sof_control *scontrol,
/* copy from kcontrol */
memcpy(data, ucontrol->value.bytes.data, size);
- sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
+ ret = sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
+ if (!ret)
+ /* Update the cdata size */
+ scontrol->size = sizeof(*cdata) + size;
- return 0;
+ return ret;
}
static int sof_ipc4_bytes_get(struct snd_sof_control *scontrol,
@@ -487,6 +613,8 @@ static int sof_ipc4_bytes_get(struct snd_sof_control *scontrol,
return -EINVAL;
}
+ sof_ipc4_refresh_bytes_control(scontrol, true);
+
size = data->size + sizeof(*data);
/* copy back to kcontrol */
@@ -559,7 +687,7 @@ static int sof_ipc4_bytes_ext_put(struct snd_sof_control *scontrol,
if (!scontrol->old_ipc_control_data) {
/* Create a backup of the current, valid bytes control */
scontrol->old_ipc_control_data = kmemdup(scontrol->ipc_control_data,
- scontrol->max_size, GFP_KERNEL);
+ scontrol->size, GFP_KERNEL);
if (!scontrol->old_ipc_control_data)
return -ENOMEM;
}
@@ -567,12 +695,15 @@ static int sof_ipc4_bytes_ext_put(struct snd_sof_control *scontrol,
/* Copy the whole binary data which includes the ABI header and the payload */
if (copy_from_user(data, tlvd->tlv, header.length)) {
memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data,
- scontrol->max_size);
+ scontrol->size);
kfree(scontrol->old_ipc_control_data);
scontrol->old_ipc_control_data = NULL;
return -EFAULT;
}
+ /* Update the cdata size */
+ scontrol->size = sizeof(*cdata) + header.length;
+
return sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
}
@@ -638,6 +769,8 @@ static int sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data,
unsigned int size)
{
+ sof_ipc4_refresh_bytes_control(scontrol, true);
+
return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, false);
}
@@ -691,6 +824,9 @@ static void sof_ipc4_control_update(struct snd_sof_dev *sdev, void *ipc_message)
case SOF_IPC4_ENUM_CONTROL_PARAM_ID:
type = SND_SOC_TPLG_TYPE_ENUM;
break;
+ case SOF_IPC4_BYTES_CONTROL_PARAM_ID:
+ type = SND_SOC_TPLG_TYPE_BYTES;
+ break;
default:
dev_err(sdev->dev,
"%s: Invalid control type for module %u.%u: %u\n",
@@ -741,23 +877,38 @@ static void sof_ipc4_control_update(struct snd_sof_dev *sdev, void *ipc_message)
* The message includes the updated value/data, update the
* control's local cache using the received notification
*/
- for (i = 0; i < msg_data->num_elems; i++) {
- u32 channel = msg_data->chanv[i].channel;
+ if (type == SND_SOC_TPLG_TYPE_BYTES) {
+ struct sof_abi_hdr *data = cdata->data;
- if (channel >= scontrol->num_channels) {
+ if (msg_data->num_elems > scontrol->max_size - sizeof(*data)) {
dev_warn(sdev->dev,
- "Invalid channel index for %s: %u\n",
- scontrol->name, i);
-
- /*
- * Mark the scontrol as dirty to force a refresh
- * on next read
- */
- scontrol->comp_data_dirty = true;
- break;
+ "%s: no space for data in %s (%u, %zu)\n",
+ __func__, scontrol->name, msg_data->num_elems,
+ scontrol->max_size - sizeof(*data));
+ } else {
+ memcpy(data->data, msg_data->data, msg_data->num_elems);
+ data->size = msg_data->num_elems;
+ scontrol->size = sizeof(*cdata) + sizeof(*data) + data->size;
+ }
+ } else {
+ for (i = 0; i < msg_data->num_elems; i++) {
+ u32 channel = msg_data->chanv[i].channel;
+
+ if (channel >= scontrol->num_channels) {
+ dev_warn(sdev->dev,
+ "Invalid channel index for %s: %u\n",
+ scontrol->name, i);
+
+ /*
+ * Mark the scontrol as dirty to force a refresh
+ * on next read
+ */
+ scontrol->comp_data_dirty = true;
+ break;
+ }
+
+ cdata->chanv[channel].value = msg_data->chanv[i].value;
}
-
- cdata->chanv[channel].value = msg_data->chanv[i].value;
}
} else {
/*
diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c
index 221e9d4052b8..d64e498c6985 100644
--- a/sound/soc/sof/ipc4-topology.c
+++ b/sound/soc/sof/ipc4-topology.c
@@ -2855,22 +2855,41 @@ static int sof_ipc4_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_
struct sof_ipc4_msg *msg;
int ret;
- if (scontrol->max_size < (sizeof(*control_data) + sizeof(struct sof_abi_hdr))) {
- dev_err(sdev->dev, "insufficient size for a bytes control %s: %zu.\n",
+ /*
+ * The max_size is coming from topology and indicates the maximum size
+ * of sof_abi_hdr plus the payload, which excludes the local only
+ * 'struct sof_ipc4_control_data'
+ */
+ if (scontrol->max_size < sizeof(struct sof_abi_hdr)) {
+ dev_err(sdev->dev,
+ "insufficient maximum size for a bytes control %s: %zu.\n",
scontrol->name, scontrol->max_size);
return -EINVAL;
}
- if (scontrol->priv_size > scontrol->max_size - sizeof(*control_data)) {
- dev_err(sdev->dev, "scontrol %s bytes data size %zu exceeds max %zu.\n",
- scontrol->name, scontrol->priv_size,
- scontrol->max_size - sizeof(*control_data));
+ if (scontrol->priv_size > scontrol->max_size) {
+ dev_err(sdev->dev,
+ "bytes control %s initial data size %zu exceeds max %zu.\n",
+ scontrol->name, scontrol->priv_size, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ if (scontrol->priv_size < sizeof(struct sof_abi_hdr)) {
+ dev_err(sdev->dev,
+ "bytes control %s initial data size %zu is insufficient.\n",
+ scontrol->name, scontrol->priv_size);
return -EINVAL;
}
- scontrol->size = sizeof(struct sof_ipc4_control_data) + scontrol->priv_size;
+ /*
+ * The used size behind the cdata pointer, which can be smaller than
+ * the maximum size
+ */
+ scontrol->size = sizeof(*control_data) + scontrol->priv_size;
- scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL);
+ /* Allocate the cdata: local struct size + maximum payload size */
+ scontrol->ipc_control_data = kzalloc(sizeof(*control_data) + scontrol->max_size,
+ GFP_KERNEL);
if (!scontrol->ipc_control_data)
return -ENOMEM;
@@ -2905,6 +2924,7 @@ static int sof_ipc4_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_
msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(control_data->data->type);
return 0;
diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h
index 191b51d97993..9a028a59c553 100644
--- a/sound/soc/sof/ipc4-topology.h
+++ b/sound/soc/sof/ipc4-topology.h
@@ -368,19 +368,24 @@ struct sof_ipc4_control_data {
#define SOF_IPC4_SWITCH_CONTROL_PARAM_ID 200
#define SOF_IPC4_ENUM_CONTROL_PARAM_ID 201
+#define SOF_IPC4_BYTES_CONTROL_PARAM_ID 202
/**
* struct sof_ipc4_control_msg_payload - IPC payload for kcontrol parameters
* @id: unique id of the control
- * @num_elems: Number of elements in the chanv array
+ * @num_elems: Number of elements in the chanv array or number of bytes in data
* @reserved: reserved for future use, must be set to 0
* @chanv: channel ID and value array
+ * @data: binary payload
*/
struct sof_ipc4_control_msg_payload {
uint16_t id;
uint16_t num_elems;
uint32_t reserved[4];
- DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv);
+ union {
+ DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv);
+ DECLARE_FLEX_ARRAY(uint8_t, data);
+ };
} __packed;
/**
diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c
index 1df97129cee6..73d9f2083326 100644
--- a/sound/soc/sof/ipc4.c
+++ b/sound/soc/sof/ipc4.c
@@ -15,6 +15,7 @@
#include "sof-audio.h"
#include "ipc4-fw-reg.h"
#include "ipc4-priv.h"
+#include "ipc4-topology.h"
#include "ipc4-telemetry.h"
#include "ops.h"
@@ -433,6 +434,24 @@ static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_
return ret;
}
+static bool sof_ipc4_tx_payload_for_get_data(struct sof_ipc4_msg *tx)
+{
+ /*
+ * Messages that require TX payload with LARGE_CONFIG_GET.
+ * The TX payload is placed into the IPC message data section by caller,
+ * which needs to be copied to temporary buffer since the received data
+ * will overwrite it.
+ */
+ switch (tx->extension & SOF_IPC4_MOD_EXT_MSG_PARAM_ID_MASK) {
+ case SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_SWITCH_CONTROL_PARAM_ID):
+ case SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_ENUM_CONTROL_PARAM_ID):
+ case SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_BYTES_CONTROL_PARAM_ID):
+ return true;
+ default:
+ return false;
+ }
+}
+
static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data,
size_t payload_bytes, bool set)
{
@@ -444,6 +463,8 @@ static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data,
struct sof_ipc4_msg tx = {{ 0 }};
struct sof_ipc4_msg rx = {{ 0 }};
size_t remaining = payload_bytes;
+ void *tx_payload_for_get = NULL;
+ size_t tx_data_size = 0;
size_t offset = 0;
size_t chunk_size;
int ret;
@@ -469,10 +490,20 @@ static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data,
tx.extension |= SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK(1);
+ if (sof_ipc4_tx_payload_for_get_data(&tx)) {
+ tx_data_size = min(ipc4_msg->data_size, payload_limit);
+ tx_payload_for_get = kmemdup(ipc4_msg->data_ptr, tx_data_size,
+ GFP_KERNEL);
+ if (!tx_payload_for_get)
+ return -ENOMEM;
+ }
+
/* ensure the DSP is in D0i0 before sending IPC */
ret = snd_sof_dsp_set_power_state(sdev, &target_state);
- if (ret < 0)
+ if (ret < 0) {
+ kfree(tx_payload_for_get);
return ret;
+ }
/* Serialise IPC TX */
mutex_lock(&sdev->ipc->tx_mutex);
@@ -506,7 +537,15 @@ static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data,
rx.data_size = chunk_size;
rx.data_ptr = ipc4_msg->data_ptr + offset;
- tx_size = 0;
+ if (tx_payload_for_get) {
+ tx_size = tx_data_size;
+ tx.data_size = tx_size;
+ tx.data_ptr = tx_payload_for_get;
+ } else {
+ tx_size = 0;
+ tx.data_size = 0;
+ tx.data_ptr = NULL;
+ }
rx_size = chunk_size;
}
@@ -553,6 +592,8 @@ out:
mutex_unlock(&sdev->ipc->tx_mutex);
+ kfree(tx_payload_for_get);
+
return ret;
}