diff --git a/include/sound/sof.h b/include/sound/sof.h index 813680ab9aad..7cdfc954df12 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -39,6 +39,14 @@ enum sof_fw_state { SOF_FW_CRASHED, }; +/* DSP power states */ +enum sof_dsp_power_states { + SOF_DSP_PM_D0, + SOF_DSP_PM_D1, + SOF_DSP_PM_D2, + SOF_DSP_PM_D3, +}; + /* * SOF Platform data. */ diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 1a7d6cefd3b7..4542868cd730 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -53,13 +53,21 @@ config SND_SOC_SOF_COMPRESS select SND_SOC_COMPRESS config SND_SOC_SOF_DEBUG_PROBES - bool "SOF enable data probing" + tristate + select SND_SOC_SOF_CLIENT select SND_SOC_COMPRESS help This option enables the data probing feature that can be used to gather data directly from specific points of the audio pipeline. - Say Y if you want to enable probes. - If unsure, select "N". + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_CLIENT + tristate + select AUXILIARY_BUS + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. config SND_SOC_SOF_DEVELOPER_SUPPORT bool "SOF developer options support" @@ -187,15 +195,26 @@ config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE If unsure, select "N". config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST - bool "SOF enable IPC flood test" + tristate "SOF enable IPC flood test" + select SND_SOC_SOF_CLIENT help - This option enables the IPC flood test which can be used to flood - the DSP with test IPCs and gather stats about response times. + This option enables a separate client device for IPC flood test + which can be used to flood the DSP with test IPCs and gather stats + about response times. Say Y if you want to enable IPC flood test. If unsure, select "N". +config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM + int "Number of IPC flood test clients" + range 1 32 + default 2 + depends on SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST + help + Select the number of IPC flood test clients to be created. + config SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR - bool "SOF enable IPC message injector" + tristate "SOF enable IPC message injector" + select SND_SOC_SOF_CLIENT help This option enables the IPC message injector which can be used to send crafted IPC messages to the DSP to test its robustness. diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 964b429146be..4d31282c847d 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,25 +1,36 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ - control.o trace.o utils.o sof-audio.o stream-ipc.o + control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o +snd-sof-$(CONFIG_SND_SOC_SOF_CLIENT) += sof-client.o -snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += sof-probes.o snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o snd-sof-of-objs := sof-of-dev.o +snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o +snd-sof-ipc-msg-injector-objs := sof-client-ipc-msg-injector.o +snd-sof-probes-objs := sof-client-probes.o + snd-sof-nocodec-objs := nocodec.o +snd-sof-utils-objs := sof-utils.o + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o +obj-$(CONFIG_SND_SOC_SOF) += snd-sof-utils.o obj-$(CONFIG_SND_SOC_SOF_ACPI_DEV) += snd-sof-acpi.o obj-$(CONFIG_SND_SOC_SOF_OF_DEV) += snd-sof-of.o obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o + obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ obj-$(CONFIG_SND_SOC_SOF_AMD_TOPLEVEL) += amd/ diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 91a9c95929cd..2137d1d65dd9 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -9,6 +9,7 @@ #include #include "sof-audio.h" #include "sof-priv.h" +#include "sof-utils.h" static void sof_set_transferred_bytes(struct snd_compr_tstamp *tstamp, u64 host_pos, u64 buffer_size) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 8f32b5b12b3e..d99ecbb4282d 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -14,9 +14,6 @@ #include #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" -#endif /* see SOF_DBG_ flags */ static int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); @@ -122,6 +119,27 @@ out: } EXPORT_SYMBOL(sof_print_oops_and_stack); +/* Helper to manage DSP state */ +void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state) +{ + if (sdev->fw_state == new_state) + return; + + dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); + sdev->fw_state = new_state; + + switch (new_state) { + case SOF_FW_BOOT_NOT_STARTED: + case SOF_FW_BOOT_COMPLETE: + case SOF_FW_CRASHED: + sof_client_fw_state_dispatcher(sdev); + fallthrough; + default: + break; + } +} +EXPORT_SYMBOL(sof_set_fw_state); + /* * FW Boot State Transition Diagram * @@ -266,6 +284,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_trace_err; } + ret = sof_register_clients(sdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to register clients %d\n", ret); + goto sof_machine_err; + } + /* * Some platforms in SOF, ex: BYT, may not have their platform PM * callbacks set. Increment the usage count so as to @@ -281,6 +305,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) return 0; +sof_machine_err: + snd_sof_machine_unregister(sdev, plat_data); fw_trace_err: snd_sof_free_trace(sdev); fw_run_err: @@ -329,10 +355,6 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->pdata = plat_data; sdev->first_boot = true; - sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; -#endif dev_set_drvdata(dev, sdev); /* check all mandatory ops */ @@ -350,9 +372,14 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->ipc_client_list); + INIT_LIST_HEAD(&sdev->ipc_rx_handler_list); + INIT_LIST_HEAD(&sdev->fw_state_handler_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); mutex_init(&sdev->power_state_access); + mutex_init(&sdev->ipc_client_mutex); + mutex_init(&sdev->client_event_handler_mutex); /* set default timeouts if none provided */ if (plat_data->desc->ipc_timeout == 0) @@ -364,6 +391,8 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) else sdev->boot_timeout = plat_data->desc->boot_timeout; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { INIT_WORK(&sdev->probe_work, sof_probe_work); schedule_work(&sdev->probe_work); @@ -391,6 +420,12 @@ int snd_sof_device_remove(struct device *dev) if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work); + /* + * Unregister any registered client device first before IPC and debugfs + * to allow client drivers to be removed cleanly + */ + sof_unregister_clients(sdev); + /* * Unregister machine driver. This will unbind the snd_card which * will remove the component driver and unload the topology @@ -443,3 +478,4 @@ MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:sof-audio"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 6d6757075f7c..145fd0d1e166 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -19,433 +19,9 @@ #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" - -/** - * strsplit_u32 - Split string into sequence of u32 tokens - * @buf: String to split into tokens. - * @delim: String containing delimiter characters. - * @tkns: Returned u32 sequence pointer. - * @num_tkns: Returned number of tokens obtained. - */ -static int -strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns) -{ - char *s; - u32 *data, *tmp; - size_t count = 0; - size_t cap = 32; - int ret = 0; - - *tkns = NULL; - *num_tkns = 0; - data = kcalloc(cap, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - while ((s = strsep(buf, delim)) != NULL) { - ret = kstrtouint(s, 0, data + count); - if (ret) - goto exit; - if (++count >= cap) { - cap *= 2; - tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); - if (!tmp) { - ret = -ENOMEM; - goto exit; - } - data = tmp; - } - } - - if (!count) - goto exit; - *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); - if (*tkns == NULL) { - ret = -ENOMEM; - goto exit; - } - *num_tkns = count; - -exit: - kfree(data); - return ret; -} - -static int tokenize_input(const char __user *from, size_t count, - loff_t *ppos, u32 **tkns, size_t *num_tkns) -{ - char *buf; - int ret; - - buf = kmalloc(count + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = simple_write_to_buffer(buf, count, ppos, from, count); - if (ret != count) { - ret = ret >= 0 ? -EIO : ret; - goto exit; - } - - buf[count] = '\0'; - ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_read(struct file *file, - char __user *to, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_desc, len = 0; - char *buf; - int i, ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - buf = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) - goto exit; - - for (i = 0; i < num_desc; i++) { - ret = snprintf(buf + len, PAGE_SIZE - len, - "Id: %#010x Purpose: %d Node id: %#x\n", - desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); - if (ret < 0) - goto free_desc; - len += ret; - } - - ret = simple_read_from_buffer(to, count, ppos, buf, len); -free_desc: - kfree(desc); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_tkns, bytes; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - bytes = sizeof(*tkns) * num_tkns; - if (!num_tkns || (bytes % sizeof(*desc))) { - ret = -EINVAL; - goto exit; - } - - desc = (struct sof_probe_point_desc *)tkns; - ret = sof_ipc_probe_points_add(sdev, - desc, bytes / sizeof(*desc)); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_fops = { - .open = simple_open, - .read = probe_points_read, - .write = probe_points_write, - .llseek = default_llseek, -}; - -static ssize_t probe_points_remove_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - size_t num_tkns; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - if (!num_tkns) { - ret = -EINVAL; - goto exit; - } - - ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_remove_fops = { - .open = simple_open, - .write = probe_points_remove_write, - .llseek = default_llseek, -}; - -static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, - const char *name, mode_t mode, - const struct file_operations *fops) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->sdev = sdev; - - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) -#define MAX_IPC_FLOOD_DURATION_MS 1000 -#define MAX_IPC_FLOOD_COUNT 10000 -#define IPC_FLOOD_TEST_RESULT_LEN 512 - -static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev, - struct snd_sof_dfsentry *dfse, - bool flood_duration_test, - unsigned long ipc_duration_ms, - unsigned long ipc_count) -{ - struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply reply; - u64 min_response_time = U64_MAX; - ktime_t start, end, test_end; - u64 avg_response_time = 0; - u64 max_response_time = 0; - u64 ipc_response_time; - int i = 0; - int ret; - - /* configure test IPC */ - hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; - hdr.size = sizeof(hdr); - - /* set test end time for duration flood test */ - if (flood_duration_test) - test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; - - /* send test IPC's */ - while (1) { - start = ktime_get(); - ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size, - &reply, sizeof(reply)); - end = ktime_get(); - - if (ret < 0) - break; - - /* compute min and max response times */ - ipc_response_time = ktime_to_ns(ktime_sub(end, start)); - min_response_time = min(min_response_time, ipc_response_time); - max_response_time = max(max_response_time, ipc_response_time); - - /* sum up response times */ - avg_response_time += ipc_response_time; - i++; - - /* test complete? */ - if (flood_duration_test) { - if (ktime_to_ns(end) >= test_end) - break; - } else { - if (i == ipc_count) - break; - } - } - - if (ret < 0) - dev_err(sdev->dev, - "error: ipc flood test failed at %d iterations\n", i); - - /* return if the first IPC fails */ - if (!i) - return ret; - - /* compute average response time */ - do_div(avg_response_time, i); - - /* clear previous test output */ - memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN); - - if (flood_duration_test) { - dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n", - ipc_duration_ms); - snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN, - "IPC Flood test duration: %lums\n", ipc_duration_ms); - } - - dev_dbg(sdev->dev, - "IPC Flood count: %d, Avg response time: %lluns\n", - i, avg_response_time); - dev_dbg(sdev->dev, "Max response time: %lluns\n", - max_response_time); - dev_dbg(sdev->dev, "Min response time: %lluns\n", - min_response_time); - - /* format output string */ - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "IPC Flood count: %d\nAvg response time: %lluns\n", - i, avg_response_time); - - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "Max response time: %lluns\nMin response time: %lluns\n", - max_response_time, min_response_time); - - return ret; -} -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) -static ssize_t msg_inject_read(struct file *file, char __user *buffer, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct sof_ipc_reply *rhdr = dfse->msg_inject_rx; - - if (!rhdr->hdr.size || !count || *ppos) - return 0; - - if (count > rhdr->hdr.size) - count = rhdr->hdr.size; - - if (copy_to_user(buffer, dfse->msg_inject_rx, count)) - return -EFAULT; - - *ppos += count; - return count; -} - -static ssize_t msg_inject_write(struct file *file, const char __user *buffer, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_ipc_cmd_hdr *hdr = dfse->msg_inject_tx; - size_t size; - int ret, err; - - if (*ppos) - return 0; - - size = simple_write_to_buffer(dfse->msg_inject_tx, SOF_IPC_MSG_MAX_SIZE, - ppos, buffer, count); - if (size != count) - return size > 0 ? -EFAULT : size; - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - dev_err_ratelimited(sdev->dev, "%s: DSP resume failed: %d\n", - __func__, ret); - pm_runtime_put_noidle(sdev->dev); - goto out; - } - - /* send the message */ - memset(dfse->msg_inject_rx, 0, SOF_IPC_MSG_MAX_SIZE); - ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, dfse->msg_inject_tx, count, - dfse->msg_inject_rx, SOF_IPC_MSG_MAX_SIZE); - - pm_runtime_mark_last_busy(sdev->dev); - err = pm_runtime_put_autosuspend(sdev->dev); - if (err < 0) - dev_err_ratelimited(sdev->dev, "%s: DSP idle failed: %d\n", - __func__, err); - - /* return size if test is successful */ - if (ret >= 0) - ret = size; - -out: - return ret; -} - -static const struct file_operations msg_inject_fops = { - .open = simple_open, - .read = msg_inject_read, - .write = msg_inject_write, - .llseek = default_llseek, -}; - -static int snd_sof_debugfs_msg_inject_item(struct snd_sof_dev *sdev, - const char *name, mode_t mode, - const struct file_operations *fops) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - /* pre allocate the tx and rx buffers */ - dfse->msg_inject_tx = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - dfse->msg_inject_rx = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!dfse->msg_inject_tx || !dfse->msg_inject_rx) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->sdev = sdev; - - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} -#endif - static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - unsigned long ipc_duration_ms = 0; - bool flood_duration_test = false; - unsigned long ipc_count = 0; - struct dentry *dentry; - int err; -#endif size_t size; char *string; int ret; @@ -457,78 +33,6 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size = simple_write_to_buffer(string, count, ppos, buffer, count); ret = size; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* - * write op is only supported for ipc_flood_count or - * ipc_flood_duration_ms debugfs entries atm. - * ipc_flood_count floods the DSP with the number of IPC's specified. - * ipc_duration_ms test floods the DSP for the time specified - * in the debugfs entry. - */ - dentry = file->f_path.dentry; - if (strcmp(dentry->d_name.name, "ipc_flood_count") && - strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) { - ret = -EINVAL; - goto out; - } - - if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) - flood_duration_test = true; - - /* test completion criterion */ - if (flood_duration_test) - ret = kstrtoul(string, 0, &ipc_duration_ms); - else - ret = kstrtoul(string, 0, &ipc_count); - if (ret < 0) - goto out; - - /* limit max duration/ipc count for flood test */ - if (flood_duration_test) { - if (!ipc_duration_ms) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) - ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; - } else { - if (!ipc_count) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_count > MAX_IPC_FLOOD_COUNT) - ipc_count = MAX_IPC_FLOOD_COUNT; - } - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to resume %d\n", - ret); - pm_runtime_put_noidle(sdev->dev); - goto out; - } - - /* flood test */ - ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test, - ipc_duration_ms, ipc_count); - - pm_runtime_mark_last_busy(sdev->dev); - err = pm_runtime_put_autosuspend(sdev->dev); - if (err < 0) - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to idle %d\n", - err); - - /* return size if test is successful */ - if (ret >= 0) - ret = size; -out: -#endif kfree(string); return ret; } @@ -544,24 +48,6 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, int size; u8 *buf; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct dentry *dentry; - - dentry = file->f_path.dentry; - if ((!strcmp(dentry->d_name.name, "ipc_flood_count") || - !strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))) { - if (*ppos) - return 0; - - count = strlen(dfse->cache_buf); - size_ret = copy_to_user(buffer, dfse->cache_buf, count); - if (size_ret) - return -EFAULT; - - *ppos += count; - return count; - } -#endif size = dfse->size; /* validate position & count */ @@ -719,19 +205,6 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, dfse->size = size; dfse->sdev = sdev; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - if (!strncmp(name, "ipc_flood", strlen("ipc_flood"))) { - /* - * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries. - * So, use it to save the results of the last IPC flood test. - */ - dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN, - GFP_KERNEL); - if (!dfse->cache_buf) - return -ENOMEM; - } -#endif - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, &sof_dfs_fops); /* add to dfsentry list */ @@ -881,44 +354,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - err = snd_sof_debugfs_probe_item(sdev, "probe_points", - 0644, &probe_points_fops); - if (err < 0) - return err; - err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove", - 0200, &probe_points_remove_fops); - if (err < 0) - return err; -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* create read-write ipc_flood_count debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_count", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; - - /* create read-write ipc_flood_duration_ms debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_duration_ms", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) - err = snd_sof_debugfs_msg_inject_item(sdev, "ipc_msg_inject", 0644, - &msg_inject_fops); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; -#endif - return 0; } EXPORT_SYMBOL_GPL(snd_sof_dbg_init); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 88b6176af021..b53f216d4ecc 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -215,6 +215,7 @@ config SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_PCI_DEV select SND_INTEL_DSP_CONFIG select SND_SOC_SOF_HDA_LINK_BASELINE + select SND_SOC_SOF_HDA_PROBES help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -240,15 +241,6 @@ config SND_SOC_SOF_HDA_AUDIO_CODEC Say Y if you want to enable HDAudio codecs with SOF. If unsure select "N". -config SND_SOC_SOF_HDA_PROBES - bool "SOF enable probes over HDA" - depends on SND_SOC_SOF_DEBUG_PROBES - help - This option enables the data probing for Intel(R) - Skylake and newer platforms. - Say Y if you want to enable probes. - If unsure, select "N". - endif ## SND_SOC_SOF_HDA_COMMON config SND_SOC_SOF_HDA_LINK_BASELINE @@ -266,6 +258,15 @@ config SND_SOC_SOF_HDA This option is not user-selectable but automagically handled by 'select' statements at a higher level. +config SND_SOC_SOF_HDA_PROBES + bool + select SND_SOC_SOF_DEBUG_PROBES + help + The option enables the data probing for Intel(R) Skylake and newer + (HDA) platforms. + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE tristate select SOUNDWIRE_INTEL if SND_SOC_SOF_INTEL_SOUNDWIRE diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 810b8b6748a0..cd8d08c17561 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -80,15 +80,6 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw, @@ -110,6 +101,10 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger, + /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 1911e104f113..bef27e8751f2 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -298,15 +298,6 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw, @@ -328,6 +319,10 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger, + /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index af0c85e4e299..75063140ed0c 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -16,10 +16,6 @@ #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "../sof-probes.h" -#endif - struct hda_pipe_params { u32 ch; u32 s_freq; @@ -737,20 +733,5 @@ struct snd_soc_dai_driver skl_dai[] = { .channels_max = 16, }, }, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -{ - .name = "Probe Extraction CPU DAI", - .compress_new = snd_soc_new_compress, - .cops = &sof_probe_compr_ops, - .capture = { - .stream_name = "Probe Extraction", - .channels_min = 1, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .rate_min = 48000, - .rate_max = 48000, - }, -}, -#endif #endif }; diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 0fe522549c91..8ddde60c56b3 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -498,15 +498,9 @@ static void hda_dsp_state_log(struct snd_sof_dev *sdev) case SOF_DSP_PM_D2: dev_dbg(sdev->dev, "Current DSP power state: D2\n"); break; - case SOF_DSP_PM_D3_HOT: - dev_dbg(sdev->dev, "Current DSP power state: D3_HOT\n"); - break; case SOF_DSP_PM_D3: dev_dbg(sdev->dev, "Current DSP power state: D3\n"); break; - case SOF_DSP_PM_D3_COLD: - dev_dbg(sdev->dev, "Current DSP power state: D3_COLD\n"); - break; default: dev_dbg(sdev->dev, "Unknown DSP power state: %d\n", sdev->dsp_power_state.state); diff --git a/sound/soc/sof/intel/hda-probes.c b/sound/soc/sof/intel/hda-probes.c index 35e548da3609..31e85d4aae8c 100644 --- a/sound/soc/sof/intel/hda-probes.c +++ b/sound/soc/sof/intel/hda-probes.c @@ -3,14 +3,20 @@ // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // -// Copyright(c) 2019-2020 Intel Corporation. All rights reserved. +// Copyright(c) 2019-2021 Intel Corporation. All rights reserved. // // Author: Cezary Rojewski +// Converted to SOF client: +// Ranjani Sridharan +// Peter Ujfalusi // +#include #include #include #include "../sof-priv.h" +#include "../sof-client-probes.h" +#include "../sof-client.h" #include "hda.h" static inline struct hdac_ext_stream * @@ -19,10 +25,11 @@ hda_compr_get_stream(struct snd_compr_stream *cstream) return cstream->runtime->private_data; } -int hda_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) +static int hda_probes_compr_assign(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id) { + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct hdac_ext_stream *hext_stream; hext_stream = hda_dsp_stream_get(sdev, cstream->direction, 0); @@ -33,14 +40,17 @@ int hda_probe_compr_assign(struct snd_sof_dev *sdev, hdac_stream(hext_stream)->cstream = cstream; cstream->runtime->private_data = hext_stream; - return hdac_stream(hext_stream)->stream_tag; + *stream_id = hdac_stream(hext_stream)->stream_tag; + + return 0; } -int hda_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) +static int hda_probes_compr_free(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); int ret; ret = hda_dsp_stream_put(sdev, cstream->direction, @@ -56,12 +66,13 @@ int hda_probe_compr_free(struct snd_sof_dev *sdev, return 0; } -int hda_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai) +static int hda_probes_compr_set_params(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct hdac_stream *hstream = hdac_stream(hext_stream); struct snd_dma_buffer *dmab; u32 bits, rate; @@ -89,19 +100,20 @@ int hda_probe_compr_set_params(struct snd_sof_dev *sdev, return 0; } -int hda_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) +static int hda_probes_compr_trigger(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); return hda_dsp_stream_trigger(sdev, hext_stream, cmd); } -int hda_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai) +static int hda_probes_compr_pointer(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) { struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); struct snd_soc_pcm_stream *pstream; @@ -112,3 +124,25 @@ int hda_probe_compr_pointer(struct snd_sof_dev *sdev, return 0; } + +/* SOF client implementation */ +static const struct sof_probes_host_ops hda_probes_ops = { + .assign = hda_probes_compr_assign, + .free = hda_probes_compr_free, + .set_params = hda_probes_compr_set_params, + .trigger = hda_probes_compr_trigger, + .pointer = hda_probes_compr_pointer, +}; + +int hda_probes_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "hda-probes", 0, &hda_probes_ops, + sizeof(hda_probes_ops)); +} + +void hda_probes_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "hda-probes", 0); +} + +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 8f6765317cfa..659fe9d1a542 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1423,6 +1423,16 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) } EXPORT_SYMBOL_NS(hda_pci_intel_probe, SND_SOC_SOF_INTEL_HDA_COMMON); +int hda_register_clients(struct snd_sof_dev *sdev) +{ + return hda_probes_register(sdev); +} + +void hda_unregister_clients(struct snd_sof_dev *sdev) +{ + hda_probes_unregister(sdev); +} + MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 324418582044..21e34580a403 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -16,6 +16,7 @@ #include #include #include +#include "../sof-client-probes.h" #include "shim.h" /* PCI registers */ @@ -351,13 +352,7 @@ /* Number of DAIs */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -#define SOF_SKL_NUM_DAIS 16 -#else #define SOF_SKL_NUM_DAIS 15 -#endif - #else #define SOF_SKL_NUM_DAIS 8 #endif @@ -575,29 +570,6 @@ int hda_ipc_pcm_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, const struct sof_ipc_pcm_params_reply *reply); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -/* - * Probe Compress Operations. - */ -int hda_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int hda_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int hda_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai); -int hda_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); -int hda_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai); -#endif - /* * DSP IPC Operations. */ @@ -729,6 +701,25 @@ extern const struct sof_intel_dsp_desc ehl_chip_info; extern const struct sof_intel_dsp_desc jsl_chip_info; extern const struct sof_intel_dsp_desc adls_chip_info; +/* Probes support */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +int hda_probes_register(struct snd_sof_dev *sdev); +void hda_probes_unregister(struct snd_sof_dev *sdev); +#else +static inline int hda_probes_register(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void hda_probes_unregister(struct snd_sof_dev *sdev) +{ +} +#endif /* CONFIG_SND_SOC_SOF_HDA_PROBES */ + +/* SOF client registration for HDA platforms */ +int hda_register_clients(struct snd_sof_dev *sdev); +void hda_unregister_clients(struct snd_sof_dev *sdev); + /* machine driver select */ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev); void hda_set_mach_params(struct snd_soc_acpi_mach *mach, diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index f75e3983969f..f20ab60e8a52 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -142,15 +142,6 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw, @@ -173,6 +164,10 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger, + /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 7f7929c5cb88..c7d1c244bc48 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -115,15 +115,6 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - /* firmware loading */ .load_firmware = snd_sof_load_firmware_raw, @@ -146,6 +137,10 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger, + /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/iomem-utils.c similarity index 59% rename from sound/soc/sof/utils.c rename to sound/soc/sof/iomem-utils.c index 66fa6602fb67..3f57f6cf6542 100644 --- a/sound/soc/sof/utils.c +++ b/sound/soc/sof/iomem-utils.c @@ -3,7 +3,7 @@ // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // -// Copyright(c) 2018 Intel Corporation. All rights reserved. +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. // // Author: Keyon Jie // @@ -125,62 +125,3 @@ int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, return 0; } EXPORT_SYMBOL(sof_block_read); - -/* - * Generic buffer page table creation. - * Take the each physical page address and drop the least significant unused - * bits from each (based on PAGE_SIZE). Then pack valid page address bits - * into compressed page table. - */ - -int snd_sof_create_page_table(struct device *dev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size) -{ - int i, pages; - - pages = snd_sgbuf_aligned_pages(size); - - dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", - dmab->area, size, pages); - - for (i = 0; i < pages; i++) { - /* - * The number of valid address bits for each page is 20. - * idx determines the byte position within page_table - * where the current page's address is stored - * in the compressed page_table. - * This can be calculated by multiplying the page number by 2.5. - */ - u32 idx = (5 * i) >> 1; - u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; - u8 *pg_table; - - dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); - - pg_table = (u8 *)(page_table + idx); - - /* - * pagetable compression: - * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 - * ___________pfn 0__________ __________pfn 1___________ _pfn 2... - * .... .... .... .... .... .... .... .... .... .... .... - * It is created by: - * 1. set current location to 0, PFN index i to 0 - * 2. put pfn[i] at current location in Little Endian byte order - * 3. calculate an intermediate value as - * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) - * 4. put x at offset (current location + 2) in LE byte order - * 5. increment current location by 5 bytes, increment i by 2 - * 6. continue to (2) - */ - if (i & 1) - put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, - pg_table); - else - put_unaligned_le32(pfn, pg_table); - } - - return pages; -} -EXPORT_SYMBOL(snd_sof_create_page_table); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 16a0d7a059f3..c729bb7bf8c8 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -18,8 +18,10 @@ #include "sof-audio.h" #include "ops.h" -static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type); -static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); +typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf); + +static void ipc_trace_message(struct snd_sof_dev *sdev, void *msg_buf); +static void ipc_stream_message(struct snd_sof_dev *sdev, void *msg_buf); /* * IPC message Tx/Rx message handling. @@ -477,44 +479,30 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) } EXPORT_SYMBOL(snd_sof_ipc_reply); -static void ipc_comp_notification(struct snd_sof_dev *sdev, - struct sof_ipc_cmd_hdr *hdr) +static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf) { + struct sof_ipc_cmd_hdr *hdr = msg_buf; u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - struct sof_ipc_ctrl_data *cdata; - int ret; switch (msg_type) { case SOF_IPC_COMP_GET_VALUE: case SOF_IPC_COMP_GET_DATA: - cdata = kmalloc(hdr->size, GFP_KERNEL); - if (!cdata) - return; - - /* read back full message */ - ret = snd_sof_ipc_msg_data(sdev, NULL, cdata, hdr->size); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to read component event: %d\n", ret); - goto err; - } break; default: dev_err(sdev->dev, "error: unhandled component message %#x\n", msg_type); return; } - snd_sof_control_notify(sdev, cdata); - -err: - kfree(cdata); + snd_sof_control_notify(sdev, msg_buf); } /* DSP firmware has sent host a message */ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) { + ipc_rx_callback rx_callback = NULL; struct sof_ipc_cmd_hdr hdr; - u32 cmd, type; + void *msg_buf; + u32 cmd; int err; /* read back header */ @@ -523,10 +511,15 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); return; } + + if (hdr.size < sizeof(hdr)) { + dev_err(sdev->dev, "The received message size is invalid\n"); + return; + } + ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); cmd = hdr.cmd & SOF_GLB_TYPE_MASK; - type = hdr.cmd & SOF_CMD_TYPE_MASK; /* check message type */ switch (cmd) { @@ -551,20 +544,38 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) case SOF_IPC_GLB_PM_MSG: break; case SOF_IPC_GLB_COMP_MSG: - ipc_comp_notification(sdev, &hdr); + rx_callback = ipc_comp_notification; break; case SOF_IPC_GLB_STREAM_MSG: - /* need to pass msg id into the function */ - ipc_stream_message(sdev, hdr.cmd); + rx_callback = ipc_stream_message; break; case SOF_IPC_GLB_TRACE_MSG: - ipc_trace_message(sdev, type); + rx_callback = ipc_trace_message; break; default: - dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); + dev_err(sdev->dev, "%s: Unknown DSP message: 0x%x\n", __func__, cmd); break; } + /* read the full message */ + msg_buf = kmalloc(hdr.size, GFP_KERNEL); + if (!msg_buf) + return; + + err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); + if (err < 0) { + dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err); + } else { + /* Call local handler for the message */ + if (rx_callback) + rx_callback(sdev, msg_buf); + + /* Notify registered clients */ + sof_client_ipc_rx_dispatcher(sdev, msg_buf); + } + + kfree(msg_buf); + ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); } EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); @@ -573,19 +584,14 @@ EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); * IPC trace mechanism. */ -static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type) +static void ipc_trace_message(struct snd_sof_dev *sdev, void *msg_buf) { - struct sof_ipc_dma_trace_posn posn; - int ret; + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; switch (msg_type) { case SOF_IPC_TRACE_DMA_POSITION: - /* read back full message */ - ret = snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); - if (ret < 0) - dev_warn(sdev->dev, "failed to read trace position: %d\n", ret); - else - snd_sof_trace_update_pos(sdev, &posn); + snd_sof_trace_update_pos(sdev, msg_buf); break; default: dev_err(sdev->dev, "error: unhandled trace message %#x\n", msg_type); @@ -667,11 +673,11 @@ static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) } /* stream notifications from DSP FW */ -static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) +static void ipc_stream_message(struct snd_sof_dev *sdev, void *msg_buf) { - /* get msg cmd type and msd id */ - u32 msg_type = msg_cmd & SOF_CMD_TYPE_MASK; - u32 msg_id = SOF_IPC_MESSAGE_ID(msg_cmd); + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + u32 msg_id = SOF_IPC_MESSAGE_ID(hdr->cmd); switch (msg_type) { case SOF_IPC_STREAM_POSITION: diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 1f84d30296cf..999a36208b11 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -497,49 +497,6 @@ static inline int snd_sof_pcm_platform_ack(struct snd_sof_dev *sdev, return 0; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -static inline int -snd_sof_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_assign(sdev, cstream, dai); -} - -static inline int -snd_sof_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_free(sdev, cstream, dai); -} - -static inline int -snd_sof_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_set_params(sdev, cstream, params, dai); -} - -static inline int -snd_sof_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_trigger(sdev, cstream, cmd, dai); -} - -static inline int -snd_sof_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai) -{ - if (sof_ops(sdev) && sof_ops(sdev)->probe_pointer) - return sof_ops(sdev)->probe_pointer(sdev, cstream, tstamp, dai); - - return 0; -} -#endif - /* machine driver */ static inline int snd_sof_machine_register(struct snd_sof_dev *sdev, void *pdata) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 37fb8e6cd493..137f8ed71677 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -15,10 +15,8 @@ #include #include "sof-priv.h" #include "sof-audio.h" +#include "sof-utils.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" -#endif /* Create DMA buffer page table for DSP */ static int create_page_table(struct snd_soc_component *component, @@ -924,9 +922,6 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->pointer = sof_pcm_pointer; pd->ack = sof_pcm_ack; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - pd->compress_ops = &sof_probe_compressed_ops; -#endif pd->pcm_construct = sof_pcm_new; pd->ignore_machine = drv_name; pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 197a88695fef..7300ecadabd9 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -167,6 +167,9 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } + /* Notify clients not managed by pm framework about core resume */ + sof_resume_clients(sdev); + /* notify DSP of system resume */ ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); if (ret < 0) @@ -180,6 +183,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) static int sof_suspend(struct device *dev, bool runtime_suspend) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + pm_message_t pm_state; u32 target_state = 0; int ret; @@ -205,16 +209,23 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) } target_state = snd_sof_dsp_power_target(sdev); + pm_state.event = target_state; /* Skip to platform-specific suspend if DSP is entering D0 */ - if (target_state == SOF_DSP_PM_D0) + if (target_state == SOF_DSP_PM_D0) { + /* Notify clients not managed by pm framework about core suspend */ + sof_suspend_clients(sdev, pm_state); goto suspend; + } sof_tear_down_pipelines(sdev, false); /* release trace */ snd_sof_release_trace(sdev); + /* Notify clients not managed by pm framework about core suspend */ + sof_suspend_clients(sdev, pm_state); + #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) /* cache debugfs contents during runtime suspend */ if (runtime_suspend) diff --git a/sound/soc/sof/sof-client-ipc-flood-test.c b/sound/soc/sof/sof-client-ipc-flood-test.c new file mode 100644 index 000000000000..db3a052c5dd2 --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-flood-test.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan +// Peter Ujfalusi +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sof-client.h" + +#define MAX_IPC_FLOOD_DURATION_MS 1000 +#define MAX_IPC_FLOOD_COUNT 10000 +#define IPC_FLOOD_TEST_RESULT_LEN 512 +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +#define DEBUGFS_IPC_FLOOD_COUNT "ipc_flood_count" +#define DEBUGFS_IPC_FLOOD_DURATION "ipc_flood_duration_ms" + +struct sof_ipc_flood_priv { + struct dentry *dfs_root; + struct dentry *dfs_link[2]; + char *buf; +}; + +static int sof_ipc_flood_dfs_open(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = debugfs_file_get(file->f_path.dentry); + if (unlikely(ret)) + return ret; + + ret = simple_open(inode, file); + if (ret) + debugfs_file_put(file->f_path.dentry); + + return ret; +} + +/* + * helper function to perform the flood test. Only one of the two params, ipc_duration_ms + * or ipc_count, will be non-zero and will determine the type of test + */ +static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, + bool flood_duration_test, + unsigned long ipc_duration_ms, + unsigned long ipc_count) +{ + struct sof_ipc_flood_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply reply; + u64 min_response_time = U64_MAX; + ktime_t start, end, test_end; + u64 avg_response_time = 0; + u64 max_response_time = 0; + u64 ipc_response_time; + int i = 0; + int ret; + + /* configure test IPC */ + hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; + hdr.size = sizeof(hdr); + + /* set test end time for duration flood test */ + if (flood_duration_test) + test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; + + /* send test IPC's */ + while (1) { + start = ktime_get(); + ret = sof_client_ipc_tx_message(cdev, &hdr, &reply, sizeof(reply)); + end = ktime_get(); + + if (ret < 0) + break; + + /* compute min and max response times */ + ipc_response_time = ktime_to_ns(ktime_sub(end, start)); + min_response_time = min(min_response_time, ipc_response_time); + max_response_time = max(max_response_time, ipc_response_time); + + /* sum up response times */ + avg_response_time += ipc_response_time; + i++; + + /* test complete? */ + if (flood_duration_test) { + if (ktime_to_ns(end) >= test_end) + break; + } else { + if (i == ipc_count) + break; + } + } + + if (ret < 0) + dev_err(dev, "ipc flood test failed at %d iterations\n", i); + + /* return if the first IPC fails */ + if (!i) + return ret; + + /* compute average response time */ + do_div(avg_response_time, i); + + /* clear previous test output */ + memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN); + + if (!ipc_count) { + dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms); + snprintf(priv->buf, IPC_FLOOD_TEST_RESULT_LEN, + "IPC Flood test duration: %lums\n", ipc_duration_ms); + } + + dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n", + i, avg_response_time); + dev_dbg(dev, "Max response time: %lluns\n", max_response_time); + dev_dbg(dev, "Min response time: %lluns\n", min_response_time); + + /* format output string and save test results */ + snprintf(priv->buf + strlen(priv->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), + "IPC Flood count: %d\nAvg response time: %lluns\n", + i, avg_response_time); + + snprintf(priv->buf + strlen(priv->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), + "Max response time: %lluns\nMin response time: %lluns\n", + max_response_time, min_response_time); + + return ret; +} + +/* + * Writing to the debugfs entry initiates the IPC flood test based on + * the IPC count or the duration specified by the user. + */ +static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct device *dev = &cdev->auxdev.dev; + unsigned long ipc_duration_ms = 0; + bool flood_duration_test = false; + unsigned long ipc_count = 0; + struct dentry *dentry; + int err; + size_t size; + char *string; + int ret; + + string = kzalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + + /* + * write op is only supported for ipc_flood_count or + * ipc_flood_duration_ms debugfs entries atm. + * ipc_flood_count floods the DSP with the number of IPC's specified. + * ipc_duration_ms test floods the DSP for the time specified + * in the debugfs entry. + */ + dentry = file->f_path.dentry; + if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) && + strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { + ret = -EINVAL; + goto out; + } + + if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) + flood_duration_test = true; + + /* test completion criterion */ + if (flood_duration_test) + ret = kstrtoul(string, 0, &ipc_duration_ms); + else + ret = kstrtoul(string, 0, &ipc_count); + if (ret < 0) + goto out; + + /* limit max duration/ipc count for flood test */ + if (flood_duration_test) { + if (!ipc_duration_ms) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) + ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; + } else { + if (!ipc_count) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_count > MAX_IPC_FLOOD_COUNT) + ipc_count = MAX_IPC_FLOOD_COUNT; + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto out; + } + + /* flood test */ + ret = sof_debug_ipc_flood_test(cdev, flood_duration_test, + ipc_duration_ms, ipc_count); + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); + + /* return size if test is successful */ + if (ret >= 0) + ret = size; +out: + kfree(string); + return ret; +} + +/* return the result of the last IPC flood test */ +static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_ipc_flood_priv *priv = cdev->data; + size_t size_ret; + + struct dentry *dentry; + + dentry = file->f_path.dentry; + if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) || + !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { + if (*ppos) + return 0; + + count = min_t(size_t, count, strlen(priv->buf)); + size_ret = copy_to_user(buffer, priv->buf, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + return count; + } + return count; +} + +static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_ipc_flood_fops = { + .open = sof_ipc_flood_dfs_open, + .read = sof_ipc_flood_dfs_read, + .llseek = default_llseek, + .write = sof_ipc_flood_dfs_write, + .release = sof_ipc_flood_dfs_release, + + .owner = THIS_MODULE, +}; + +/* + * The IPC test client creates a couple of debugfs entries that will be used + * flood tests. Users can write to these entries to execute the IPC flood test + * by specifying either the number of IPCs to flood the DSP with or the duration + * (in ms) for which the DSP should be flooded with test IPCs. At the + * end of each test, the average, min and max response times are reported back. + * The results of the last flood test can be accessed by reading the debugfs + * entries. + */ +static int sof_ipc_flood_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct sof_ipc_flood_priv *priv; + + /* allocate memory for client data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL); + if (!priv->buf) + return -ENOMEM; + + cdev->data = priv; + + /* create debugfs root folder with device name under parent SOF dir */ + priv->dfs_root = debugfs_create_dir(dev_name(dev), debugfs_root); + if (!IS_ERR_OR_NULL(priv->dfs_root)) { + /* create read-write ipc_flood_count debugfs entry */ + debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, 0644, priv->dfs_root, + cdev, &sof_ipc_flood_fops); + + /* create read-write ipc_flood_duration_ms debugfs entry */ + debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, 0644, + priv->dfs_root, cdev, &sof_ipc_flood_fops); + + if (auxdev->id == 0) { + /* + * Create symlinks for backwards compatibility to the + * first IPC flood test instance + */ + char target[100]; + + snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_COUNT, + dev_name(dev)); + priv->dfs_link[0] = + debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT, + debugfs_root, target); + + snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_DURATION, + dev_name(dev)); + priv->dfs_link[1] = + debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION, + debugfs_root, target); + } + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_ipc_flood_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_ipc_flood_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + if (auxdev->id == 0) { + debugfs_remove(priv->dfs_link[0]); + debugfs_remove(priv->dfs_link[1]); + } + + debugfs_remove_recursive(priv->dfs_root); +} + +static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = { + { .name = "snd_sof.ipc_flood" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_ipc_flood_client_drv = { + .probe = sof_ipc_flood_probe, + .remove = sof_ipc_flood_remove, + + .id_table = sof_ipc_flood_client_id_table, +}; + +module_auxiliary_driver(sof_ipc_flood_client_drv); + +MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-ipc-msg-injector.c b/sound/soc/sof/sof-client-ipc-msg-injector.c new file mode 100644 index 000000000000..bce103da4c49 --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-msg-injector.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Peter Ujfalusi +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sof-client.h" + +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +struct sof_msg_inject_priv { + struct dentry *dfs_file; + + void *tx_buffer; + void *rx_buffer; +}; + +static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = debugfs_file_get(file->f_path.dentry); + if (unlikely(ret)) + return ret; + + ret = simple_open(inode, file); + if (ret) + debugfs_file_put(file->f_path.dentry); + + return ret; +} + +static ssize_t sof_msg_inject_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct sof_ipc_reply *rhdr = priv->rx_buffer; + + if (!rhdr->hdr.size || !count || *ppos) + return 0; + + if (count > rhdr->hdr.size) + count = rhdr->hdr.size; + + if (copy_to_user(buffer, priv->rx_buffer, count)) + return -EFAULT; + + *ppos += count; + return count; +} + +static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + int ret, err; + size_t size; + + if (*ppos) + return 0; + + size = simple_write_to_buffer(priv->tx_buffer, SOF_IPC_MSG_MAX_SIZE, + ppos, buffer, count); + if (size != count) + return size > 0 ? -EFAULT : size; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + return ret; + } + + /* send the message */ + memset(priv->rx_buffer, 0, SOF_IPC_MSG_MAX_SIZE); + ret = sof_client_ipc_tx_message(cdev, priv->tx_buffer, priv->rx_buffer, + SOF_IPC_MSG_MAX_SIZE); + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); + + /* return size if test is successful */ + if (ret >= 0) + ret = size; + + return ret; +}; + +static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_msg_inject_fops = { + .open = sof_msg_inject_dfs_open, + .read = sof_msg_inject_dfs_read, + .write = sof_msg_inject_dfs_write, + .llseek = default_llseek, + .release = sof_msg_inject_dfs_release, + + .owner = THIS_MODULE, +}; + +static int sof_msg_inject_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct sof_msg_inject_priv *priv; + + /* allocate memory for client data */ + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->tx_buffer = devm_kmalloc(dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + priv->rx_buffer = devm_kmalloc(dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!priv->tx_buffer || !priv->rx_buffer) + return -ENOMEM; + + cdev->data = priv; + + priv->dfs_file = debugfs_create_file("ipc_msg_inject", 0644, debugfs_root, + cdev, &sof_msg_inject_fops); + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_msg_inject_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_msg_inject_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + debugfs_remove(priv->dfs_file); +} + +static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = { + { .name = "snd_sof.msg_injector" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_msg_inject_client_drv = { + .probe = sof_msg_inject_probe, + .remove = sof_msg_inject_remove, + + .id_table = sof_msg_inject_client_id_table, +}; + +module_auxiliary_driver(sof_msg_inject_client_drv); + +MODULE_DESCRIPTION("SOF IPC Message Injector Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c new file mode 100644 index 000000000000..797dedb26163 --- /dev/null +++ b/sound/soc/sof/sof-client-probes.c @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2019-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski +// +// SOF client support: +// Ranjani Sridharan +// Peter Ujfalusi +// + +#include +#include +#include +#include +#include +#include "sof-client.h" +#include "sof-client-probes.h" + +#define SOF_PROBES_SUSPEND_DELAY_MS 3000 +/* only extraction supported for now */ +#define SOF_PROBES_NUM_DAI_LINKS 1 + +#define SOF_PROBES_INVALID_NODE_ID UINT_MAX + +static bool __read_mostly sof_probes_enabled; +module_param_named(enable, sof_probes_enabled, bool, 0444); +MODULE_PARM_DESC(enable, "Enable SOF probes support"); + +struct sof_probes_priv { + struct dentry *dfs_points; + struct dentry *dfs_points_remove; + u32 extractor_stream_tag; + struct snd_soc_card card; + + const struct sof_probes_host_ops *host_ops; +}; + +struct sof_probe_point_desc { + unsigned int buffer_id; + unsigned int purpose; + unsigned int stream_tag; +} __packed; + +struct sof_probe_dma { + unsigned int stream_tag; + unsigned int dma_buffer_size; +} __packed; + +struct sof_ipc_probe_dma_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_dma dma[]; +} __packed; + +struct sof_ipc_probe_info_params { + struct sof_ipc_reply rhdr; + unsigned int num_elems; + union { + struct sof_probe_dma dma[0]; + struct sof_probe_point_desc desc[0]; + }; +} __packed; + +struct sof_ipc_probe_point_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_point_desc desc[]; +} __packed; + +struct sof_ipc_probe_point_remove_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + unsigned int buffer_id[]; +} __packed; + +/** + * sof_probes_init - initialize data probing + * @cdev: SOF client device + * @stream_tag: Extractor stream tag + * @buffer_size: DMA buffer size to set for extractor + * + * Host chooses whether extraction is supported or not by providing + * valid stream tag to DSP. Once specified, stream described by that + * tag will be tied to DSP for extraction for the entire lifetime of + * probe. + * + * Probing is initialized only once and each INIT request must be + * matched by DEINIT call. + */ +static int sof_probes_init(struct sof_client_dev *cdev, u32 stream_tag, + size_t buffer_size) +{ + struct sof_ipc_probe_dma_add_params *msg; + size_t size = struct_size(msg, dma, 1); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT; + msg->num_elems = 1; + msg->dma[0].stream_tag = stream_tag; + msg->dma[0].dma_buffer_size = buffer_size; + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +/** + * sof_probes_deinit - cleanup after data probing + * @cdev: SOF client device + * + * Host sends DEINIT request to free previously initialized probe + * on DSP side once it is no longer needed. DEINIT only when there + * are no probes connected and with all injectors detached. + */ +static int sof_probes_deinit(struct sof_client_dev *cdev) +{ + struct sof_ipc_cmd_hdr msg; + struct sof_ipc_reply reply; + + msg.size = sizeof(msg); + msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; + + return sof_client_ipc_tx_message(cdev, &msg, &reply, sizeof(reply)); +} + +static int sof_probes_info(struct sof_client_dev *cdev, unsigned int cmd, + void **params, size_t *num_params) +{ + struct sof_ipc_probe_info_params msg = {{{0}}}; + struct sof_ipc_probe_info_params *reply; + size_t bytes; + int ret; + + *params = NULL; + *num_params = 0; + + reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!reply) + return -ENOMEM; + msg.rhdr.hdr.size = sizeof(msg); + msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; + + ret = sof_client_ipc_tx_message(cdev, &msg, reply, SOF_IPC_MSG_MAX_SIZE); + if (ret < 0 || reply->rhdr.error < 0) + goto exit; + + if (!reply->num_elems) + goto exit; + + if (cmd == SOF_IPC_PROBE_DMA_INFO) + bytes = sizeof(reply->dma[0]); + else + bytes = sizeof(reply->desc[0]); + bytes *= reply->num_elems; + *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL); + if (!*params) { + ret = -ENOMEM; + goto exit; + } + *num_params = reply->num_elems; + +exit: + kfree(reply); + return ret; +} + +/** + * sof_probes_points_info - retrieve list of active probe points + * @cdev: SOF client device + * @desc: Returned list of active probes + * @num_desc: Returned count of active probes + * + * Host sends PROBE_POINT_INFO request to obtain list of active probe + * points, valid for disconnection when given probe is no longer + * required. + */ +static int sof_probes_points_info(struct sof_client_dev *cdev, + struct sof_probe_point_desc **desc, + size_t *num_desc) +{ + return sof_probes_info(cdev, SOF_IPC_PROBE_POINT_INFO, + (void **)desc, num_desc); +} + +/** + * sof_probes_points_add - connect specified probes + * @cdev: SOF client device + * @desc: List of probe points to connect + * @num_desc: Number of elements in @desc + * + * Dynamically connects to provided set of endpoints. Immediately + * after connection is established, host must be prepared to + * transfer data from or to target stream given the probing purpose. + * + * Each probe point should be removed using PROBE_POINT_REMOVE + * request when no longer needed. + */ +static int sof_probes_points_add(struct sof_client_dev *cdev, + struct sof_probe_point_desc *desc, + size_t num_desc) +{ + struct sof_ipc_probe_point_add_params *msg; + size_t size = struct_size(msg, desc, num_desc); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_desc; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; + memcpy(&msg->desc[0], desc, size - sizeof(*msg)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +/** + * sof_probes_points_remove - disconnect specified probes + * @cdev: SOF client device + * @buffer_id: List of probe points to disconnect + * @num_buffer_id: Number of elements in @desc + * + * Removes previously connected probes from list of active probe + * points and frees all resources on DSP side. + */ +static int sof_probes_points_remove(struct sof_client_dev *cdev, + unsigned int *buffer_id, size_t num_buffer_id) +{ + struct sof_ipc_probe_point_remove_params *msg; + size_t size = struct_size(msg, buffer_id, num_buffer_id); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_buffer_id; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; + memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +static int sof_probes_compr_startup(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = sof_client_core_module_get(cdev); + if (ret) + return ret; + + ret = ops->assign(cdev, cstream, dai, &priv->extractor_stream_tag); + if (ret) { + dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + sof_client_core_module_put(cdev); + } + + return ret; +} + +static int sof_probes_compr_shutdown(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + struct sof_probe_point_desc *desc; + size_t num_desc; + int i, ret; + + /* disconnect all probe points */ + ret = sof_probes_points_info(cdev, &desc, &num_desc); + if (ret < 0) { + dev_err(dai->dev, "Failed to get probe points: %d\n", ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) + sof_probes_points_remove(cdev, &desc[i].buffer_id, 1); + kfree(desc); + +exit: + ret = sof_probes_deinit(cdev); + if (ret < 0) + dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); + + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + snd_compr_free_pages(cstream); + + ret = ops->free(cdev, cstream, dai); + + sof_client_core_module_put(cdev); + + return ret; +} + +static int sof_probes_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct snd_compr_runtime *rtd = cstream->runtime; + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + int ret; + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = sof_client_get_dma_dev(cdev); + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; + + ret = ops->set_params(cdev, cstream, params, dai); + if (ret) + return ret; + + ret = sof_probes_init(cdev, priv->extractor_stream_tag, rtd->dma_bytes); + if (ret < 0) { + dev_err(dai->dev, "Failed to init probe: %d\n", ret); + return ret; + } + + return 0; +} + +static int sof_probes_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + + return ops->trigger(cdev, cstream, cmd, dai); +} + +static int sof_probes_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + + return ops->pointer(cdev, cstream, tstamp, dai); +} + +static const struct snd_soc_cdai_ops sof_probes_compr_ops = { + .startup = sof_probes_compr_startup, + .shutdown = sof_probes_compr_shutdown, + .set_params = sof_probes_compr_set_params, + .trigger = sof_probes_compr_trigger, + .pointer = sof_probes_compr_pointer, +}; + +static int sof_probes_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + unsigned int offset, n; + void *ptr; + int ret; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, rtd->dma_area, count - n); + } + + if (ret) + return count - ret; + return count; +} + +static const struct snd_compress_ops sof_probes_compressed_ops = { + .copy = sof_probes_compr_copy, +}; + +/** + * strsplit_u32 - Split string into sequence of u32 tokens + * @buf: String to split into tokens. + * @delim: String containing delimiter characters. + * @tkns: Returned u32 sequence pointer. + * @num_tkns: Returned number of tokens obtained. + */ +static int strsplit_u32(char *buf, const char *delim, u32 **tkns, size_t *num_tkns) +{ + char *s; + u32 *data, *tmp; + size_t count = 0; + size_t cap = 32; + int ret = 0; + + *tkns = NULL; + *num_tkns = 0; + data = kcalloc(cap, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + while ((s = strsep(&buf, delim)) != NULL) { + ret = kstrtouint(s, 0, data + count); + if (ret) + goto exit; + if (++count >= cap) { + cap *= 2; + tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); + if (!tmp) { + ret = -ENOMEM; + goto exit; + } + data = tmp; + } + } + + if (!count) + goto exit; + *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); + if (!(*tkns)) { + ret = -ENOMEM; + goto exit; + } + *num_tkns = count; + +exit: + kfree(data); + return ret; +} + +static int tokenize_input(const char __user *from, size_t count, + loff_t *ppos, u32 **tkns, size_t *num_tkns) +{ + char *buf; + int ret; + + buf = kmalloc(count + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = simple_write_to_buffer(buf, count, ppos, from, count); + if (ret != count) { + ret = ret >= 0 ? -EIO : ret; + goto exit; + } + + buf[count] = '\0'; + ret = strsplit_u32(buf, ",", tkns, num_tkns); +exit: + kfree(buf); + return ret; +} + +static ssize_t sof_probes_dfs_points_read(struct file *file, char __user *to, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + int remaining, offset; + size_t num_desc; + char *buf; + int i, ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs read failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probes_points_info(cdev, &desc, &num_desc); + if (ret < 0) + goto exit; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs read failed to idle %d\n", err); + + for (i = 0; i < num_desc; i++) { + offset = strlen(buf); + remaining = PAGE_SIZE - offset; + ret = snprintf(buf + offset, remaining, + "Id: %#010x Purpose: %u Node id: %#x\n", + desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); + if (ret < 0 || ret >= remaining) { + /* truncate the output buffer at the last full line */ + buf[offset] = '\0'; + break; + } + } + + ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf)); + + kfree(desc); +exit: + kfree(buf); + return ret; +} + +static ssize_t +sof_probes_dfs_points_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + size_t num_tkns, bytes; + u32 *tkns; + int ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + bytes = sizeof(*tkns) * num_tkns; + if (!num_tkns || (bytes % sizeof(*desc))) { + ret = -EINVAL; + goto exit; + } + + desc = (struct sof_probe_point_desc *)tkns; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probes_points_add(cdev, desc, bytes / sizeof(*desc)); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations sof_probes_points_fops = { + .open = simple_open, + .read = sof_probes_dfs_points_read, + .write = sof_probes_dfs_points_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static ssize_t +sof_probes_dfs_points_remove_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + size_t num_tkns; + u32 *tkns; + int ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + if (!num_tkns) { + ret = -EINVAL; + goto exit; + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probes_points_remove(cdev, tkns, num_tkns); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations sof_probes_points_remove_fops = { + .open = simple_open, + .write = sof_probes_dfs_points_remove_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static struct snd_soc_dai_driver sof_probes_dai_drv[] = { +{ + .name = "Probe Extraction CPU DAI", + .compress_new = snd_soc_new_compress, + .cops = &sof_probes_compr_ops, + .capture = { + .stream_name = "Probe Extraction", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + }, +}, +}; + +static const struct snd_soc_component_driver sof_probes_component = { + .name = "sof-probes-component", + .compress_ops = &sof_probes_compressed_ops, + .module_get_upon_open = 1, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static int sof_probes_client_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *dfsroot = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct snd_soc_dai_link_component platform_component[] = { + { + .name = dev_name(dev), + } + }; + struct snd_soc_card *card; + struct sof_probes_priv *priv; + struct snd_soc_dai_link_component *cpus; + struct sof_probes_host_ops *ops; + struct snd_soc_dai_link *links; + int ret; + + /* do not set up the probes support if it is not enabled */ + if (!sof_probes_enabled) + return -ENXIO; + + if (!dev->platform_data) { + dev_err(dev, "missing platform data\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ops = dev->platform_data; + + if (!ops->assign || !ops->free || !ops->set_params || !ops->trigger || + !ops->pointer) { + dev_err(dev, "missing platform callback(s)\n"); + return -ENODEV; + } + + priv->host_ops = ops; + cdev->data = priv; + + /* register probes component driver and dai */ + ret = devm_snd_soc_register_component(dev, &sof_probes_component, + sof_probes_dai_drv, + ARRAY_SIZE(sof_probes_dai_drv)); + if (ret < 0) { + dev_err(dev, "failed to register SOF probes DAI driver %d\n", ret); + return ret; + } + + /* set client data */ + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + + /* create read-write probes_points debugfs entry */ + priv->dfs_points = debugfs_create_file("probe_points", 0644, dfsroot, + cdev, &sof_probes_points_fops); + + /* create read-write probe_points_remove debugfs entry */ + priv->dfs_points_remove = debugfs_create_file("probe_points_remove", 0644, + dfsroot, cdev, + &sof_probes_points_remove_fops); + + links = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*links), GFP_KERNEL); + cpus = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*cpus), GFP_KERNEL); + if (!links || !cpus) { + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); + return -ENOMEM; + } + + /* extraction DAI link */ + links[0].name = "Compress Probe Capture"; + links[0].id = 0; + links[0].cpus = &cpus[0]; + links[0].num_cpus = 1; + links[0].cpus->dai_name = "Probe Extraction CPU DAI"; + links[0].codecs = dummy; + links[0].num_codecs = 1; + links[0].platforms = platform_component; + links[0].num_platforms = ARRAY_SIZE(platform_component); + links[0].nonatomic = 1; + + card = &priv->card; + + card->dev = dev; + card->name = "sof-probes"; + card->owner = THIS_MODULE; + card->num_links = SOF_PROBES_NUM_DAI_LINKS; + card->dai_link = links; + + /* set idle_bias_off to prevent the core from resuming the card->dev */ + card->dapm.idle_bias_off = true; + + snd_soc_card_set_drvdata(card, cdev); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); + dev_err(dev, "Probes card register failed %d\n", ret); + return ret; + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_PROBES_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_probes_client_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_probes_priv *priv = cdev->data; + + if (!sof_probes_enabled) + return; + + pm_runtime_disable(&auxdev->dev); + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); +} + +static const struct auxiliary_device_id sof_probes_client_id_table[] = { + { .name = "snd_sof.hda-probes", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table); + +/* driver name will be set based on KBUILD_MODNAME */ +static struct auxiliary_driver sof_probes_client_drv = { + .probe = sof_probes_client_probe, + .remove = sof_probes_client_remove, + + .id_table = sof_probes_client_id_table, +}; + +module_auxiliary_driver(sof_probes_client_drv); + +MODULE_DESCRIPTION("SOF Probes Client Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-probes.h b/sound/soc/sof/sof-client-probes.h new file mode 100644 index 000000000000..0f9ed4569fd3 --- /dev/null +++ b/sound/soc/sof/sof-client-probes.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOF_CLIENT_PROBES_H +#define __SOF_CLIENT_PROBES_H + +struct snd_compr_stream; +struct snd_compr_tstamp; +struct snd_compr_params; +struct sof_client_dev; +struct snd_soc_dai; + +/* + * Callbacks used on platforms where the control for audio is split between + * DSP and host, like HDA. + */ +struct sof_probes_host_ops { + int (*assign)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id); + int (*free)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); + int (*set_params)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai); + int (*trigger)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai); + int (*pointer)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai); +}; + +#endif diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c new file mode 100644 index 000000000000..686ad0c3bb61 --- /dev/null +++ b/sound/soc/sof/sof-client.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan +// Peter Ujfalusi +// + +#include +#include +#include +#include +#include +#include +#include "ops.h" +#include "sof-client.h" +#include "sof-priv.h" + +/** + * struct sof_ipc_event_entry - IPC client event description + * @ipc_msg_type: IPC msg type of the event the client is interested + * @cdev: sof_client_dev of the requesting client + * @callback: Callback function of the client + * @list: item in SOF core client event list + */ +struct sof_ipc_event_entry { + u32 ipc_msg_type; + struct sof_client_dev *cdev; + sof_client_event_callback callback; + struct list_head list; +}; + +/** + * struct sof_state_event_entry - DSP panic event subscription entry + * @cdev: sof_client_dev of the requesting client + * @callback: Callback function of the client + * @list: item in SOF core client event list + */ +struct sof_state_event_entry { + struct sof_client_dev *cdev; + sof_client_fw_state_callback callback; + struct list_head list; +}; + +static void sof_client_auxdev_release(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + + kfree(cdev->auxdev.dev.platform_data); + kfree(cdev); +} + +static int sof_client_dev_add_data(struct sof_client_dev *cdev, const void *data, + size_t size) +{ + void *d = NULL; + + if (data) { + d = kmemdup(data, size, GFP_KERNEL); + if (!d) + return -ENOMEM; + } + + cdev->auxdev.dev.platform_data = d; + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) +static int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) +{ + int ret = 0; + int i; + + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) { + ret = sof_client_dev_register(sdev, "ipc_flood", i, NULL, 0); + if (ret < 0) + break; + } + + if (ret) { + for (; i >= 0; --i) + sof_client_dev_unregister(sdev, "ipc_flood", i); + } + + return ret; +} + +static void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) +{ + int i; + + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) + sof_client_dev_unregister(sdev, "ipc_flood", i); +} +#else +static inline int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) +static int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "msg_injector", 0, NULL, 0); +} + +static void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "msg_injector", 0); +} +#else +static inline int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */ + +int sof_register_clients(struct snd_sof_dev *sdev) +{ + int ret; + + /* Register platform independent client devices */ + ret = sof_register_ipc_flood_test(sdev); + if (ret) { + dev_err(sdev->dev, "IPC flood test client registration failed\n"); + return ret; + } + + ret = sof_register_ipc_msg_injector(sdev); + if (ret) { + dev_err(sdev->dev, "IPC message injector client registration failed\n"); + goto err_msg_injector; + } + + /* Platform depndent client device registration */ + + if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) + ret = sof_ops(sdev)->register_ipc_clients(sdev); + + if (!ret) + return 0; + + sof_unregister_ipc_msg_injector(sdev); + +err_msg_injector: + sof_unregister_ipc_flood_test(sdev); + + return ret; +} + +void sof_unregister_clients(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients) + sof_ops(sdev)->unregister_ipc_clients(sdev); + + sof_unregister_ipc_msg_injector(sdev); + sof_unregister_ipc_flood_test(sdev); +} + +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, + const void *data, size_t size) +{ + struct auxiliary_device *auxdev; + struct sof_client_dev *cdev; + int ret; + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + cdev->sdev = sdev; + auxdev = &cdev->auxdev; + auxdev->name = name; + auxdev->dev.parent = sdev->dev; + auxdev->dev.release = sof_client_auxdev_release; + auxdev->id = id; + + ret = sof_client_dev_add_data(cdev, data, size); + if (ret < 0) + goto err_dev_add_data; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to initialize client dev %s.%d\n", name, id); + goto err_dev_init; + } + + ret = auxiliary_device_add(&cdev->auxdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to add client dev %s.%d\n", name, id); + /* + * sof_client_auxdev_release() will be invoked to free up memory + * allocations through put_device() + */ + auxiliary_device_uninit(&cdev->auxdev); + return ret; + } + + /* add to list of SOF client devices */ + mutex_lock(&sdev->ipc_client_mutex); + list_add(&cdev->list, &sdev->ipc_client_list); + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; + +err_dev_init: + kfree(cdev->auxdev.dev.platform_data); + +err_dev_add_data: + kfree(cdev); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_register, SND_SOC_SOF_CLIENT); + +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id) +{ + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + /* + * sof_client_auxdev_release() will be invoked to free up memory + * allocations through put_device() + */ + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) { + list_del(&cdev->list); + auxiliary_device_delete(&cdev->auxdev); + auxiliary_device_uninit(&cdev->auxdev); + break; + } + } + + mutex_unlock(&sdev->ipc_client_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT); + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, + void *reply_data, size_t reply_bytes) +{ + struct sof_ipc_cmd_hdr *hdr = ipc_msg; + + return sof_ipc_tx_message(cdev->sdev->ipc, hdr->cmd, ipc_msg, hdr->size, + reply_data, reply_bytes); +} +EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT); + +int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) +{ + struct auxiliary_driver *adrv; + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + /* Skip devices without loaded driver */ + if (!cdev->auxdev.dev.driver) + continue; + + adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); + if (adrv->suspend) + adrv->suspend(&cdev->auxdev, state); + } + + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_suspend_clients, SND_SOC_SOF_CLIENT); + +int sof_resume_clients(struct snd_sof_dev *sdev) +{ + struct auxiliary_driver *adrv; + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + /* Skip devices without loaded driver */ + if (!cdev->auxdev.dev.driver) + continue; + + adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); + if (adrv->resume) + adrv->resume(&cdev->auxdev); + } + + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_resume_clients, SND_SOC_SOF_CLIENT); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev) +{ + return cdev->sdev->debugfs_root; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_debugfs_root, SND_SOC_SOF_CLIENT); + +/* DMA buffer allocation in client drivers must use the core SOF device */ +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev) +{ + return cdev->sdev->dev; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_dma_dev, SND_SOC_SOF_CLIENT); + +const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return &sdev->fw_ready.version; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_version, SND_SOC_SOF_CLIENT); + +/* module refcount management of SOF core */ +int sof_client_core_module_get(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + if (!try_module_get(sdev->dev->driver->owner)) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_core_module_get, SND_SOC_SOF_CLIENT); + +void sof_client_core_module_put(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + module_put(sdev->dev->driver->owner); +} +EXPORT_SYMBOL_NS_GPL(sof_client_core_module_put, SND_SOC_SOF_CLIENT); + +/* IPC event handling */ +void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_GLB_TYPE_MASK; + struct sof_ipc_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { + if (event->ipc_msg_type == msg_type) + event->callback(event->cdev, msg_buf); + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} + +int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type, + sof_client_event_callback callback) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_ipc_event_entry *event; + + if (!callback || !(ipc_msg_type & SOF_GLB_TYPE_MASK)) + return -EINVAL; + + event = kmalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->ipc_msg_type = ipc_msg_type; + event->cdev = cdev; + event->callback = callback; + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_event_handler_mutex); + list_add(&event->list, &sdev->ipc_rx_handler_list); + mutex_unlock(&sdev->client_event_handler_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_register_ipc_rx_handler, SND_SOC_SOF_CLIENT); + +void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_ipc_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { + if (event->cdev == cdev && event->ipc_msg_type == ipc_msg_type) { + list_del(&event->list); + kfree(event); + break; + } + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_unregister_ipc_rx_handler, SND_SOC_SOF_CLIENT); + +/*DSP state notification and query */ +void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) +{ + struct sof_state_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->fw_state_handler_list, list) + event->callback(event->cdev, sdev->fw_state); + + mutex_unlock(&sdev->client_event_handler_mutex); +} + +int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, + sof_client_fw_state_callback callback) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_state_event_entry *event; + + if (!callback) + return -EINVAL; + + event = kmalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->cdev = cdev; + event->callback = callback; + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_event_handler_mutex); + list_add(&event->list, &sdev->fw_state_handler_list); + mutex_unlock(&sdev->client_event_handler_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_register_fw_state_handler, SND_SOC_SOF_CLIENT); + +void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_state_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->fw_state_handler_list, list) { + if (event->cdev == cdev) { + list_del(&event->list); + kfree(event); + break; + } + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_unregister_fw_state_handler, SND_SOC_SOF_CLIENT); + +enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->fw_state; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_state, SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h new file mode 100644 index 000000000000..4b6394b4c694 --- /dev/null +++ b/sound/soc/sof/sof-client.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_SOF_CLIENT_H +#define __SOC_SOF_CLIENT_H + +#include +#include +#include +#include + +struct sof_ipc_fw_version; +struct sof_ipc_cmd_hdr; +struct snd_sof_dev; +struct dentry; + +/** + * struct sof_client_dev - SOF client device + * @auxdev: auxiliary device + * @sdev: pointer to SOF core device struct + * @list: item in SOF core client dev list + * @data: device specific data + */ +struct sof_client_dev { + struct auxiliary_device auxdev; + struct snd_sof_dev *sdev; + struct list_head list; + void *data; +}; + +#define sof_client_dev_to_sof_dev(cdev) ((cdev)->sdev) + +#define auxiliary_dev_to_sof_client_dev(auxiliary_dev) \ + container_of(auxiliary_dev, struct sof_client_dev, auxdev) + +#define dev_to_sof_client_dev(dev) \ + container_of(to_auxiliary_dev(dev), struct sof_client_dev, auxdev) + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, + void *reply_data, size_t reply_bytes); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev); +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev); +const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev); + +/* module refcount management of SOF core */ +int sof_client_core_module_get(struct sof_client_dev *cdev); +void sof_client_core_module_put(struct sof_client_dev *cdev); + +/* IPC notification */ +typedef void (*sof_client_event_callback)(struct sof_client_dev *cdev, void *msg_buf); + +int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type, + sof_client_event_callback callback); +void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type); + +/* DSP state notification and query */ +typedef void (*sof_client_fw_state_callback)(struct sof_client_dev *cdev, + enum sof_fw_state state); + +int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, + sof_client_fw_state_callback callback); +void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev); +enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev); + +#endif /* __SOC_SOF_CLIENT_H */ diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index e48402ce4bdb..2c8e556cd5cc 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -69,26 +69,12 @@ bool sof_debug_check_flag(int mask); #define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT) -#define ENABLE_DEBUGFS_CACHEBUF \ - (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ - IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) - /* So far the primary core on all DSPs has ID 0 */ #define SOF_DSP_PRIMARY_CORE 0 /* max number of DSP cores */ #define SOF_MAX_DSP_NUM_CORES 8 -/* DSP power state */ -enum sof_dsp_power_states { - SOF_DSP_PM_D0, - SOF_DSP_PM_D1, - SOF_DSP_PM_D2, - SOF_DSP_PM_D3_HOT, - SOF_DSP_PM_D3, - SOF_DSP_PM_D3_COLD, -}; - struct sof_dsp_power_state { u32 state; u32 substate; /* platform-specific */ @@ -215,27 +201,6 @@ struct snd_sof_dsp_ops { /* pcm ack */ int (*pcm_ack)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */ -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - /* Except for probe_pointer, all probe ops are mandatory */ - int (*probe_assign)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_free)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_set_params)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_trigger)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_pointer)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai); /* optional */ -#endif - /* host read DSP stream data */ int (*ipc_msg_data)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, @@ -302,6 +267,10 @@ struct snd_sof_dsp_ops { void (*set_mach_params)(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev); /* optional */ + /* IPC client ops */ + int (*register_ipc_clients)(struct snd_sof_dev *sdev); /* optional */ + void (*unregister_ipc_clients)(struct snd_sof_dev *sdev); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; @@ -332,12 +301,8 @@ struct snd_sof_dfsentry { * or if it is accessible only when the DSP is in D0. */ enum sof_debugfs_access_type access_type; -#if ENABLE_DEBUGFS_CACHEBUF +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) char *cache_buf; /* buffer to cache the contents of debugfs memory */ -#endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) - void *msg_inject_tx; - void *msg_inject_rx; #endif struct snd_sof_dev *sdev; struct list_head list; /* list in sdev dfsentry list */ @@ -460,10 +425,6 @@ struct snd_sof_dev { int ipc_timeout; int boot_timeout; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - unsigned int extractor_stream_tag; -#endif - /* DMA for Trace */ struct snd_dma_buffer dmatb; struct snd_dma_buffer dmatp; @@ -489,6 +450,30 @@ struct snd_sof_dev { */ int dsp_core_ref_count[SOF_MAX_DSP_NUM_CORES]; + /* + * Used to keep track of registered IPC client devices so that they can + * be removed when the parent SOF module is removed. + */ + struct list_head ipc_client_list; + + /* mutex to protect client list */ + struct mutex ipc_client_mutex; + + /* + * Used for tracking the IPC client's RX registration for DSP initiated + * message handling. + */ + struct list_head ipc_rx_handler_list; + + /* + * Used for tracking the IPC client's registration for DSP state change + * notification + */ + struct list_head fw_state_handler_list; + + /* to protect the ipc_rx_handler_list and dsp_state_handler_list list */ + struct mutex client_event_handler_mutex; + void *private; /* core does not touch this */ }; @@ -512,10 +497,6 @@ void snd_sof_complete(struct device *dev); void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); -int snd_sof_create_page_table(struct device *dev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size); - /* * Firmware loading. */ @@ -596,15 +577,7 @@ extern const struct dsp_arch_ops sof_xtensa_arch_ops; /* * Firmware state tracking */ -static inline void sof_set_fw_state(struct snd_sof_dev *sdev, - enum sof_fw_state new_state) -{ - if (sdev->fw_state == new_state) - return; - - dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); - sdev->fw_state = new_state; -} +void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state); /* * Utilities @@ -637,4 +610,56 @@ int sof_stream_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int sof_machine_check(struct snd_sof_dev *sdev); + +/* SOF client support */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_CLIENT) +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, + const void *data, size_t size); +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id); +int sof_register_clients(struct snd_sof_dev *sdev); +void sof_unregister_clients(struct snd_sof_dev *sdev); +void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf); +void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev); +int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state); +int sof_resume_clients(struct snd_sof_dev *sdev); +#else /* CONFIG_SND_SOC_SOF_CLIENT */ +static inline int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, + u32 id, const void *data, size_t size) +{ + return 0; +} + +static inline void sof_client_dev_unregister(struct snd_sof_dev *sdev, + const char *name, u32 id) +{ +} + +static inline int sof_register_clients(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_clients(struct snd_sof_dev *sdev) +{ +} + +static inline void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) +{ +} + +static inline void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) +{ +} + +static inline int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) +{ + return 0; +} + +static inline int sof_resume_clients(struct snd_sof_dev *sdev) +{ + return 0; +} +#endif /* CONFIG_SND_SOC_SOF_CLIENT */ + #endif diff --git a/sound/soc/sof/sof-probes.c b/sound/soc/sof/sof-probes.c deleted file mode 100644 index c79026cdb8c7..000000000000 --- a/sound/soc/sof/sof-probes.c +++ /dev/null @@ -1,364 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) -// -// This file is provided under a dual BSD/GPLv2 license. When using or -// redistributing this file, you may do so under either license. -// -// Copyright(c) 2019-2021 Intel Corporation. All rights reserved. -// Author: Cezary Rojewski -// - -#include -#include "ops.h" -#include "sof-priv.h" -#include "sof-probes.h" - -struct sof_probe_dma { - unsigned int stream_tag; - unsigned int dma_buffer_size; -} __packed; - -struct sof_ipc_probe_dma_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_dma dma[]; -} __packed; - -struct sof_ipc_probe_info_params { - struct sof_ipc_reply rhdr; - unsigned int num_elems; - union { - struct sof_probe_dma dma[0]; - struct sof_probe_point_desc desc[0]; - }; -} __packed; - -struct sof_ipc_probe_point_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_point_desc desc[]; -} __packed; - -struct sof_ipc_probe_point_remove_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - unsigned int buffer_id[]; -} __packed; - -/** - * sof_ipc_probe_init - initialize data probing - * @sdev: SOF sound device - * @stream_tag: Extractor stream tag - * @buffer_size: DMA buffer size to set for extractor - * - * Host chooses whether extraction is supported or not by providing - * valid stream tag to DSP. Once specified, stream described by that - * tag will be tied to DSP for extraction for the entire lifetime of - * probe. - * - * Probing is initialized only once and each INIT request must be - * matched by DEINIT call. - */ -static int sof_ipc_probe_init(struct snd_sof_dev *sdev, u32 stream_tag, - size_t buffer_size) -{ - struct sof_ipc_probe_dma_add_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, dma, 1); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT; - msg->num_elems = 1; - msg->dma[0].stream_tag = stream_tag; - msg->dma[0].dma_buffer_size = buffer_size; - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} - -/** - * sof_ipc_probe_deinit - cleanup after data probing - * @sdev: SOF sound device - * - * Host sends DEINIT request to free previously initialized probe - * on DSP side once it is no longer needed. DEINIT only when there - * are no probes connected and with all injectors detached. - */ -static int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) -{ - struct sof_ipc_cmd_hdr msg; - struct sof_ipc_reply reply; - - msg.size = sizeof(msg); - msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; - - return sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, - &reply, sizeof(reply)); -} - -static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, - void **params, size_t *num_params) -{ - struct sof_ipc_probe_info_params msg = {{{0}}}; - struct sof_ipc_probe_info_params *reply; - size_t bytes; - int ret; - - *params = NULL; - *num_params = 0; - - reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!reply) - return -ENOMEM; - msg.rhdr.hdr.size = sizeof(msg); - msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; - - ret = sof_ipc_tx_message(sdev->ipc, msg.rhdr.hdr.cmd, &msg, - msg.rhdr.hdr.size, reply, SOF_IPC_MSG_MAX_SIZE); - if (ret < 0 || reply->rhdr.error < 0) - goto exit; - - if (!reply->num_elems) - goto exit; - - if (cmd == SOF_IPC_PROBE_DMA_INFO) - bytes = sizeof(reply->dma[0]); - else - bytes = sizeof(reply->desc[0]); - bytes *= reply->num_elems; - *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL); - if (!*params) { - ret = -ENOMEM; - goto exit; - } - *num_params = reply->num_elems; - -exit: - kfree(reply); - return ret; -} - -/** - * sof_ipc_probe_points_info - retrieve list of active probe points - * @sdev: SOF sound device - * @desc: Returned list of active probes - * @num_desc: Returned count of active probes - * - * Host sends PROBE_POINT_INFO request to obtain list of active probe - * points, valid for disconnection when given probe is no longer - * required. - */ -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, - size_t *num_desc) -{ - return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_POINT_INFO, - (void **)desc, num_desc); -} -EXPORT_SYMBOL(sof_ipc_probe_points_info); - -/** - * sof_ipc_probe_points_add - connect specified probes - * @sdev: SOF sound device - * @desc: List of probe points to connect - * @num_desc: Number of elements in @desc - * - * Dynamically connects to provided set of endpoints. Immediately - * after connection is established, host must be prepared to - * transfer data from or to target stream given the probing purpose. - * - * Each probe point should be removed using PROBE_POINT_REMOVE - * request when no longer needed. - */ -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, size_t num_desc) -{ - struct sof_ipc_probe_point_add_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, desc, num_desc); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->num_elems = num_desc; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; - memcpy(&msg->desc[0], desc, size - sizeof(*msg)); - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} -EXPORT_SYMBOL(sof_ipc_probe_points_add); - -/** - * sof_ipc_probe_points_remove - disconnect specified probes - * @sdev: SOF sound device - * @buffer_id: List of probe points to disconnect - * @num_buffer_id: Number of elements in @desc - * - * Removes previously connected probes from list of active probe - * points and frees all resources on DSP side. - */ -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id) -{ - struct sof_ipc_probe_point_remove_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, buffer_id, num_buffer_id); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->num_elems = num_buffer_id; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; - memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} -EXPORT_SYMBOL(sof_ipc_probe_points_remove); - -static int sof_probe_compr_startup(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - int ret; - - ret = snd_sof_probe_compr_assign(sdev, cstream, dai); - if (ret < 0) { - dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); - return ret; - } - - sdev->extractor_stream_tag = ret; - return 0; -} - -static int sof_probe_compr_shutdown(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct sof_probe_point_desc *desc; - size_t num_desc; - int i, ret; - - /* disconnect all probe points */ - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) { - dev_err(dai->dev, "Failed to get probe points: %d\n", ret); - goto exit; - } - - for (i = 0; i < num_desc; i++) - sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1); - kfree(desc); - -exit: - ret = sof_ipc_probe_deinit(sdev); - if (ret < 0) - dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); - - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; - snd_compr_free_pages(cstream); - - return snd_sof_probe_compr_free(sdev, cstream, dai); -} - -static int sof_probe_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct snd_compr_runtime *rtd = cstream->runtime; - int ret; - - cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; - cstream->dma_buffer.dev.dev = sdev->dev; - ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); - if (ret < 0) - return ret; - - ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai); - if (ret < 0) - return ret; - - ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag, - rtd->dma_bytes); - if (ret < 0) { - dev_err(dai->dev, "Failed to init probe: %d\n", ret); - return ret; - } - - return 0; -} - -static int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - - return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai); -} - -static int sof_probe_compr_pointer(struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - - return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai); -} - -const struct snd_soc_cdai_ops sof_probe_compr_ops = { - .startup = sof_probe_compr_startup, - .shutdown = sof_probe_compr_shutdown, - .set_params = sof_probe_compr_set_params, - .trigger = sof_probe_compr_trigger, - .pointer = sof_probe_compr_pointer, -}; -EXPORT_SYMBOL(sof_probe_compr_ops); - -static int sof_probe_compr_copy(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - char __user *buf, size_t count) -{ - struct snd_compr_runtime *rtd = cstream->runtime; - unsigned int offset, n; - void *ptr; - int ret; - - if (count > rtd->buffer_size) - count = rtd->buffer_size; - - div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); - ptr = rtd->dma_area + offset; - n = rtd->buffer_size - offset; - - if (count < n) { - ret = copy_to_user(buf, ptr, count); - } else { - ret = copy_to_user(buf, ptr, n); - ret += copy_to_user(buf + n, rtd->dma_area, count - n); - } - - if (ret) - return count - ret; - return count; -} - -const struct snd_compress_ops sof_probe_compressed_ops = { - .copy = sof_probe_compr_copy, -}; -EXPORT_SYMBOL(sof_probe_compressed_ops); diff --git a/sound/soc/sof/sof-probes.h b/sound/soc/sof/sof-probes.h deleted file mode 100644 index 4a1ed2942d28..000000000000 --- a/sound/soc/sof/sof-probes.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ -/* - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * Copyright(c) 2019-2021 Intel Corporation. All rights reserved. - * Author: Cezary Rojewski - */ - -#ifndef __SOF_PROBES_H -#define __SOF_PROBES_H - -#include -#include - -struct snd_sof_dev; - -#define SOF_PROBE_INVALID_NODE_ID UINT_MAX - -struct sof_probe_point_desc { - unsigned int buffer_id; - unsigned int purpose; - unsigned int stream_tag; -} __packed; - -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, - size_t *num_desc); -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, - size_t num_desc); -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id); - -extern const struct snd_soc_cdai_ops sof_probe_compr_ops; -extern const struct snd_compress_ops sof_probe_compressed_ops; - -#endif diff --git a/sound/soc/sof/sof-utils.c b/sound/soc/sof/sof-utils.c new file mode 100644 index 000000000000..a3300ecee062 --- /dev/null +++ b/sound/soc/sof/sof-utils.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. +// +// Author: Keyon Jie +// + +#include +#include +#include +#include +#include +#include "sof-utils.h" + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bits from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (2) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} +EXPORT_SYMBOL(snd_sof_create_page_table); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-utils.h b/sound/soc/sof/sof-utils.h new file mode 100644 index 000000000000..6f902893807e --- /dev/null +++ b/sound/soc/sof/sof-utils.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + */ + +#ifndef __SOC_SOF_UTILS_H +#define __SOC_SOF_UTILS_H + +struct snd_dma_buffer; +struct device; + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size); + +#endif diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index 2335d0f06d42..104388c551cb 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -12,6 +12,7 @@ #include #include "sof-priv.h" #include "ops.h" +#include "sof-utils.h" #define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 #define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024