[alsa-devel] [PATCH v3 00/14] Sound Open Firmware (SOF) core
Sound Open Firmware (SOF) is a host and DSP architecture agnostic audio DSP firmware. SOF is not tied to any specific host architecture or any specific physical IO communication type (it will work with on SoC DSPs, or DSP connected via SPI/I2C).
SOF is also not coupled to any particular DSP architecture and has abstraction similar to Linux to allow porting to other DSP architectures.
This patch series introduces the SOF core and utilities. Support for Intel devices is provided as a follow-up series.
The SOF core manages all the core DSP services and ALSA/ASoC IO. The core is responsible for loading firmware, parsing topology, exposing PCMs and kcontrols, providing debug and trace mechanisms and performing IPC between host and DSP.
The SOF core also has logic to allow reuse of existing machine drivers for other platforms/machines without any code modification. i.e. DAI links can be modified at runtime to bind with SOF and SOF topologies instead of existing hard coded DAI links and topology.
Main changes since v2 (end of August):
There are honestly too many changes to be listed... The biggest changes are the introduction of an ABI check and runtime_pm, as well as countless improvements coming from product validation. We will provide a revision history starting with the next update.
Precisions:
Since I've been maintaining the SOF kernel parts for most of 2018, Liam and I mutually agreed that it would be more efficient for me to send the patches upstream to avoid any disconnects. The code in this patchset is directly squashed from the SOF development branch [1], which tracks Mark Brown's for-next branch on a weekly basis and the configurations used for testing are based on the defconfigs at [2]
Full disclosure on known limitation and issues (full list at [3])
a) the interaction with ASoC is not always perfect, there are a couple of points where a better solution is desired (use of private data, support for link DMA, etc). The known points are explicitly documented in the code and will be updated. To be clearer, it's our belief that SOF does not cripple ASoC in any way, and that merging this SOF core does not harm others. It's just complicated to get things right, especially on a couple of cases where the topology and DPCM frameworks generate complex flows that very few people understand. b) The get/put methods for controls generate an IPC and possibly a wake-up. This is not optimal and is being fixed. c) The debugfs parts will be updated for runtime_pm parts d) The topology free, firmware release and Hdaudio exit sequences will be updated. It is our intention to fully support module load/unload and free all resources. e) runtime_pm is being hardened and the use of SMART_SUSPEND was suggested.
Thank you for reviews and comments, we appreciate the time spent commenting on this large patchset. Thanks in particular to Alan Cox and Andy Shevchenko for their comments on an earlier version.
Pierre
[1] https://github.com/thesofproject/linux [2] https://github.com/thesofproject/kconfig [3] https://github.com/thesofproject/linux/issues
Liam Girdwood (12): ASoC: SOF: Add Sound Open Firmware driver core ASoC: SOF: Add Sound Open Firmware KControl support ASoC: SOF: Add driver debug support. ASoC: SOF: Add support for IPC IO between DSP and Host ASoC: SOF: Add PCM operations support ASoC: SOF: Add support for loading topologies ASoC: SOF: Add DSP firmware logger support ASoC: SOF: Add DSP HW abstraction operations ASoC: SOF: Add firmware loader support ASoC: SOF: Add userspace ABI support ASoC: SOF: Add PM support ASoC: SOF: Add Nocodec machine driver support
Pierre-Louis Bossart (2): ASoC: SOF: Add xtensa support ASoC: SOF: Add utils
include/sound/soc.h | 3 + include/sound/sof.h | 91 + include/sound/sof/control.h | 125 ++ include/sound/sof/dai-intel.h | 175 ++ include/sound/sof/dai.h | 75 + include/sound/sof/header.h | 158 ++ include/sound/sof/info.h | 118 ++ include/sound/sof/pm.h | 48 + include/sound/sof/stream.h | 149 ++ include/sound/sof/topology.h | 256 +++ include/sound/sof/trace.h | 66 + include/sound/sof/xtensa.h | 44 + include/uapi/sound/sof/abi.h | 63 + include/uapi/sound/sof/eq.h | 164 ++ include/uapi/sound/sof/fw.h | 67 + include/uapi/sound/sof/header.h | 27 + include/uapi/sound/sof/manifest.h | 188 ++ include/uapi/sound/sof/tokens.h | 95 + include/uapi/sound/sof/tone.h | 21 + include/uapi/sound/sof/trace.h | 93 + sound/soc/sof/control.c | 421 +++++ sound/soc/sof/core.c | 398 +++++ sound/soc/sof/debug.c | 176 ++ sound/soc/sof/ipc.c | 813 +++++++++ sound/soc/sof/loader.c | 322 ++++ sound/soc/sof/nocodec.c | 81 + sound/soc/sof/ops.c | 200 +++ sound/soc/sof/ops.h | 379 ++++ sound/soc/sof/pcm.c | 755 ++++++++ sound/soc/sof/pm.c | 403 +++++ sound/soc/sof/sof-priv.h | 574 ++++++ sound/soc/sof/topology.c | 2775 +++++++++++++++++++++++++++++ sound/soc/sof/trace.c | 295 +++ sound/soc/sof/utils.c | 173 ++ sound/soc/sof/xtensa/Kconfig | 3 + sound/soc/sof/xtensa/Makefile | 5 + sound/soc/sof/xtensa/core.c | 152 ++ 37 files changed, 9951 insertions(+) create mode 100644 include/sound/sof.h create mode 100644 include/sound/sof/control.h create mode 100644 include/sound/sof/dai-intel.h create mode 100644 include/sound/sof/dai.h create mode 100644 include/sound/sof/header.h create mode 100644 include/sound/sof/info.h create mode 100644 include/sound/sof/pm.h create mode 100644 include/sound/sof/stream.h create mode 100644 include/sound/sof/topology.h create mode 100644 include/sound/sof/trace.h create mode 100644 include/sound/sof/xtensa.h create mode 100644 include/uapi/sound/sof/abi.h create mode 100644 include/uapi/sound/sof/eq.h create mode 100644 include/uapi/sound/sof/fw.h create mode 100644 include/uapi/sound/sof/header.h create mode 100644 include/uapi/sound/sof/manifest.h create mode 100644 include/uapi/sound/sof/tokens.h create mode 100644 include/uapi/sound/sof/tone.h create mode 100644 include/uapi/sound/sof/trace.h create mode 100644 sound/soc/sof/control.c create mode 100644 sound/soc/sof/core.c create mode 100644 sound/soc/sof/debug.c create mode 100644 sound/soc/sof/ipc.c create mode 100644 sound/soc/sof/loader.c create mode 100644 sound/soc/sof/nocodec.c create mode 100644 sound/soc/sof/ops.c create mode 100644 sound/soc/sof/ops.h create mode 100644 sound/soc/sof/pcm.c create mode 100644 sound/soc/sof/pm.c create mode 100644 sound/soc/sof/sof-priv.h create mode 100644 sound/soc/sof/topology.c create mode 100644 sound/soc/sof/trace.c create mode 100644 sound/soc/sof/utils.c create mode 100644 sound/soc/sof/xtensa/Kconfig create mode 100644 sound/soc/sof/xtensa/Makefile create mode 100644 sound/soc/sof/xtensa/core.c
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The Sound Open Firmware driver core is a generic architecture independent layer that allows SOF to be used on many different different architectures and platforms. It abstracts DSP operations and IO methods so that the target DSP can be an internal memory mapped or external SPI or I2C based device. This abstraction also allows SOF to be run on many different VMs on the same physical HW.
SOF also requires some data in ASoC PCM runtime data for looking up SOF data during ASoC PCM operations.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/soc.h | 3 + include/sound/sof.h | 81 ++++++ sound/soc/sof/core.c | 398 +++++++++++++++++++++++++++ sound/soc/sof/sof-priv.h | 574 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 1056 insertions(+) create mode 100644 include/sound/sof.h create mode 100644 sound/soc/sof/core.c create mode 100644 sound/soc/sof/sof-priv.h
diff --git a/include/sound/soc.h b/include/sound/soc.h index 8ec1de856ee7..45f1be5b7541 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1186,6 +1186,9 @@ struct snd_soc_pcm_runtime { /* bit field */ unsigned int dev_registered:1; unsigned int pop_wait:1; + + /* private data - core does not touch */ + void *private; /* FIXME: still SOF-specific, needs to less ambiguous */ }; #define for_each_rtd_codec_dai(rtd, i, dai)\ for ((i) = 0; \ diff --git a/include/sound/sof.h b/include/sound/sof.h new file mode 100644 index 000000000000..371a14cb4a56 --- /dev/null +++ b/include/sound/sof.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood liam.r.girdwood@linux.intel.com + */ + +#ifndef __INCLUDE_SOUND_SOF_H +#define __INCLUDE_SOUND_SOF_H + +#include <linux/pci.h> +#include <sound/soc-acpi.h> + +struct snd_sof_dsp_ops; + +/* SOF probe type */ +enum sof_device_type { + SOF_DEVICE_PCI = 0, + SOF_DEVICE_APCI, + SOF_DEVICE_SPI +}; + +/* + * SOF Platform data. + */ +struct snd_sof_pdata { + u32 id; /* PCI/ACPI ID */ + const struct firmware *fw; + const char *drv_name; + const char *name; + const char *platform; + + /* parent device */ + struct device *dev; + enum sof_device_type type; + + /* descriptor */ + const struct sof_dev_desc *desc; + + /* SPI data */ + unsigned int gpio; + unsigned int active; + + /* machine */ + struct platform_device *pdev_mach; + const struct snd_soc_acpi_mach *machine; +}; + +/* + * Descriptor used for setting up SOF platform data. This is used when + * ACPI/PCI data is missing or mapped differently. + */ +struct sof_dev_desc { + /* list of machines using this configuration */ + struct snd_soc_acpi_mach *machines; + + /* Platform resource indexes in BAR / ACPI resources. */ + /* Must set to -1 if not used - add new items to end */ + int resindex_lpe_base; + int resindex_pcicfg_base; + int resindex_imr_base; + int irqindex_host_ipc; + int resindex_dma_base; + + /* DMA only valid when resindex_dma_base != -1*/ + int dma_engine; + int dma_size; + + /* IPC timeouts in ms */ + int ipc_timeout; + int boot_timeout; + + /* defaults for no codec mode */ + const char *nocodec_fw_filename; + const char *nocodec_tplg_filename; +}; + +#endif diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c new file mode 100644 index 000000000000..480e4a3110dd --- /dev/null +++ b/sound/soc/sof/core.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "ops.h" + +/* SOF defaults if not provided by the platform in ms */ +#define TIMEOUT_DEFAULT_IPC 5 +#define TIMEOUT_DEFAULT_BOOT 100 + +/* + * Generic object lookup APIs. + */ + +struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, + char *name) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (strcmp(spcm->pcm.dai_name, name) == 0) + return spcm; + + if (strcmp(spcm->pcm.caps[0].name, name) == 0) + return spcm; + + if (strcmp(spcm->pcm.caps[1].name, name) == 0) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == + comp_id) { + *direction = SNDRV_PCM_STREAM_PLAYBACK; + return spcm; + } + if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) { + *direction = SNDRV_PCM_STREAM_CAPTURE; + return spcm; + } + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, + unsigned int pcm_id) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) + return spcm; + } + + return NULL; +} + +struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, + char *name) +{ + struct snd_sof_widget *swidget = NULL; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (strcmp(name, swidget->widget->name) == 0) + return swidget; + } + + return NULL; +} + +struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, + char *name) +{ + struct snd_sof_dai *dai = NULL; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name) + continue; + + if (strcmp(name, dai->name) == 0) + return dai; + } + + return NULL; +} + +/* + * FW Panic/fault handling. + */ + +struct sof_panic_msg { + u32 id; + const char *msg; +}; + +/* standard FW panic types */ +static const struct sof_panic_msg panic_msg[] = { + {SOF_IPC_PANIC_MEM, "out of memory"}, + {SOF_IPC_PANIC_WORK, "work subsystem init failed"}, + {SOF_IPC_PANIC_IPC, "IPC subsystem init failed"}, + {SOF_IPC_PANIC_ARCH, "arch init failed"}, + {SOF_IPC_PANIC_PLATFORM, "platform init failed"}, + {SOF_IPC_PANIC_TASK, "scheduler init failed"}, + {SOF_IPC_PANIC_EXCEPTION, "runtime exception"}, + {SOF_IPC_PANIC_DEADLOCK, "deadlock"}, + {SOF_IPC_PANIC_STACK, "stack overflow"}, + {SOF_IPC_PANIC_IDLE, "can't enter idle"}, + {SOF_IPC_PANIC_WFI, "invalid wait state"}, +}; + +int snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, + u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_words) +{ + u32 code; + int i; + + /* is firmware dead ? */ + if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { + dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", + panic_code, tracep_code); + return 0; /* no fault ? */ + } + + code = panic_code & + (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); + + for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { + if (panic_msg[i].id == code) { + dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); + dev_err(sdev->dev, "error: trace point %8.8x\n", + tracep_code); + goto out; + } + } + + /* unknown error */ + dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); + dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); + +out: + dev_err(sdev->dev, "error: panic happen at %s:%d\n", + panic_info->filename, panic_info->linenum); + sof_oops(sdev, oops); + sof_stack(sdev, oops, stack, stack_words); + return -EFAULT; +} +EXPORT_SYMBOL(snd_sof_get_status); + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bites from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + u32 idx = (((i << 2) + i)) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u32 *pg_table; + + dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u32 *)(page_table + idx); + + if (i & 1) + *pg_table |= (pfn << 4); + else + *pg_table |= pfn; + } + + return pages; +} + +/* + * SOF Driver enumeration. + */ + +static int sof_probe(struct platform_device *pdev) +{ + struct snd_sof_pdata *plat_data = dev_get_platdata(&pdev->dev); + struct snd_sof_dev *sdev; + const char *drv_name; + const void *mach; + int size; + int ret; + + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + dev_dbg(&pdev->dev, "probing SOF DSP device....\n"); + + /* initialize sof device */ + sdev->dev = &pdev->dev; + sdev->parent = plat_data->dev; + if (plat_data->type == SOF_DEVICE_PCI) + sdev->pci = container_of(plat_data->dev, struct pci_dev, dev); + sdev->ops = plat_data->machine->pdata; + + sdev->pdata = plat_data; + sdev->first_boot = true; + INIT_LIST_HEAD(&sdev->pcm_list); + INIT_LIST_HEAD(&sdev->kcontrol_list); + INIT_LIST_HEAD(&sdev->widget_list); + INIT_LIST_HEAD(&sdev->dai_list); + INIT_LIST_HEAD(&sdev->route_list); + dev_set_drvdata(&pdev->dev, sdev); + spin_lock_init(&sdev->ipc_lock); + spin_lock_init(&sdev->hw_lock); + + /* set up platform component driver */ + snd_sof_new_platform_drv(sdev); + + /* set default timeouts if none provided */ + if (plat_data->desc->ipc_timeout == 0) + sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC; + else + sdev->ipc_timeout = plat_data->desc->ipc_timeout; + if (plat_data->desc->boot_timeout == 0) + sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT; + else + sdev->boot_timeout = plat_data->desc->boot_timeout; + + /* probe the DSP hardware */ + ret = snd_sof_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); + return ret; + } + + /* register any debug/trace capabilities */ + ret = snd_sof_dbg_init(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n", + ret); + goto dbg_err; + } + + /* init the IPC */ + sdev->ipc = snd_sof_ipc_init(sdev); + if (!sdev->ipc) { + dev_err(sdev->dev, "error: failed to init DSP IPC %d\n", ret); + goto ipc_err; + } + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", + ret); + goto fw_load_err; + } + + /* boot the firmware */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", + ret); + goto fw_run_err; + } + + /* init DMA trace */ + ret = snd_sof_init_trace(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to initialize trace %d\n", ret); + } + + /* hereafter all FW boot flows are for PM reasons */ + sdev->first_boot = false; + + /* now register audio DSP platform driver and dai */ + ret = snd_soc_register_component(&pdev->dev, &sdev->plat_drv, + sdev->ops->drv, + sdev->ops->num_drv); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to register DSP DAI driver %d\n", ret); + goto comp_err; + } + + drv_name = plat_data->machine->drv_name; + mach = (const void *)plat_data->machine; + size = sizeof(*plat_data->machine); + + /* register machine driver, pass machine info as pdata */ + plat_data->pdev_mach = + platform_device_register_data(sdev->dev, drv_name, + -1, mach, size); + + if (IS_ERR(plat_data->pdev_mach)) { + ret = PTR_ERR(plat_data->pdev_mach); + goto comp_err; + } + + dev_dbg(sdev->dev, "created machine %s\n", + dev_name(&plat_data->pdev_mach->dev)); + + return 0; + +comp_err: + snd_soc_unregister_component(&pdev->dev); + snd_sof_free_topology(sdev); +fw_run_err: + snd_sof_fw_unload(sdev); +fw_load_err: + snd_sof_ipc_free(sdev); +ipc_err: + snd_sof_free_debug(sdev); +dbg_err: + snd_sof_remove(sdev); + + return ret; +} + +static int sof_remove(struct platform_device *pdev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(&pdev->dev); + struct snd_sof_pdata *pdata = sdev->pdata; + + if (pdata && !IS_ERR(pdata->pdev_mach)) + platform_device_unregister(pdata->pdev_mach); + + snd_soc_unregister_component(&pdev->dev); + snd_sof_fw_unload(sdev); + snd_sof_ipc_free(sdev); + snd_sof_free_debug(sdev); + snd_sof_free_trace(sdev); + snd_sof_remove(sdev); + return 0; +} + +void snd_sof_shutdown(struct device *dev) +{ +} +EXPORT_SYMBOL(snd_sof_shutdown); + +static struct platform_driver sof_driver = { + .driver = { + .name = "sof-audio", + }, + + .probe = sof_probe, + .remove = sof_remove, +}; +module_platform_driver(sof_driver); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:sof-audio"); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h new file mode 100644 index 000000000000..f3c609320a3b --- /dev/null +++ b/sound/soc/sof/sof-priv.h @@ -0,0 +1,574 @@ +/* SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood liam.r.girdwood@linux.intel.com + */ + +#ifndef __SOUND_SOC_SOF_PRIV_H +#define __SOUND_SOC_SOF_PRIV_H + +#include <linux/device.h> +#include <sound/soc.h> +#include <sound/hdaudio.h> +#include <sound/sof/stream.h> +#include <sound/sof/control.h> +#include <sound/sof/dai.h> +#include <sound/sof/trace.h> +#include <sound/sof/topology.h> +#include <sound/sof/info.h> +#include <sound/sof/pm.h> +#include <uapi/sound/sof/fw.h> + +/* debug flags */ +#define SOF_DBG_REGS BIT(1) +#define SOF_DBG_MBOX BIT(2) +#define SOF_DBG_TEXT BIT(3) +#define SOF_DBG_PCI BIT(4) + +/* max BARs mmaped devices can use */ +#define SND_SOF_BARS 8 + +/* time in ms for runtime suspend delay */ +#define SND_SOF_SUSPEND_DELAY 2000 + +/* DMA buffer size for trace */ +#define DMA_BUF_SIZE_FOR_TRACE (PAGE_SIZE * 16) + +/* max number of FE PCMs before BEs */ +#define SOF_BE_PCM_BASE 16 + +#define SOF_IPC_DSP_REPLY 0 +#define SOF_IPC_HOST_REPLY 1 + +/* convenience constructor for DAI driver streams */ +#define SOF_DAI_STREAM(sname, scmin, scmax, srates, sfmt) \ + {.stream_name = sname, .channels_min = scmin, .channels_max = scmax, \ + .rates = srates, .formats = sfmt} + +#define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT) + +struct snd_sof_dev; +struct snd_sof_ipc_msg; +struct snd_sof_ipc; +struct snd_sof_debugfs_map; +struct snd_soc_tplg_ops; +struct snd_soc_component; +struct sof_intel_hda_dev; +struct snd_sof_pdata; + +/* + * SOF DSP HW abstraction operations. + * Used to abstract DSP HW architecture and any IO busses between host CPU + * and DSP device(s). + */ +struct snd_sof_dsp_ops { + /* probe and remove */ + int (*remove)(struct snd_sof_dev *sof_dev); + int (*probe)(struct snd_sof_dev *sof_dev); + + /* DSP core boot / reset */ + int (*run)(struct snd_sof_dev *sof_dev); + int (*stall)(struct snd_sof_dev *sof_dev); + int (*reset)(struct snd_sof_dev *sof_dev); + int (*core_power_up)(struct snd_sof_dev *sof_dev, + unsigned int core_mask); + int (*core_power_down)(struct snd_sof_dev *sof_dev, + unsigned int core_mask); + + /* pre/post firmware run */ + int (*pre_fw_run)(struct snd_sof_dev *sof_dev); + int (*post_fw_run)(struct snd_sof_dev *sof_dev); + + /* DSP PM */ + int (*suspend)(struct snd_sof_dev *sof_dev, int state); + int (*resume)(struct snd_sof_dev *sof_dev); + int (*runtime_suspend)(struct snd_sof_dev *sof_dev, int state); + int (*runtime_resume)(struct snd_sof_dev *sof_dev); + + /* DSP clocking */ + int (*set_clk)(struct snd_sof_dev *sof_dev, u32 freq); + + /* Register IO */ + void (*write)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u32 value); + u32 (*read)(struct snd_sof_dev *sof_dev, void __iomem *addr); + void (*write64)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u64 value); + u64 (*read64)(struct snd_sof_dev *sof_dev, void __iomem *addr); + + /* memcpy IO */ + void (*block_read)(struct snd_sof_dev *sof_dev, + u32 offset, void *dest, size_t size); + void (*block_write)(struct snd_sof_dev *sof_dev, + u32 offset, void *src, size_t size); + + /* doorbell */ + irqreturn_t (*irq_handler)(int irq, void *context); + irqreturn_t (*irq_thread)(int irq, void *context); + + /* mailbox */ + void (*mailbox_read)(struct snd_sof_dev *sof_dev, u32 offset, + void *addr, size_t bytes); + void (*mailbox_write)(struct snd_sof_dev *sof_dev, u32 offset, + void *addr, size_t bytes); + + /* ipc */ + int (*send_msg)(struct snd_sof_dev *sof_dev, + struct snd_sof_ipc_msg *msg); + int (*get_reply)(struct snd_sof_dev *sof_dev, + struct snd_sof_ipc_msg *msg); + int (*is_ready)(struct snd_sof_dev *sof_dev); + int (*cmd_done)(struct snd_sof_dev *sof_dev, int dir); + + /* debug */ + const struct snd_sof_debugfs_map *debug_map; + int debug_map_count; + void (*dbg_dump)(struct snd_sof_dev *sof_dev, u32 flags); + + /* connect pcm substream to a host stream */ + int (*pcm_open)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + /* disconnect pcm substream to a host stream */ + int (*pcm_close)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + + /* host stream hw params */ + int (*pcm_hw_params)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params); + + /* host stream trigger */ + int (*pcm_trigger)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd); + + /* host stream pointer */ + snd_pcm_uframes_t (*pcm_pointer)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + + /* FW loading */ + int (*load_firmware)(struct snd_sof_dev *sof_dev); + int (*load_module)(struct snd_sof_dev *sof_dev, + struct snd_sof_mod_hdr *hdr); + int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); + + /* host DMA trace initialization */ + int (*trace_init)(struct snd_sof_dev *sdev, u32 *stream_tag); + int (*trace_release)(struct snd_sof_dev *sdev); + int (*trace_trigger)(struct snd_sof_dev *sdev, int cmd); + + /* DAI ops */ + struct snd_soc_dai_driver *drv; + int num_drv; +}; + +/* DSP architecture specific callbacks for oops and stack dumps */ +struct sof_arch_ops { + void (*dsp_oops)(struct snd_sof_dev *sdev, void *oops); + void (*dsp_stack)(struct snd_sof_dev *sdev, void *oops, + u32 *stack, u32 stack_words); +}; + +/* DSP device HW descriptor mapping between bus ID and ops */ +struct sof_ops_table { + const struct sof_dev_desc *desc; + struct snd_sof_dsp_ops *ops; +}; + +/* FS entry for debug files that can expose DSP memories, registers */ +struct snd_sof_dfsentry_io { + struct dentry *dfsentry; + size_t size; + void __iomem *buf; + struct snd_sof_dev *sdev; +}; + +struct snd_sof_dfsentry_buf { + struct dentry *dfsentry; + size_t size; + void *buf; + struct snd_sof_dev *sdev; +}; + +/* Debug mapping for any DSP memory or registers that can used for debug */ +struct snd_sof_debugfs_map { + const char *name; + u32 bar; + u32 offset; + u32 size; +}; + +/* mailbox descriptor, used for host <-> DSP IPC */ +struct snd_sof_mailbox { + u32 offset; + size_t size; +}; + +/* IPC message descriptor for host <-> DSP IO */ +struct snd_sof_ipc_msg { + struct list_head list; + + /* message data */ + u32 header; + void *msg_data; + void *reply_data; + size_t msg_size; + size_t reply_size; + + wait_queue_head_t waitq; + u32 ipc_complete; +}; + +/* PCM stream, mapped to FW component */ +struct snd_sof_pcm_stream { + u32 comp_id; + struct snd_dma_buffer page_table; + struct sof_ipc_stream_posn posn; + struct snd_pcm_substream *substream; +}; + +/* ASLA SOF PCM device */ +struct snd_sof_pcm { + struct snd_sof_dev *sdev; + struct snd_soc_tplg_pcm pcm; + struct snd_sof_pcm_stream stream[2]; + u32 posn_offset[2]; + struct mutex mutex; /* access mutex */ + struct list_head list; /* list in sdev pcm list */ + struct snd_pcm_hw_params params[2]; + int restore_stream[2]; /* restore hw_params for paused stream */ +}; + +/* ALSA SOF Kcontrol device */ +struct snd_sof_control { + struct snd_sof_dev *sdev; + int comp_id; + int num_channels; + u32 readback_offset; /* offset to mmaped data if used */ + struct sof_ipc_ctrl_data *control_data; + u32 size; /* cdata size */ + enum sof_ipc_ctrl_cmd cmd; + u32 *volume_table; /* volume table computed from tlv data*/ + + struct mutex mutex; /* access mutex */ + struct list_head list; /* list in sdev control list */ +}; + +/* ASoC SOF DAPM widget */ +struct snd_sof_widget { + struct snd_sof_dev *sdev; + int comp_id; + int pipeline_id; + int complete; + int id; + + struct snd_soc_dapm_widget *widget; + struct mutex mutex; /* access mutex */ + struct list_head list; /* list in sdev widget list */ + + void *private; /* core does not touch this */ +}; + +/* ASoC SOF DAPM route */ +struct snd_sof_route { + struct snd_sof_dev *sdev; + + struct snd_soc_dapm_route route; + struct list_head list; /* list in sdev route list */ + + void *private; +}; + +/* ASoC DAI device */ +struct snd_sof_dai { + struct snd_sof_dev *sdev; + const char *name; + + struct sof_ipc_comp_dai comp_dai; + struct sof_ipc_dai_config *dai_config; + struct list_head list; /* list in sdev dai list */ +}; + +/* + * SOF Device Level. + */ +struct snd_sof_dev { + struct device *dev; + struct device *parent; + spinlock_t ipc_lock; /* lock for IPC users */ + spinlock_t hw_lock; /* lock for HW IO access */ + struct pci_dev *pci; + + /* ASoC components */ + struct snd_soc_component_driver plat_drv; + + /* DSP firmware boot */ + wait_queue_head_t boot_wait; + u32 boot_complete; + u32 first_boot; + + /* DSP HW differentiation */ + struct snd_sof_pdata *pdata; + const struct snd_sof_dsp_ops *ops; + struct sof_intel_hda_dev *hda; /* for HDA based DSP HW */ + const struct sof_arch_ops *arch_ops; + + /* IPC */ + struct snd_sof_ipc *ipc; + struct snd_sof_mailbox dsp_box; /* DSP initiated IPC */ + struct snd_sof_mailbox host_box; /* Host initiated IPC */ + struct snd_sof_mailbox stream_box; /* Stream position update */ + u64 irq_status; + int ipc_irq; + u32 next_comp_id; /* monotonic - reset during S3 */ + + /* memory bases for mmaped DSPs - set by dsp_init() */ + void __iomem *bar[SND_SOF_BARS]; /* DSP base address */ + int mmio_bar; + int mailbox_bar; + size_t dsp_oops_offset; + + /* debug */ + struct dentry *debugfs_root; + + /* firmware loader */ + int cl_bar; + struct snd_dma_buffer dmab; + struct snd_dma_buffer dmab_bdl; + struct sof_ipc_fw_ready fw_ready; + struct sof_ipc_fw_version fw_version; + + /* topology */ + struct snd_soc_tplg_ops *tplg_ops; + struct list_head pcm_list; + struct list_head kcontrol_list; + struct list_head widget_list; + struct list_head dai_list; + struct list_head route_list; + struct snd_soc_component *component; + int tplg_loaded; /* keep track of topology load success */ + + /* FW configuration */ + struct sof_ipc_dma_buffer_data *info_buffer; + struct sof_ipc_window *info_window; + + /* IPC timeouts in ms */ + int ipc_timeout; + int boot_timeout; + + /* Wait queue for code loading */ + wait_queue_head_t waitq; + int code_loading; + + /* DMA for Trace */ + struct snd_dma_buffer dmatb; + struct snd_dma_buffer dmatp; + int dma_trace_pages; + wait_queue_head_t trace_sleep; + u32 host_offset; + u32 dtrace_is_enabled; + u32 dtrace_error; + u32 msi_enabled; + + /* PM */ + u32 restore_kcontrols; /* restore kcontrols upon resume */ + + void *private; /* core does not touch this */ +}; + +#define sof_to_bus(s) (&(s)->hda->hbus.core) +#define sof_to_hbus(s) (&(s)->hda->hbus) + +/* + * SOF platform private struct used as drvdata of + * platform dev (e.g. pci/acpi/spi...) drvdata. + */ +struct sof_platform_priv { + struct snd_sof_pdata *sof_pdata; + struct platform_device *pdev_pcm; +}; + +/* + * Device Level. + */ +void snd_sof_shutdown(struct device *dev); +int snd_sof_runtime_suspend(struct device *dev); +int snd_sof_runtime_resume(struct device *dev); +int snd_sof_resume(struct device *dev); +int snd_sof_suspend(struct device *dev); +int snd_sof_prepare(struct device *dev); +int snd_sof_suspend_late(struct device *dev); + +void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); + +int snd_sof_create_page_table(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size); + +/* + * Firmware loading. + */ +int snd_sof_load_firmware(struct snd_sof_dev *sdev); +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); +int snd_sof_run_firmware(struct snd_sof_dev *sdev); +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module); +void snd_sof_fw_unload(struct snd_sof_dev *sdev); +int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset); + +/* + * IPC low level APIs. + */ +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev); +void snd_sof_ipc_free(struct snd_sof_dev *sdev); +int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id); +void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev); +void snd_sof_ipc_msgs_tx(struct snd_sof_dev *sdev); +int snd_sof_ipc_stream_pcm_params(struct snd_sof_dev *sdev, + struct sof_ipc_pcm_params *params); +int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, + size_t dspbox_size, u32 hostbox, + size_t hostbox_size); +int snd_sof_ipc_valid(struct snd_sof_dev *sdev); +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, void *tx_data, + size_t tx_bytes, void *rx_data, size_t rx_bytes); +struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, + char *name); +struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, + char *name); +struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd); +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, + char *name); +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction); +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, + unsigned int pcm_id); +void sof_ipc_drop_all(struct snd_sof_ipc *ipc); + +/* + * Stream IPC + */ +int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn); + +/* + * Mixer IPC + */ +int snd_sof_ipc_set_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd); +int snd_sof_ipc_get_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd); + +/* + * Topology. + */ +int snd_sof_init_topology(struct snd_sof_dev *sdev, + struct snd_soc_tplg_ops *ops); +int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file); +void snd_sof_free_topology(struct snd_sof_dev *sdev); +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget); + +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r); + +/* + * Trace/debug + */ +int snd_sof_init_trace(struct snd_sof_dev *sdev); +void snd_sof_release_trace(struct snd_sof_dev *sdev); +void snd_sof_free_trace(struct snd_sof_dev *sdev); +int snd_sof_dbg_init(struct snd_sof_dev *sdev); +void snd_sof_free_debug(struct snd_sof_dev *sdev); +int snd_sof_debugfs_io_create_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name); +int snd_sof_debugfs_buf_create_item(struct snd_sof_dev *sdev, + void *base, size_t size, + const char *name); +int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn); +void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev); +int snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, + u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_size); +int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); + +/* + * Platform specific ops. + */ +extern struct snd_compr_ops sof_compressed_ops; + +/* + * Kcontrols. + */ + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, + unsigned int size); +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *bytes, + unsigned int size); + +/* + * DSP Architectures. + */ +static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + if (sdev->arch_ops->dsp_stack) + sdev->arch_ops->dsp_stack(sdev, oops, stack, stack_words); +} + +static inline void sof_oops(struct snd_sof_dev *sdev, void *oops) +{ + if (sdev->arch_ops->dsp_oops) + sdev->arch_ops->dsp_oops(sdev, oops); +} + +extern const struct sof_arch_ops sof_xtensa_arch_ops; + +/* + * Utilities + */ +int sof_create_platform_device(struct sof_platform_priv *priv); +void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value); +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value); +u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr); +u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr); +void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes); +void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes); +void sof_block_write(struct snd_sof_dev *sdev, u32 offset, void *src, + size_t size); +void sof_block_read(struct snd_sof_dev *sdev, u32 offset, void *dest, + size_t size); + +#endif
On Tue, Dec 11, 2018 at 03:23:05PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The Sound Open Firmware driver core is a generic architecture independent layer that allows SOF to be used on many different different architectures and platforms. It abstracts DSP operations and IO methods so that the target DSP can be an internal memory mapped or external SPI or I2C based device. This abstraction also allows SOF to be run on many different VMs on the same physical HW.
SOF also requires some data in ASoC PCM runtime data for looking up SOF data during ASoC PCM operations.
+/* SOF defaults if not provided by the platform in ms */ +#define TIMEOUT_DEFAULT_IPC 5 +#define TIMEOUT_DEFAULT_BOOT 100
Perhaps _MS at the end?
+struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev,
char *name)
+{
- struct snd_sof_dai *dai = NULL;
- list_for_each_entry(dai, &sdev->dai_list, list) {
if (!dai->name)
continue;
if (strcmp(name, dai->name) == 0)
return dai;
Perhaps
if (dai->name && strcmp(...))
- }
- return NULL;
+}
+int snd_sof_create_page_table(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
unsigned char *page_table, size_t size)
+{
- int i, pages;
- pages = snd_sgbuf_aligned_pages(size);
- dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n",
dmab->area, size, pages);
- for (i = 0; i < pages; i++) {
u32 idx = (((i << 2) + i)) >> 1;
Looks like
u32 idx = (5 * i) >> 1;
u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
u32 *pg_table;
dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn);
pg_table = (u32 *)(page_table + idx);
if (i & 1)
*pg_table |= (pfn << 4);
else
*pg_table |= pfn;
- }
- return pages;
+}
- if (plat_data->type == SOF_DEVICE_PCI)
sdev->pci = container_of(plat_data->dev, struct pci_dev, dev);
Why not to use generic functions? dev_is_pci() to_pci_dev()
- /* register machine driver, pass machine info as pdata */
- plat_data->pdev_mach =
platform_device_register_data(sdev->dev, drv_name,
-1, mach, size);
PLATFORM_DEVID_AUTO (IIRC the name)?
+static int sof_remove(struct platform_device *pdev) +{
- struct snd_sof_dev *sdev = dev_get_drvdata(&pdev->dev);
- struct snd_sof_pdata *pdata = sdev->pdata;
- if (pdata && !IS_ERR(pdata->pdev_mach))
platform_device_unregister(pdata->pdev_mach);
I'm wondering if pdata could be ever NULL here. Also, as I mentioned internally the patch to accept error pointers would be in v4.21.
- snd_soc_unregister_component(&pdev->dev);
- snd_sof_fw_unload(sdev);
- snd_sof_ipc_free(sdev);
- snd_sof_free_debug(sdev);
- snd_sof_free_trace(sdev);
- snd_sof_remove(sdev);
- return 0;
+}
+void snd_sof_shutdown(struct device *dev) +{ +} +EXPORT_SYMBOL(snd_sof_shutdown);
No need to provide an empty stub. Why not to add when it would have something useful?
+/* max BARs mmaped devices can use */ +#define SND_SOF_BARS 8
+/* time in ms for runtime suspend delay */ +#define SND_SOF_SUSPEND_DELAY 2000
_MS ?
- struct mutex mutex; /* access mutex */
- struct list_head list; /* list in sdev pcm list */
- struct snd_pcm_hw_params params[2];
- int restore_stream[2]; /* restore hw_params for paused stream */
- u32 readback_offset; /* offset to mmaped data if used */
- struct sof_ipc_ctrl_data *control_data;
- u32 size; /* cdata size */
- enum sof_ipc_ctrl_cmd cmd;
- u32 *volume_table; /* volume table computed from tlv data*/
- struct mutex mutex; /* access mutex */
- struct list_head list; /* list in sdev control list */
Thanks for the late night review Andy :-)
On 12/11/18 4:20 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:05PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The Sound Open Firmware driver core is a generic architecture independent layer that allows SOF to be used on many different different architectures and platforms. It abstracts DSP operations and IO methods so that the target DSP can be an internal memory mapped or external SPI or I2C based device. This abstraction also allows SOF to be run on many different VMs on the same physical HW.
SOF also requires some data in ASoC PCM runtime data for looking up SOF data during ASoC PCM operations. +/* SOF defaults if not provided by the platform in ms */ +#define TIMEOUT_DEFAULT_IPC 5 +#define TIMEOUT_DEFAULT_BOOT 100
Perhaps _MS at the end?
ok
+struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev,
char *name)
+{
- struct snd_sof_dai *dai = NULL;
- list_for_each_entry(dai, &sdev->dai_list, list) {
if (!dai->name)
continue;
if (strcmp(name, dai->name) == 0)
return dai;
Perhaps
if (dai->name && strcmp(...))
ok
- }
- return NULL;
+} +int snd_sof_create_page_table(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
unsigned char *page_table, size_t size)
+{
- int i, pages;
- pages = snd_sgbuf_aligned_pages(size);
- dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n",
dmab->area, size, pages);
- for (i = 0; i < pages; i++) {
u32 idx = (((i << 2) + i)) >> 1;
Looks like
u32 idx = (5 * i) >> 1;
Yes, but we'd also want an explanation of what we try to multiply by 2.5... Liam or Keyon, can you chime in?
u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
u32 *pg_table;
dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn);
pg_table = (u32 *)(page_table + idx);
if (i & 1)
*pg_table |= (pfn << 4);
else
*pg_table |= pfn;
- }
- return pages;
+}
- if (plat_data->type == SOF_DEVICE_PCI)
sdev->pci = container_of(plat_data->dev, struct pci_dev, dev);
Why not to use generic functions? dev_is_pci() to_pci_dev()
ok
- /* register machine driver, pass machine info as pdata */
- plat_data->pdev_mach =
platform_device_register_data(sdev->dev, drv_name,
-1, mach, size);
PLATFORM_DEVID_AUTO (IIRC the name)?
did you mean replace -1 by PLATFORM_DEVID_NONE?
+static int sof_remove(struct platform_device *pdev) +{
- struct snd_sof_dev *sdev = dev_get_drvdata(&pdev->dev);
- struct snd_sof_pdata *pdata = sdev->pdata;
- if (pdata && !IS_ERR(pdata->pdev_mach))
platform_device_unregister(pdata->pdev_mach);
I'm wondering if pdata could be ever NULL here.
I didn't find an error flow that yields NULL but Liam reported some issues that made no sense so might be safer with NULL
Also, as I mentioned internally the patch to accept error pointers would be in v4.21.
Indeed, but I can't rely on this just yet.
- snd_soc_unregister_component(&pdev->dev);
- snd_sof_fw_unload(sdev);
- snd_sof_ipc_free(sdev);
- snd_sof_free_debug(sdev);
- snd_sof_free_trace(sdev);
- snd_sof_remove(sdev);
- return 0;
+}
+void snd_sof_shutdown(struct device *dev) +{ +} +EXPORT_SYMBOL(snd_sof_shutdown);
No need to provide an empty stub. Why not to add when it would have something useful?
ok
+/* max BARs mmaped devices can use */ +#define SND_SOF_BARS 8
+/* time in ms for runtime suspend delay */ +#define SND_SOF_SUSPEND_DELAY 2000
_MS ?
ok
- struct mutex mutex; /* access mutex */
- struct list_head list; /* list in sdev pcm list */
- struct snd_pcm_hw_params params[2];
- int restore_stream[2]; /* restore hw_params for paused stream */
- u32 readback_offset; /* offset to mmaped data if used */
- struct sof_ipc_ctrl_data *control_data;
- u32 size; /* cdata size */
- enum sof_ipc_ctrl_cmd cmd;
- u32 *volume_table; /* volume table computed from tlv data*/
- struct mutex mutex; /* access mutex */
- struct list_head list; /* list in sdev control list */
On Tue, 11 Dec 2018 22:23:05 +0100, Pierre-Louis Bossart wrote:
+static int sof_probe(struct platform_device *pdev) +{
....
- /* register any debug/trace capabilities */
- ret = snd_sof_dbg_init(sdev);
- if (ret < 0) {
dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n",
ret);
goto dbg_err;
- }
So that's the problem Andy suggested. snd_sof_dbg_init() returns an error for whatever reason, and this is considered as a fatal error and the probe fails. The error about debugfs is no fatal error, it should continue.
Moreover, as I mentioned for another patch (I read that before this one due to the mail delivery order :), it would fail always when CONFIG_DEBUGFS=n. At best, create a wrapper for CONFIG_DEBUGFS=n to return 0 (and void for free).
thanks,
Takashi
On 12/12/18 1:51 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:05 +0100, Pierre-Louis Bossart wrote:
+static int sof_probe(struct platform_device *pdev) +{
....
- /* register any debug/trace capabilities */
- ret = snd_sof_dbg_init(sdev);
- if (ret < 0) {
dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n",
ret);
goto dbg_err;
- }
So that's the problem Andy suggested. snd_sof_dbg_init() returns an error for whatever reason, and this is considered as a fatal error and the probe fails. The error about debugfs is no fatal error, it should continue.
Moreover, as I mentioned for another patch (I read that before this one due to the mail delivery order :), it would fail always when CONFIG_DEBUGFS=n. At best, create a wrapper for CONFIG_DEBUGFS=n to return 0 (and void for free).
ok. we'll fix this, point taken.
Thanks a lot Pierre, Liam and all the people at Intel for does such a great job with this project!
Few comments inline:
<snip>
+/* SOF probe type */ +enum sof_device_type {
SOF_DEVICE_PCI = 0,
SOF_DEVICE_APCI,
SOF_DEVICE_SPI
Maybe comma after last element here? We have at least one more: SOF_DEVICE_DT.
+static int sof_probe(struct platform_device *pdev) +{
struct snd_sof_pdata *plat_data = dev_get_platdata(&pdev->dev);
struct snd_sof_dev *sdev;
const char *drv_name;
const void *mach;
int size;
int ret;
sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
if (!sdev)
return -ENOMEM;
dev_dbg(&pdev->dev, "probing SOF DSP device....\n");
/* initialize sof device */
sdev->dev = &pdev->dev;
sdev->parent = plat_data->dev;
if (plat_data->type == SOF_DEVICE_PCI)
sdev->pci = container_of(plat_data->dev, struct pci_dev, dev);
sdev->ops = plat_data->machine->pdata;
sdev->pdata = plat_data;
sdev->first_boot = true;
INIT_LIST_HEAD(&sdev->pcm_list);
INIT_LIST_HEAD(&sdev->kcontrol_list);
INIT_LIST_HEAD(&sdev->widget_list);
INIT_LIST_HEAD(&sdev->dai_list);
INIT_LIST_HEAD(&sdev->route_list);
dev_set_drvdata(&pdev->dev, sdev);
spin_lock_init(&sdev->ipc_lock);
spin_lock_init(&sdev->hw_lock);
/* set up platform component driver */
snd_sof_new_platform_drv(sdev);
/* set default timeouts if none provided */
if (plat_data->desc->ipc_timeout == 0)
sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC;
else
sdev->ipc_timeout = plat_data->desc->ipc_timeout;
if (plat_data->desc->boot_timeout == 0)
sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT;
else
sdev->boot_timeout = plat_data->desc->boot_timeout;
/* probe the DSP hardware */
ret = snd_sof_probe(sdev);
Something looks fishy here.
I couldn't find this function's definition anywhere in the first patch which means that after first patch the code doesn't compile :D.
For bisection reasons would be great if the code compiles after each patch.
<snip>
/* now register audio DSP platform driver and dai */
ret = snd_soc_register_component(&pdev->dev, &sdev->plat_drv,
sdev->ops->drv,
sdev->ops->num_drv);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to register DSP DAI driver %d\n", ret);
goto comp_err;
This goes to snd_soc_unregister_component which is not really needed because the component didn't register succcesfully.
<snd>
+comp_err:
snd_soc_unregister_component(&pdev->dev);
snd_sof_free_topology(sdev);
It is not very clear to me where does the snd_sof_load_topology it is called so that there is a need to free it here.
+static int sof_remove(struct platform_device *pdev) +{
struct snd_sof_dev *sdev = dev_get_drvdata(&pdev->dev);
struct snd_sof_pdata *pdata = sdev->pdata;
if (pdata && !IS_ERR(pdata->pdev_mach))
platform_device_unregister(pdata->pdev_mach);
snd_soc_unregister_component(&pdev->dev);
snd_sof_fw_unload(sdev);
snd_sof_ipc_free(sdev);
snd_sof_free_debug(sdev);
snd_sof_free_trace(sdev);
snd_sof_remove(sdev);
There is no snd_sof_free_topology here. Which means that either this call is not needed in the probe function or it is missing from here.
+void snd_sof_shutdown(struct device *dev) +{ +} +EXPORT_SYMBOL(snd_sof_shutdown);
I would avoid this empty function calls for the moment. Will add it later when there is a need for it.
<snip>
+/*
- SOF Device Level.
- */
+struct snd_sof_dev {
struct device *dev;
struct device *parent;
spinlock_t ipc_lock; /* lock for IPC users */
spinlock_t hw_lock; /* lock for HW IO access */
struct pci_dev *pci;
/* ASoC components */
struct snd_soc_component_driver plat_drv;
/* DSP firmware boot */
wait_queue_head_t boot_wait;
u32 boot_complete;
u32 first_boot;
/* DSP HW differentiation */
struct snd_sof_pdata *pdata;
const struct snd_sof_dsp_ops *ops;
struct sof_intel_hda_dev *hda; /* for HDA based DSP HW */
Just a design question. Is it ok to hold SOC specific data here? For example on arm there is no HDA but there is some other DAI. Would it be ok for me to just add it here?
thanks, Daniel.
Hi Daniel,
On 12/12/18 2:42 PM, Daniel Baluta wrote:
Thanks a lot Pierre, Liam and all the people at Intel for does such a great job with this project!
you're welcome, thanks for the comments
Few comments inline:
<snip>
+/* SOF probe type */ +enum sof_device_type {
SOF_DEVICE_PCI = 0,
SOF_DEVICE_APCI,
SOF_DEVICE_SPI
Maybe comma after last element here? We have at least one more: SOF_DEVICE_DT.
Based on the comments from Andy, we've already removed this type since it's not really needed any longer. We would probably need to have a sof-dt-dev.c file to deal with the different enumeration style but we'd need a platform to work with (or patches welcome). Hint hint wink wink.
+static int sof_probe(struct platform_device *pdev) +{
struct snd_sof_pdata *plat_data = dev_get_platdata(&pdev->dev);
struct snd_sof_dev *sdev;
const char *drv_name;
const void *mach;
int size;
int ret;
sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
if (!sdev)
return -ENOMEM;
dev_dbg(&pdev->dev, "probing SOF DSP device....\n");
/* initialize sof device */
sdev->dev = &pdev->dev;
sdev->parent = plat_data->dev;
if (plat_data->type == SOF_DEVICE_PCI)
sdev->pci = container_of(plat_data->dev, struct pci_dev, dev);
sdev->ops = plat_data->machine->pdata;
sdev->pdata = plat_data;
sdev->first_boot = true;
INIT_LIST_HEAD(&sdev->pcm_list);
INIT_LIST_HEAD(&sdev->kcontrol_list);
INIT_LIST_HEAD(&sdev->widget_list);
INIT_LIST_HEAD(&sdev->dai_list);
INIT_LIST_HEAD(&sdev->route_list);
dev_set_drvdata(&pdev->dev, sdev);
spin_lock_init(&sdev->ipc_lock);
spin_lock_init(&sdev->hw_lock);
/* set up platform component driver */
snd_sof_new_platform_drv(sdev);
/* set default timeouts if none provided */
if (plat_data->desc->ipc_timeout == 0)
sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC;
else
sdev->ipc_timeout = plat_data->desc->ipc_timeout;
if (plat_data->desc->boot_timeout == 0)
sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT;
else
sdev->boot_timeout = plat_data->desc->boot_timeout;
/* probe the DSP hardware */
ret = snd_sof_probe(sdev);
Something looks fishy here.
I couldn't find this function's definition anywhere in the first patch which means that after first patch the code doesn't compile :D.
For bisection reasons would be great if the code compiles after each patch.
the build is only added last (in the 37th patch). We didn't really plan to be able to compile the initial patches one after the other. we *could* add the core compilation earlier and add support for each device incrementally, but you need a set of files that are somewhat interdependent first.
<snip>
/* now register audio DSP platform driver and dai */
ret = snd_soc_register_component(&pdev->dev, &sdev->plat_drv,
sdev->ops->drv,
sdev->ops->num_drv);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to register DSP DAI driver %d\n", ret);
goto comp_err;
This goes to snd_soc_unregister_component which is not really needed because the component didn't register succcesfully.
Right. we went through a series of checks for error flows and missed this one. Thanks for reporting this.
<snd>
+comp_err:
snd_soc_unregister_component(&pdev->dev);
snd_sof_free_topology(sdev);
It is not very clear to me where does the snd_sof_load_topology it is called so that there is a need to free it here.
the free_topology part is something we are currently looking it. It seems that the framework frees almost everything. It's one of the difficulties we are facing, it's not always obvious what the topology framework does for you and what needs to be freed manually. It's a known open we will fix shortly.
+static int sof_remove(struct platform_device *pdev) +{
struct snd_sof_dev *sdev = dev_get_drvdata(&pdev->dev);
struct snd_sof_pdata *pdata = sdev->pdata;
if (pdata && !IS_ERR(pdata->pdev_mach))
platform_device_unregister(pdata->pdev_mach);
snd_soc_unregister_component(&pdev->dev);
snd_sof_fw_unload(sdev);
snd_sof_ipc_free(sdev);
snd_sof_free_debug(sdev);
snd_sof_free_trace(sdev);
snd_sof_remove(sdev);
There is no snd_sof_free_topology here. Which means that either this call is not needed in the probe function or it is missing from here.
The topology is leaded in sof_pcm_probe and released in sof_pcm_remove.
But like I said above it's not clear if we need to really free anything...
+void snd_sof_shutdown(struct device *dev) +{ +} +EXPORT_SYMBOL(snd_sof_shutdown);
I would avoid this empty function calls for the moment. Will add it later when there is a need for it.
yes, we removed it already.
<snip>
+/*
- SOF Device Level.
- */
+struct snd_sof_dev {
struct device *dev;
struct device *parent;
spinlock_t ipc_lock; /* lock for IPC users */
spinlock_t hw_lock; /* lock for HW IO access */
struct pci_dev *pci;
/* ASoC components */
struct snd_soc_component_driver plat_drv;
/* DSP firmware boot */
wait_queue_head_t boot_wait;
u32 boot_complete;
u32 first_boot;
/* DSP HW differentiation */
struct snd_sof_pdata *pdata;
const struct snd_sof_dsp_ops *ops;
struct sof_intel_hda_dev *hda; /* for HDA based DSP HW */
Just a design question. Is it ok to hold SOC specific data here? For example on arm there is no HDA but there is some other DAI. Would it be ok for me to just add it here?
Darn, this was added more than a year ago and should have been cleaned-up. There is absolutely no reason why we have an Intel-specific definition in there, as you mentioned it it should have been a pointer to SoC-specific data. we'll fix this. nice catch, much appreciated.
-Pierre
<snip>
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
<snip>
+/* SOF defaults if not provided by the platform in ms */
/* firmware loader */
int cl_bar;
This cl_bar variable seems to be only assigned to but never used.
I propose to remove it if there is no much trouble.
On 1/29/19 10:49 AM, Daniel Baluta wrote:
<snip>
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
<snip>
+/* SOF defaults if not provided by the platform in ms */
/* firmware loader */
int cl_bar;
This cl_bar variable seems to be only assigned to but never used.
I propose to remove it if there is no much trouble.
Indeed, not sure what it's supposed to be used for, it was part of the initial commit for all the files below and never used. If it's not been used in a year it's probably useless, I'll remove it.
git grep cl_bar
intel/bdw.c: sdev->cl_bar = BDW_DSP_BAR; intel/byt.c: sdev->cl_bar = BYT_DSP_BAR; intel/byt.c: sdev->cl_bar = BYT_DSP_BAR; intel/hsw.c: sdev->cl_bar = HSW_DSP_BAR; sof-priv.h: int cl_bar;
From: Liam Girdwood liam.r.girdwood@linux.intel.com
SOF exposes regular ALSA Kcontrols that are defined by topology. This patch converts the Kcontrol IO to DSP IPC.
The current implementation is not optimal and can be improved: a) for every get/put the host wakes up the DSP and generates an IPC. The kernel should cache the values and generate an IPC only when strictly necessary. b) the firmware can be implemented to only instantiate the pipelines and related control-related parts that are needed at a given time, and power-gate the relevant SRAM blocks.
The development tasks for these two improvements has started, once validated they willl be provided in an update.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/control.c | 421 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 sound/soc/sof/control.c
diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c new file mode 100644 index 000000000000..2000f62e931b --- /dev/null +++ b/sound/soc/sof/control.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +/* Mixer Controls */ + +#include <linux/pm_runtime.h> +#include "sof-priv.h" + +static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) +{ + if (value >= size) + return volume_map[size - 1]; + + return volume_map[value]; +} + +static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: volume get failed to resume %d\n", + ret); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_VOLUME); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = + ipc_to_mixer(cdata->chanv[i].value, + scontrol->volume_table, sm->max + 1); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: volume get failed to idle %d\n", + err); + return 0; +} + +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: volume put failed to resume %d\n", + ret); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = + mixer_to_ipc(ucontrol->value.integer.value[i], + scontrol->volume_table, sm->max + 1); + cdata->chanv[i].channel = i; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_VOLUME); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: volume put failed to idle %d\n", + err); + return 0; +} + +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + struct snd_sof_control *scontrol = se->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: enum get failed to resume %d\n", + ret); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_ENUM); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: enum get failed to idle %d\n", + ret); + return 0; +} + +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + struct snd_sof_control *scontrol = se->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: enum put failed to resume %d\n", + ret); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) + cdata->chanv[i].value = ucontrol->value.integer.value[i]; + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_SET, + SOF_CTRL_CMD_ENUM); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: enum put failed to idle %d\n", + err); + return 0; +} + +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct sof_abi_hdr *data = cdata->data; + size_t size; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: bytes get failed to resume %d\n", + ret); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_DATA, + SOF_CTRL_TYPE_DATA_GET, scontrol->cmd); + size = data->size + sizeof(*data); + if (size > be->max) { + dev_err(sdev->dev, "error: DSP sent %zu bytes max is %d\n", + size, be->max); + ret = -EINVAL; + goto out; + } + + /* copy back to kcontrol */ + memcpy(ucontrol->value.bytes.data, data, size); + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: bytes get failed to idle %d\n", + err); + return ret; +} + +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct sof_abi_hdr *data = cdata->data; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: bytes put failed to resume %d\n", + ret); + return ret; + } + + if (data->size > be->max) { + dev_err(sdev->dev, "error: size too big %d bytes max is %d\n", + data->size, be->max); + ret = -EINVAL; + goto out; + } + + /* copy from kcontrol */ + memcpy(data, ucontrol->value.bytes.data, data->size); + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, scontrol->cmd); + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: bytes put failed to idle %d\n", + err); + return ret; +} + +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_ctl_tlv header; + struct snd_ctl_tlv __user *tlvd = + (struct snd_ctl_tlv __user *)binary_data; + int ret; + int err; + int max_size = SOF_IPC_MSG_MAX_SIZE - + sizeof(const struct sof_ipc_ctrl_data); + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: bytes_ext put failed to resume %d\n", + ret); + return ret; + } + + /* The beginning of bytes data contains a header from where + * the length (as bytes) is needed to know the correct copy + * length of data from tlvd->tlv. + */ + if (copy_from_user(&header, tlvd, sizeof(const struct snd_ctl_tlv))) { + ret = -EFAULT; + goto out; + } + /* The maximum length that can be copied is limited by IPC max + * length and topology defined length for ext bytes control. + */ + max_size = (be->max < max_size) ? be->max : max_size; + if (header.length > max_size) { + dev_err(sdev->dev, "error: Bytes data size %d exceeds max %d.\n", + header.length, max_size); + ret = -EINVAL; + goto out; + } + + /* Check that header id matches the command */ + if (header.numid != scontrol->cmd) { + dev_err(sdev->dev, "error: incorrect numid %d\n", header.numid); + ret = -EINVAL; + goto out; + } + + if (copy_from_user(cdata->data, tlvd->tlv, header.length)) { + ret = -EFAULT; + goto out; + } + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err(sdev->dev, "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + ret = -EINVAL; + goto out; + } + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) { + dev_err(sdev->dev, "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + ret = -EINVAL; + goto out; + } + + if (cdata->data->size + sizeof(const struct sof_abi_hdr) > max_size) { + dev_err(sdev->dev, "error: Mismatch in ABI data size (truncated?).\n"); + ret = -EINVAL; + goto out; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, scontrol->cmd); + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: bytes_ext put failed to idle %d\n", + err); + + return ret; +} + +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_ctl_tlv header; + struct snd_ctl_tlv __user *tlvd = + (struct snd_ctl_tlv __user *)binary_data; + int max_size = SOF_IPC_MSG_MAX_SIZE - + sizeof(const struct sof_ipc_ctrl_data); + int data_size; + int err; + int ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: bytes_ext get failed to resume %d\n", + ret); + return ret; + } + + /* Decrement the limit by ext bytes header size to ensure + * the user space buffer is not exceeded. + */ + size -= sizeof(const struct snd_ctl_tlv); + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* get all the component data from DSP */ + ret = snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_DATA, + SOF_CTRL_TYPE_DATA_GET, scontrol->cmd); + + /* Prevent read of other kernel data or possibly corrupt response */ + data_size = cdata->data->size + sizeof(const struct sof_abi_hdr); + max_size = (size < max_size) ? size : max_size; + max_size = (be->max < max_size) ? be->max : max_size; + if (data_size > max_size) { + dev_err(sdev->dev, "error: user data size %d exceeds max size %d.\n", + data_size, max_size); + ret = -EINVAL; + goto out; + } + + header.numid = scontrol->cmd; + header.length = data_size; + if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv))) { + ret = -EFAULT; + goto out; + } + + if (copy_to_user(tlvd->tlv, cdata->data, data_size)) + return -EFAULT; + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: bytes_ext get failed to idle %d\n", + err); + return ret; +}
On Tue, Dec 11, 2018 at 03:23:06PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
SOF exposes regular ALSA Kcontrols that are defined by topology. This patch converts the Kcontrol IO to DSP IPC.
The current implementation is not optimal and can be improved: a) for every get/put the host wakes up the DSP and generates an IPC. The kernel should cache the values and generate an IPC only when strictly necessary. b) the firmware can be implemented to only instantiate the pipelines and related control-related parts that are needed at a given time, and power-gate the relevant SRAM blocks.
The development tasks for these two improvements has started, once validated they willl be provided in an update.
- max_size = (be->max < max_size) ? be->max : max_size;
min() / max() ?
- max_size = (be->max < max_size) ? be->max : max_size;
min() / max() ?
Good catch, this can be simplified.
Not sure I like max_size = min (be->max, max_size), it's confusing.
Maybe
if (be->max < max_size)
max_size = be->max;
On Tue, Dec 11, 2018 at 04:48:37PM -0600, Pierre-Louis Bossart wrote:
- max_size = (be->max < max_size) ? be->max : max_size;
min() / max() ?
Good catch, this can be simplified.
Not sure I like max_size = min (be->max, max_size), it's confusing.
Hmm... It shows intention. For _max size_ you choose _minimum_ value out of two.
Maybe
if (be->max < max_size)
max_size = be->max;
It's minor, just choose one style and use it.
- max_size = (be->max < max_size) ? be->max : max_size;
min() / max() ?
The use of min() and min3() seems to generate tons of sparse warnings (see below), so I'll leave the code with an explicit test and a comment stating why.
sparse helped us find quite a few issues with endianess, const, iomem qualifiers, I'd like to keep it quiet on non-issues.
CHECK sound/soc/sof/control.c sound/soc/sof/control.c:300:20: warning: expression using sizeof(void) sound/soc/sof/control.c:300:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void) sound/soc/sof/control.c:396:20: warning: expression using sizeof(void)
On Tue, 11 Dec 2018 22:23:06 +0100, Pierre-Louis Bossart wrote:
+int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
....
- /* read back each channel */
- for (i = 0; i < channels; i++)
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
enum type needs to access ucontrol->value.enumerated.item[i]. This has a different size, hence using integer.value[] would be broken on BE archs.
+int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
....
- /* update each channel */
- for (i = 0; i < channels; i++)
cdata->chanv[i].value = ucontrol->value.integer.value[i];
Ditto.
+int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
....
- size = data->size + sizeof(*data);
- if (size > be->max) {
dev_err(sdev->dev, "error: DSP sent %zu bytes max is %d\n",
size, be->max);
ret = -EINVAL;
goto out;
- }
- /* copy back to kcontrol */
- memcpy(ucontrol->value.bytes.data, data, size);
I *hope* that the data size max was already examined not to exceed ucontrol data array size beforehand. But a sanity check to catch a buffer overflow here won't hurt. Ditto for *_put().
+int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
const unsigned int __user *binary_data,
unsigned int size)
+{
- struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
- struct snd_sof_control *scontrol = be->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct snd_ctl_tlv header;
- struct snd_ctl_tlv __user *tlvd =
(struct snd_ctl_tlv __user *)binary_data;
Don't drop const.
- int ret;
- int err;
- int max_size = SOF_IPC_MSG_MAX_SIZE -
sizeof(const struct sof_ipc_ctrl_data);
- ret = pm_runtime_get_sync(sdev->dev);
- if (ret < 0) {
dev_err(sdev->dev, "error: bytes_ext put failed to resume %d\n",
ret);
return ret;
- }
- /* The beginning of bytes data contains a header from where
* the length (as bytes) is needed to know the correct copy
* length of data from tlvd->tlv.
*/
- if (copy_from_user(&header, tlvd, sizeof(const struct snd_ctl_tlv))) {
ret = -EFAULT;
goto out;
- }
- /* The maximum length that can be copied is limited by IPC max
* length and topology defined length for ext bytes control.
*/
- max_size = (be->max < max_size) ? be->max : max_size;
- if (header.length > max_size) {
dev_err(sdev->dev, "error: Bytes data size %d exceeds max %d.\n",
header.length, max_size);
ret = -EINVAL;
goto out;
Here user can pass a malicious data, and printing the error at each time would flood the kernel log. The error message can be dropped or make debug, or use ratelimited version. Ditto for the rest checks.
thanks,
Takashi
On 12/12/18 1:35 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:06 +0100, Pierre-Louis Bossart wrote:
+int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
....
- /* read back each channel */
- for (i = 0; i < channels; i++)
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
enum type needs to access ucontrol->value.enumerated.item[i]. This has a different size, hence using integer.value[] would be broken on BE archs.
oops. likely a copy/paste...
+int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
....
- /* update each channel */
- for (i = 0; i < channels; i++)
cdata->chanv[i].value = ucontrol->value.integer.value[i];
Ditto.
same here
+int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
....
- size = data->size + sizeof(*data);
- if (size > be->max) {
dev_err(sdev->dev, "error: DSP sent %zu bytes max is %d\n",
size, be->max);
ret = -EINVAL;
goto out;
- }
- /* copy back to kcontrol */
- memcpy(ucontrol->value.bytes.data, data, size);
I *hope* that the data size max was already examined not to exceed ucontrol data array size beforehand. But a sanity check to catch a buffer overflow here won't hurt. Ditto for *_put().
i think we do just that in the 'if' case just above the memcpy, but we'll double-check.
+int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
const unsigned int __user *binary_data,
unsigned int size)
+{
- struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
- struct snd_sof_control *scontrol = be->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct snd_ctl_tlv header;
- struct snd_ctl_tlv __user *tlvd =
(struct snd_ctl_tlv __user *)binary_data;
Don't drop const.
Ah, I added this cast to make a sparse warning go away, not sure why the const was removed.
I'll double-check again, thanks.
- int ret;
- int err;
- int max_size = SOF_IPC_MSG_MAX_SIZE -
sizeof(const struct sof_ipc_ctrl_data);
- ret = pm_runtime_get_sync(sdev->dev);
- if (ret < 0) {
dev_err(sdev->dev, "error: bytes_ext put failed to resume %d\n",
ret);
return ret;
- }
- /* The beginning of bytes data contains a header from where
* the length (as bytes) is needed to know the correct copy
* length of data from tlvd->tlv.
*/
- if (copy_from_user(&header, tlvd, sizeof(const struct snd_ctl_tlv))) {
ret = -EFAULT;
goto out;
- }
- /* The maximum length that can be copied is limited by IPC max
* length and topology defined length for ext bytes control.
*/
- max_size = (be->max < max_size) ? be->max : max_size;
- if (header.length > max_size) {
dev_err(sdev->dev, "error: Bytes data size %d exceeds max %d.\n",
header.length, max_size);
ret = -EINVAL;
goto out;
Here user can pass a malicious data, and printing the error at each time would flood the kernel log. The error message can be dropped or make debug, or use ratelimited version. Ditto for the rest checks.
Good point, we'll fix this.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add debugFS files that can be used to expose DSP memories and and peripherals to userspace to assist with firmware debugging.
TODO: The runtime_pm parts are not final and will be updated.
Signed-off-by: Pan Xiuli xiuli.pan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/debug.c | 176 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 sound/soc/sof/debug.c
diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c new file mode 100644 index 000000000000..67f6cf79b729 --- /dev/null +++ b/sound/soc/sof/debug.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// +// Generic debug routines used to export DSP MMIO and memories to userspace +// for firmware debugging. +// + +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include "sof-priv.h" + +static int sof_dfsentry_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry_io *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + int size; + u32 *buf; + loff_t pos = *ppos; + size_t ret; + + size = dfse->size; + + /* validate position & count */ + if (pos < 0) + return -EINVAL; + if (pos >= size || !count) + return 0; + if (count > size - pos) + count = size - pos; + + /* intermediate buffer size must be u32 multiple */ + size = (count + 3) & ~3; + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* copy from DSP MMIO */ + pm_runtime_get_noresume(sdev->dev); + + memcpy_fromio(buf, dfse->buf + pos, size); + + /* + * TODO: revisit to check if we need mark_last_busy, or if we + * should change to use xxx_put_sync[_suspend](). + */ + ret = pm_runtime_put_sync_autosuspend(sdev->dev); + if (ret < 0) + dev_warn(sdev->dev, "warn: debugFS failed to autosuspend %zd\n", + ret); + + /* copy to userspace */ + ret = copy_to_user(buffer, buf, count); + kfree(buf); + + /* update count & position if copy succeeded */ + if (ret == count) + return -EFAULT; + count -= ret; + *ppos = pos + count; + + return count; +} + +static const struct file_operations sof_dfs_fops = { + .open = sof_dfsentry_open, + .read = sof_dfsentry_read, + .llseek = default_llseek, +}; + +/* create FS entry for debug files that can expose DSP memories, registers */ +int snd_sof_debugfs_io_create_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name) +{ + struct snd_sof_dfsentry_io *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->buf = base; + dfse->size = size; + dfse->sdev = sdev; + + dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, + dfse, &sof_dfs_fops); + if (!dfse->dfsentry) { + dev_err(sdev->dev, "error: cannot create debugfs entry.\n"); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_debugfs_io_create_item); + +/* create FS entry for debug files to expose kernel memory */ +int snd_sof_debugfs_buf_create_item(struct snd_sof_dev *sdev, + void *base, size_t size, + const char *name) +{ + struct snd_sof_dfsentry_buf *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->buf = base; + dfse->size = size; + dfse->sdev = sdev; + + dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, + dfse, &sof_dfs_fops); + if (!dfse->dfsentry) { + dev_err(sdev->dev, "error: cannot create debugfs entry.\n"); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_debugfs_buf_create_item); + +int snd_sof_dbg_init(struct snd_sof_dev *sdev) +{ + const struct snd_sof_dsp_ops *ops = sdev->ops; + const struct snd_sof_debugfs_map *map; + int err = 0, i; + + /* use "sof" as top level debugFS dir */ + sdev->debugfs_root = debugfs_create_dir("sof", NULL); + if (IS_ERR_OR_NULL(sdev->debugfs_root)) { + dev_err(sdev->dev, "error: failed to create debugfs directory\n"); + return -EINVAL; + } + + /* create debugFS files for platform specific MMIO/DSP memories */ + for (i = 0; i < ops->debug_map_count; i++) { + map = &ops->debug_map[i]; + + err = snd_sof_debugfs_io_create_item(sdev, sdev->bar[map->bar] + + map->offset, map->size, + map->name); + if (err < 0) + dev_err(sdev->dev, "error: cannot create debugfs for %s\n", + map->name); + } + + return err; +} +EXPORT_SYMBOL(snd_sof_dbg_init); + +void snd_sof_free_debug(struct snd_sof_dev *sdev) +{ + debugfs_remove_recursive(sdev->debugfs_root); +} +EXPORT_SYMBOL(snd_sof_free_debug);
On Tue, Dec 11, 2018 at 03:23:07PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add debugFS files that can be used to expose DSP memories and and peripherals to userspace to assist with firmware debugging.
TODO: The runtime_pm parts are not final and will be updated.
- if (count > size - pos)
count = size - pos;
max() / min()
- /* intermediate buffer size must be u32 multiple */
- size = (count + 3) & ~3;
Hmm, don't we have something like round_up()?
- dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root,
dfse, &sof_dfs_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev, "error: cannot create debugfs entry.\n");
return -ENODEV;
- }
We shouldn't rely on debugfs files. Thus, error checking needs to be removed.
Something like
void () { struct dentry *d;
d = debugfs_...(); if (!d) dev_err(); }
- dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root,
dfse, &sof_dfs_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev, "error: cannot create debugfs entry.\n");
return -ENODEV;
- }
Ditto.
- sdev->debugfs_root = debugfs_create_dir("sof", NULL);
- if (IS_ERR_OR_NULL(sdev->debugfs_root)) {
dev_err(sdev->dev, "error: failed to create debugfs directory\n");
return -EINVAL;
- }
- /* create debugFS files for platform specific MMIO/DSP memories */
- for (i = 0; i < ops->debug_map_count; i++) {
map = &ops->debug_map[i];
err = snd_sof_debugfs_io_create_item(sdev, sdev->bar[map->bar] +
map->offset, map->size,
map->name);
if (err < 0)
dev_err(sdev->dev, "error: cannot create debugfs for %s\n",
map->name);
Ditto (error message dups above).
- }
- return err;
+} +EXPORT_SYMBOL(snd_sof_dbg_init);
- if (count > size - pos)
count = size - pos;
max() / min()
ok
- /* intermediate buffer size must be u32 multiple */
- size = (count + 3) & ~3;
Hmm, don't we have something like round_up()?
yes, round_up (count, 4) would work.
- dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root,
dfse, &sof_dfs_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev, "error: cannot create debugfs entry.\n");
return -ENODEV;
- }
We shouldn't rely on debugfs files. Thus, error checking needs to be removed.
Something like
void () { struct dentry *d;
d = debugfs_...(); if (!d) dev_err(); }
You lost me on this one. The main purpose of this return -ENODEV is to avoid adding more entries if one item failed. It's not different from what our estimeed colleagues did in skl-debug.c
- dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root,
dfse, &sof_dfs_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev, "error: cannot create debugfs entry.\n");
return -ENODEV;
- }
Ditto.
- sdev->debugfs_root = debugfs_create_dir("sof", NULL);
- if (IS_ERR_OR_NULL(sdev->debugfs_root)) {
dev_err(sdev->dev, "error: failed to create debugfs directory\n");
return -EINVAL;
- }
- /* create debugFS files for platform specific MMIO/DSP memories */
- for (i = 0; i < ops->debug_map_count; i++) {
map = &ops->debug_map[i];
err = snd_sof_debugfs_io_create_item(sdev, sdev->bar[map->bar] +
map->offset, map->size,
map->name);
if (err < 0)
dev_err(sdev->dev, "error: cannot create debugfs for %s\n",
map->name);
Ditto (error message dups above).
- }
- return err;
or maybe that's the issue, we shouldn't return an error on the init proper, but I see nothing wrong in stopping when things go south.
+} +EXPORT_SYMBOL(snd_sof_dbg_init);
On Tue, Dec 11, 2018 at 03:23:07PM -0600, Pierre-Louis Bossart wrote:
- /* copy from DSP MMIO */
- pm_runtime_get_noresume(sdev->dev);
Why are we doing a _get_noresume() here? It won't actually do anything to the device...
- memcpy_fromio(buf, dfse->buf + pos, size);
Extra space?
- /*
* TODO: revisit to check if we need mark_last_busy, or if we
* should change to use xxx_put_sync[_suspend]().
*/
- ret = pm_runtime_put_sync_autosuspend(sdev->dev);
- if (ret < 0)
dev_warn(sdev->dev, "warn: debugFS failed to autosuspend %zd\n",
ret);
It rather depends what you're doing... I'm definitely confused as to why you need a _sync operation - if you're doing autosuspend stuff presumably you don't care so much if the device gets powered down immediately, and I can't in general see why that'd be important.
- dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root,
dfse, &sof_dfs_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev, "error: cannot create debugfs entry.\n");
return -ENODEV;
- }
- return 0;
+} +EXPORT_SYMBOL(snd_sof_debugfs_io_create_item);
debugfs uses EXPORT_SYMBOL_GPL().
[combined feedback from Ranjani, Liam and me]
- /* copy from DSP MMIO */
- pm_runtime_get_noresume(sdev->dev);
Why are we doing a _get_noresume() here? It won't actually do anything to the device...
the reason we currently do a get_noresume() is so that we can still read the debugfs entries in case of DSP panic, where a regular resume would not succeed.
That said this part will be reworked based on feedback from Rafael whose suggestion was to get rid of the pm calls while reading debugfs. We can make sure that all the values are flushed out of the dsp before suspending which will make the need to wake it up redundant.
- memcpy_fromio(buf, dfse->buf + pos, size);
Extra space?
yep. we run checkpatch.pl --strict all the time and missed this somehow.
- /*
* TODO: revisit to check if we need mark_last_busy, or if we
* should change to use xxx_put_sync[_suspend]().
*/
- ret = pm_runtime_put_sync_autosuspend(sdev->dev);
- if (ret < 0)
dev_warn(sdev->dev, "warn: debugFS failed to autosuspend %zd\n",
ret);
It rather depends what you're doing... I'm definitely confused as to why you need a _sync operation - if you're doing autosuspend stuff presumably you don't care so much if the device gets powered down immediately, and I can't in general see why that'd be important.
yes, as stated above we'll rework all this
- dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root,
dfse, &sof_dfs_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev, "error: cannot create debugfs entry.\n");
return -ENODEV;
- }
- return 0;
+} +EXPORT_SYMBOL(snd_sof_debugfs_io_create_item);
debugfs uses EXPORT_SYMBOL_GPL().
We don't have a burning desire to keep this as EXPORT_SYMBOL since debugfs is really Linux specific, so will move all these statements as EXPORT_SYMBOL_GPL.
Thanks Mark for your time, much appreciated. We've already dealt with most of the feedback from Takashi/Andy/Daniel so should have a new version of these patches in 1-2 weeks.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Define an IPC ABI for all host <--> DSP communication. This ABI should be transport agnostic. i.e. it should work on MMIO and SPI/I2C style interfaces.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof/control.h | 125 ++++++ include/sound/sof/dai-intel.h | 175 ++++++++ include/sound/sof/dai.h | 75 ++++ include/sound/sof/header.h | 158 +++++++ include/sound/sof/info.h | 118 +++++ include/sound/sof/pm.h | 48 ++ include/sound/sof/stream.h | 149 +++++++ include/sound/sof/trace.h | 66 +++ sound/soc/sof/ipc.c | 813 ++++++++++++++++++++++++++++++++++ 9 files changed, 1727 insertions(+) create mode 100644 include/sound/sof/control.h create mode 100644 include/sound/sof/dai-intel.h create mode 100644 include/sound/sof/dai.h create mode 100644 include/sound/sof/header.h create mode 100644 include/sound/sof/info.h create mode 100644 include/sound/sof/pm.h create mode 100644 include/sound/sof/stream.h create mode 100644 include/sound/sof/trace.h create mode 100644 sound/soc/sof/ipc.c
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h new file mode 100644 index 000000000000..7d7d2eba7d76 --- /dev/null +++ b/include/sound/sof/control.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_CONTROL_H__ +#define __INCLUDE_SOUND_SOF_CONTROL_H__ + +#include <uapi/sound/sof/header.h> +#include <sound/sof/header.h> + +/* + * Component Mixers and Controls + */ + +/* channel positions - uses same values as ALSA */ +enum sof_ipc_chmap { + SOF_CHMAP_UNKNOWN = 0, + SOF_CHMAP_NA, /**< N/A, silent */ + SOF_CHMAP_MONO, /**< mono stream */ + SOF_CHMAP_FL, /**< front left */ + SOF_CHMAP_FR, /**< front right */ + SOF_CHMAP_RL, /**< rear left */ + SOF_CHMAP_RR, /**< rear right */ + SOF_CHMAP_FC, /**< front centre */ + SOF_CHMAP_LFE, /**< LFE */ + SOF_CHMAP_SL, /**< side left */ + SOF_CHMAP_SR, /**< side right */ + SOF_CHMAP_RC, /**< rear centre */ + SOF_CHMAP_FLC, /**< front left centre */ + SOF_CHMAP_FRC, /**< front right centre */ + SOF_CHMAP_RLC, /**< rear left centre */ + SOF_CHMAP_RRC, /**< rear right centre */ + SOF_CHMAP_FLW, /**< front left wide */ + SOF_CHMAP_FRW, /**< front right wide */ + SOF_CHMAP_FLH, /**< front left high */ + SOF_CHMAP_FCH, /**< front centre high */ + SOF_CHMAP_FRH, /**< front right high */ + SOF_CHMAP_TC, /**< top centre */ + SOF_CHMAP_TFL, /**< top front left */ + SOF_CHMAP_TFR, /**< top front right */ + SOF_CHMAP_TFC, /**< top front centre */ + SOF_CHMAP_TRL, /**< top rear left */ + SOF_CHMAP_TRR, /**< top rear right */ + SOF_CHMAP_TRC, /**< top rear centre */ + SOF_CHMAP_TFLC, /**< top front left centre */ + SOF_CHMAP_TFRC, /**< top front right centre */ + SOF_CHMAP_TSL, /**< top side left */ + SOF_CHMAP_TSR, /**< top side right */ + SOF_CHMAP_LLFE, /**< left LFE */ + SOF_CHMAP_RLFE, /**< right LFE */ + SOF_CHMAP_BC, /**< bottom centre */ + SOF_CHMAP_BLC, /**< bottom left centre */ + SOF_CHMAP_BRC, /**< bottom right centre */ + SOF_CHMAP_LAST = SOF_CHMAP_BRC, +}; + +/* control data type and direction */ +enum sof_ipc_ctrl_type { + /* per channel data - uses struct sof_ipc_ctrl_value_chan */ + SOF_CTRL_TYPE_VALUE_CHAN_GET = 0, + SOF_CTRL_TYPE_VALUE_CHAN_SET, + /* component data - uses struct sof_ipc_ctrl_value_comp */ + SOF_CTRL_TYPE_VALUE_COMP_GET, + SOF_CTRL_TYPE_VALUE_COMP_SET, + /* bespoke data - struct struct sof_abi_hdr */ + SOF_CTRL_TYPE_DATA_GET, + SOF_CTRL_TYPE_DATA_SET, +}; + +/* control command type */ +enum sof_ipc_ctrl_cmd { + SOF_CTRL_CMD_VOLUME = 0, /**< maps to ALSA volume style controls */ + SOF_CTRL_CMD_ENUM, /**< maps to ALSA enum style controls */ + SOF_CTRL_CMD_SWITCH, /**< maps to ALSA switch style controls */ + SOF_CTRL_CMD_BINARY, /**< maps to ALSA binary style controls */ +}; + +/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan { + uint32_t channel; /**< channel map - enum sof_ipc_chmap */ + uint32_t value; +} __packed; + +/* generic component mapped value data */ +struct sof_ipc_ctrl_value_comp { + uint32_t index; /**< component source/sink/control index in control */ + union { + uint32_t uvalue; + int32_t svalue; + }; +} __packed; + +/* generic control data */ +struct sof_ipc_ctrl_data { + struct sof_ipc_reply rhdr; + uint32_t comp_id; + + /* control access and data type */ + uint32_t type; /**< enum sof_ipc_ctrl_type */ + uint32_t cmd; /**< enum sof_ipc_ctrl_cmd */ + uint32_t index; /**< control index for comps > 1 control */ + + /* control data - can either be appended or DMAed from host */ + struct sof_ipc_host_buffer buffer; + uint32_t num_elems; /**< in array elems or bytes for data type */ + + /* reserved for future use */ + uint32_t reserved[8]; + + /* control data - add new types if needed */ + union { + /* channel values can be used by volume type controls */ + struct sof_ipc_ctrl_value_chan chanv[0]; + /* component values used by routing controls like mux, mixer */ + struct sof_ipc_ctrl_value_comp compv[0]; + /* data can be used by binary controls */ + struct sof_abi_hdr data[0]; + }; +} __packed; + +#endif diff --git a/include/sound/sof/dai-intel.h b/include/sound/sof/dai-intel.h new file mode 100644 index 000000000000..700e1ed23450 --- /dev/null +++ b/include/sound/sof/dai-intel.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_DAI_INTEL_H__ +#define __INCLUDE_SOUND_SOF_DAI_INTEL_H__ + +#include <sound/sof/header.h> + + /* ssc1: TINTE */ +#define SOF_DAI_INTEL_SSP_QUIRK_TINTE (1 << 0) + /* ssc1: PINTE */ +#define SOF_DAI_INTEL_SSP_QUIRK_PINTE (1 << 1) + /* ssc2: SMTATF */ +#define SOF_DAI_INTEL_SSP_QUIRK_SMTATF (1 << 2) + /* ssc2: MMRATF */ +#define SOF_DAI_INTEL_SSP_QUIRK_MMRATF (1 << 3) + /* ssc2: PSPSTWFDFD */ +#define SOF_DAI_INTEL_SSP_QUIRK_PSPSTWFDFD (1 << 4) + /* ssc2: PSPSRWFDFD */ +#define SOF_DAI_INTEL_SSP_QUIRK_PSPSRWFDFD (1 << 5) + /* here is the possibility to define others aux macros */ + +#define SOF_DAI_INTEL_SSP_FRAME_PULSE_WIDTH_MAX 38 +#define SOF_DAI_INTEL_SSP_SLOT_PADDING_MAX 31 + +/* SSP clocks control settings + * + * Macros for clks_control field in sof_ipc_dai_ssp_params struct. + */ + +/* mclk 0 disable */ +#define SOF_DAI_INTEL_SSP_MCLK_0_DISABLE BIT(0) +/* mclk 1 disable */ +#define SOF_DAI_INTEL_SSP_MCLK_1_DISABLE BIT(1) +/* mclk keep active */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_MCLK_KA BIT(2) +/* bclk keep active */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_KA BIT(3) +/* fs keep active */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_FS_KA BIT(4) +/* bclk idle */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_IDLE_HIGH BIT(5) + +/* SSP Configuration Request - SOF_IPC_DAI_SSP_CONFIG */ +struct sof_ipc_dai_ssp_params { + struct sof_ipc_hdr hdr; + uint16_t reserved1; + uint16_t mclk_id; + + uint32_t mclk_rate; /* mclk frequency in Hz */ + uint32_t fsync_rate; /* fsync frequency in Hz */ + uint32_t bclk_rate; /* bclk frequency in Hz */ + + /* TDM */ + uint32_t tdm_slots; + uint32_t rx_slots; + uint32_t tx_slots; + + /* data */ + uint32_t sample_valid_bits; + uint16_t tdm_slot_width; + uint16_t reserved2; /* alignment */ + + /* MCLK */ + uint32_t mclk_direction; + + uint16_t frame_pulse_width; + uint16_t tdm_per_slot_padding_flag; + uint32_t clks_control; + uint32_t quirks; +} __packed; + +/* HDA Configuration Request - SOF_IPC_DAI_HDA_CONFIG */ +struct sof_ipc_dai_hda_params { + struct sof_ipc_hdr hdr; + uint32_t link_dma_ch; +} __packed; + +/* DMIC Configuration Request - SOF_IPC_DAI_DMIC_CONFIG */ + +/* This struct is defined per 2ch PDM controller available in the platform. + * Normally it is sufficient to set the used microphone specific enables to 1 + * and keep other parameters as zero. The customizations are: + * + * 1. If a device mixes different microphones types with different polarity + * and/or the absolute polarity matters the PCM signal from a microphone + * can be inverted with the controls. + * + * 2. If the microphones in a stereo pair do not appear in captured stream + * in desired order due to board schematics choises they can be swapped with + * the clk_edge parameter. + * + * 3. If PDM bit errors are seen in capture (poor quality) the skew parameter + * that delays the sampling time of data by half cycles of DMIC source clock + * can be tried for improvement. However there is no guarantee for this to fix + * data integrity problems. + */ +struct sof_ipc_dai_dmic_pdm_ctrl { + struct sof_ipc_hdr hdr; + uint16_t id; /**< PDM controller ID */ + + uint16_t enable_mic_a; /**< Use A (left) channel mic (0 or 1)*/ + uint16_t enable_mic_b; /**< Use B (right) channel mic (0 or 1)*/ + + uint16_t polarity_mic_a; /**< Optionally invert mic A signal (0 or 1) */ + uint16_t polarity_mic_b; /**< Optionally invert mic B signal (0 or 1) */ + + uint16_t clk_edge; /**< Optionally swap data clock edge (0 or 1) */ + uint16_t skew; /**< Adjust PDM data sampling vs. clock (0..15) */ + + uint16_t reserved[3]; /**< Make sure the total size is 4 bytes aligned */ +} __packed; + +/* This struct contains the global settings for all 2ch PDM controllers. The + * version number used in configuration data is checked vs. version used by + * device driver src/drivers/dmic.c need to match. It is incremented from + * initial value 1 if updates done for the to driver would alter the operation + * of the microhone. + * + * Note: The microphone clock (pdmclk_min, pdmclk_max, duty_min, duty_max) + * parameters need to be set as defined in microphone data sheet. E.g. clock + * range 1.0 - 3.2 MHz is usually supported microphones. Some microphones are + * multi-mode capable and there may be denied mic clock frequencies between + * the modes. In such case set the clock range limits of the desired mode to + * avoid the driver to set clock to an illegal rate. + * + * The duty cycle could be set to 48-52% if not known. Generally these + * parameters can be altered within data sheet specified limits to match + * required audio application performance power. + * + * The microphone clock needs to be usually about 50-80 times the used audio + * sample rate. With highest sample rates above 48 kHz this can relaxed + * somewhat. + * + * The parameter wake_up_time describes how long time the microphone needs + * for the data line to produce valid output from mic clock start. The driver + * will mute the captured audio for the given time. The min_clock_on_time + * parameter is used to prevent too short clock bursts to happen. The driver + * will keep the clock active after capture stop if this time is not yet + * met. The unit for both is microseconds (us). Exceed of 100 ms will be + * treated as an error. + */ +struct sof_ipc_dai_dmic_params { + struct sof_ipc_hdr hdr; + uint32_t driver_ipc_version; /**< Version (1..N) */ + + uint32_t pdmclk_min; /**< Minimum microphone clock in Hz (100000..N) */ + uint32_t pdmclk_max; /**< Maximum microphone clock in Hz (min...N) */ + + uint32_t fifo_fs_a; /**< FIFO A sample rate in Hz (8000..96000) */ + uint32_t fifo_fs_b; /**< FIFO B sample rate in Hz (8000..96000) */ + uint16_t fifo_bits_a; /**< FIFO A word length (16 or 32) */ + uint16_t fifo_bits_b; /**< FIFO B word length (16 or 32) */ + + uint16_t duty_min; /**< Min. mic clock duty cycle in % (20..80) */ + uint16_t duty_max; /**< Max. mic clock duty cycle in % (min..80) */ + + uint32_t num_pdm_active; /**< Number of active pdm controllers */ + + uint32_t wake_up_time; /**< Time from clock start to data (us) */ + uint32_t min_clock_on_time; /**< Min. time that clk is kept on (us) */ + + /* reserved for future use */ + uint32_t reserved[6]; + + /**< variable number of pdm controller config */ + struct sof_ipc_dai_dmic_pdm_ctrl pdm[0]; +} __packed; + +#endif diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h new file mode 100644 index 000000000000..3b67c93ff101 --- /dev/null +++ b/include/sound/sof/dai.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_DAI_H__ +#define __INCLUDE_SOUND_SOF_DAI_H__ + +#include <sound/sof/header.h> +#include <sound/sof/dai-intel.h> + +/* + * DAI Configuration. + * + * Each different DAI type will have it's own structure and IPC cmd. + */ + +#define SOF_DAI_FMT_I2S 1 /**< I2S mode */ +#define SOF_DAI_FMT_RIGHT_J 2 /**< Right Justified mode */ +#define SOF_DAI_FMT_LEFT_J 3 /**< Left Justified mode */ +#define SOF_DAI_FMT_DSP_A 4 /**< L data MSB after FRM LRC */ +#define SOF_DAI_FMT_DSP_B 5 /**< L data MSB during FRM LRC */ +#define SOF_DAI_FMT_PDM 6 /**< Pulse density modulation */ + +#define SOF_DAI_FMT_CONT (1 << 4) /**< continuous clock */ +#define SOF_DAI_FMT_GATED (0 << 4) /**< clock is gated */ + +#define SOF_DAI_FMT_NB_NF (0 << 8) /**< normal bit clock + frame */ +#define SOF_DAI_FMT_NB_IF (2 << 8) /**< normal BCLK + inv FRM */ +#define SOF_DAI_FMT_IB_NF (3 << 8) /**< invert BCLK + nor FRM */ +#define SOF_DAI_FMT_IB_IF (4 << 8) /**< invert BCLK + FRM */ + +#define SOF_DAI_FMT_CBM_CFM (0 << 12) /**< codec clk & FRM master */ +#define SOF_DAI_FMT_CBS_CFM (2 << 12) /**< codec clk slave & FRM master */ +#define SOF_DAI_FMT_CBM_CFS (3 << 12) /**< codec clk master & frame slave */ +#define SOF_DAI_FMT_CBS_CFS (4 << 12) /**< codec clk & FRM slave */ + +#define SOF_DAI_FMT_FORMAT_MASK 0x000f +#define SOF_DAI_FMT_CLOCK_MASK 0x00f0 +#define SOF_DAI_FMT_INV_MASK 0x0f00 +#define SOF_DAI_FMT_MASTER_MASK 0xf000 + +/** \brief Types of DAI */ +enum sof_ipc_dai_type { + SOF_DAI_INTEL_NONE = 0, /**< None */ + SOF_DAI_INTEL_SSP, /**< Intel SSP */ + SOF_DAI_INTEL_DMIC, /**< Intel DMIC */ + SOF_DAI_INTEL_HDA, /**< Intel HD/A */ +}; + +/* general purpose DAI configuration */ +struct sof_ipc_dai_config { + struct sof_ipc_cmd_hdr hdr; + uint32_t type; /**< DAI type - enum sof_ipc_dai_type */ + uint32_t dai_index; /**< index of this type dai */ + + /* physical protocol and clocking */ + uint16_t format; /**< SOF_DAI_FMT_ */ + uint16_t reserved16; /**< alignment */ + + /* reserved for future use */ + uint32_t reserved[8]; + + /* HW specific data */ + union { + struct sof_ipc_dai_ssp_params ssp; + struct sof_ipc_dai_dmic_params dmic; + struct sof_ipc_dai_hda_params hda; + }; +} __packed; + +#endif diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h new file mode 100644 index 000000000000..ccb6a004b37b --- /dev/null +++ b/include/sound/sof/header.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_HEADER_H__ +#define __INCLUDE_SOUND_SOF_HEADER_H__ + +#include <uapi/sound/sof/abi.h> + +/** \addtogroup sof_uapi uAPI + * SOF uAPI specification. + * @{ + */ + +/* + * IPC messages have a prefixed 32 bit identifier made up as follows :- + * + * 0xGCCCNNNN where + * G is global cmd type (4 bits) + * C is command type (12 bits) + * I is the ID number (16 bits) - monotonic and overflows + * + * This is sent at the start of the IPM message in the mailbox. Messages should + * not be sent in the doorbell (special exceptions for firmware . + */ + +/* Global Message - Generic */ +#define SOF_GLB_TYPE_SHIFT 28 +#define SOF_GLB_TYPE_MASK (0xf << SOF_GLB_TYPE_SHIFT) +#define SOF_GLB_TYPE(x) ((x) << SOF_GLB_TYPE_SHIFT) + +/* Command Message - Generic */ +#define SOF_CMD_TYPE_SHIFT 16 +#define SOF_CMD_TYPE_MASK (0xfff << SOF_CMD_TYPE_SHIFT) +#define SOF_CMD_TYPE(x) ((x) << SOF_CMD_TYPE_SHIFT) + +/* Global Message Types */ +#define SOF_IPC_GLB_REPLY SOF_GLB_TYPE(0x1U) +#define SOF_IPC_GLB_COMPOUND SOF_GLB_TYPE(0x2U) +#define SOF_IPC_GLB_TPLG_MSG SOF_GLB_TYPE(0x3U) +#define SOF_IPC_GLB_PM_MSG SOF_GLB_TYPE(0x4U) +#define SOF_IPC_GLB_COMP_MSG SOF_GLB_TYPE(0x5U) +#define SOF_IPC_GLB_STREAM_MSG SOF_GLB_TYPE(0x6U) +#define SOF_IPC_FW_READY SOF_GLB_TYPE(0x7U) +#define SOF_IPC_GLB_DAI_MSG SOF_GLB_TYPE(0x8U) +#define SOF_IPC_GLB_TRACE_MSG SOF_GLB_TYPE(0x9U) + +/* + * DSP Command Message Types + */ + +/* topology */ +#define SOF_IPC_TPLG_COMP_NEW SOF_CMD_TYPE(0x001) +#define SOF_IPC_TPLG_COMP_FREE SOF_CMD_TYPE(0x002) +#define SOF_IPC_TPLG_COMP_CONNECT SOF_CMD_TYPE(0x003) +#define SOF_IPC_TPLG_PIPE_NEW SOF_CMD_TYPE(0x010) +#define SOF_IPC_TPLG_PIPE_FREE SOF_CMD_TYPE(0x011) +#define SOF_IPC_TPLG_PIPE_CONNECT SOF_CMD_TYPE(0x012) +#define SOF_IPC_TPLG_PIPE_COMPLETE SOF_CMD_TYPE(0x013) +#define SOF_IPC_TPLG_BUFFER_NEW SOF_CMD_TYPE(0x020) +#define SOF_IPC_TPLG_BUFFER_FREE SOF_CMD_TYPE(0x021) + +/* PM */ +#define SOF_IPC_PM_CTX_SAVE SOF_CMD_TYPE(0x001) +#define SOF_IPC_PM_CTX_RESTORE SOF_CMD_TYPE(0x002) +#define SOF_IPC_PM_CTX_SIZE SOF_CMD_TYPE(0x003) +#define SOF_IPC_PM_CLK_SET SOF_CMD_TYPE(0x004) +#define SOF_IPC_PM_CLK_GET SOF_CMD_TYPE(0x005) +#define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006) +#define SOF_IPC_PM_CORE_ENABLE SOF_CMD_TYPE(0x007) + +/* component runtime config - multiple different types */ +#define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001) +#define SOF_IPC_COMP_GET_VALUE SOF_CMD_TYPE(0x002) +#define SOF_IPC_COMP_SET_DATA SOF_CMD_TYPE(0x003) +#define SOF_IPC_COMP_GET_DATA SOF_CMD_TYPE(0x004) + +/* DAI messages */ +#define SOF_IPC_DAI_CONFIG SOF_CMD_TYPE(0x001) +#define SOF_IPC_DAI_LOOPBACK SOF_CMD_TYPE(0x002) + +/* stream */ +#define SOF_IPC_STREAM_PCM_PARAMS SOF_CMD_TYPE(0x001) +#define SOF_IPC_STREAM_PCM_PARAMS_REPLY SOF_CMD_TYPE(0x002) +#define SOF_IPC_STREAM_PCM_FREE SOF_CMD_TYPE(0x003) +#define SOF_IPC_STREAM_TRIG_START SOF_CMD_TYPE(0x004) +#define SOF_IPC_STREAM_TRIG_STOP SOF_CMD_TYPE(0x005) +#define SOF_IPC_STREAM_TRIG_PAUSE SOF_CMD_TYPE(0x006) +#define SOF_IPC_STREAM_TRIG_RELEASE SOF_CMD_TYPE(0x007) +#define SOF_IPC_STREAM_TRIG_DRAIN SOF_CMD_TYPE(0x008) +#define SOF_IPC_STREAM_TRIG_XRUN SOF_CMD_TYPE(0x009) +#define SOF_IPC_STREAM_POSITION SOF_CMD_TYPE(0x00a) +#define SOF_IPC_STREAM_VORBIS_PARAMS SOF_CMD_TYPE(0x010) +#define SOF_IPC_STREAM_VORBIS_FREE SOF_CMD_TYPE(0x011) + +/* trace and debug */ +#define SOF_IPC_TRACE_DMA_PARAMS SOF_CMD_TYPE(0x001) +#define SOF_IPC_TRACE_DMA_POSITION SOF_CMD_TYPE(0x002) + +/* Get message component id */ +#define SOF_IPC_MESSAGE_ID(x) ((x) & 0xffff) + +/* maximum message size for mailbox Tx/Rx */ +#define SOF_IPC_MSG_MAX_SIZE 384 + +/* + * Structure Header - Header for all IPC structures except command structs. + * The size can be greater than the structure size and that means there is + * extended bespoke data beyond the end of the structure including variable + * arrays. + */ + +struct sof_ipc_hdr { + uint32_t size; /**< size of structure */ +} __packed; + +/* + * Command Header - Header for all IPC commands. Identifies IPC message. + * The size can be greater than the structure size and that means there is + * extended bespoke data beyond the end of the structure including variable + * arrays. + */ + +struct sof_ipc_cmd_hdr { + uint32_t size; /**< size of structure */ + uint32_t cmd; /**< SOF_IPC_GLB_ + cmd */ +} __packed; + +/* + * Generic reply message. Some commands override this with their own reply + * types that must include this at start. + */ +struct sof_ipc_reply { + struct sof_ipc_cmd_hdr hdr; + int32_t error; /**< negative error numbers */ +} __packed; + +/* + * Compound commands - SOF_IPC_GLB_COMPOUND. + * + * Compound commands are sent to the DSP as a single IPC operation. The + * commands are split into blocks and each block has a header. This header + * identifies the command type and the number of commands before the next + * header. + */ + +struct sof_ipc_compound_hdr { + struct sof_ipc_cmd_hdr hdr; + uint32_t count; /**< count of 0 means end of compound sequence */ +} __packed; + +/** @}*/ + +#endif diff --git a/include/sound/sof/info.h b/include/sound/sof/info.h new file mode 100644 index 000000000000..21dae04d8183 --- /dev/null +++ b/include/sound/sof/info.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_INFO_H__ +#define __INCLUDE_SOUND_SOF_INFO_H__ + +#include <sound/sof/header.h> +#include <sound/sof/stream.h> + +/* + * Firmware boot and version + */ + +#define SOF_IPC_MAX_ELEMS 16 + +/* extended data types that can be appended onto end of sof_ipc_fw_ready */ +enum sof_ipc_ext_data { + SOF_IPC_EXT_DMA_BUFFER = 0, + SOF_IPC_EXT_WINDOW, +}; + +/* FW version - SOF_IPC_GLB_VERSION */ +struct sof_ipc_fw_version { + struct sof_ipc_hdr hdr; + uint16_t major; + uint16_t minor; + uint16_t micro; + uint16_t build; + uint8_t date[12]; + uint8_t time[10]; + uint8_t tag[6]; + uint32_t abi_version; + + /* reserved for future use */ + uint32_t reserved[4]; +} __packed; + +/* FW ready Message - sent by firmware when boot has completed */ +struct sof_ipc_fw_ready { + struct sof_ipc_cmd_hdr hdr; + uint32_t dspbox_offset; /* dsp initiated IPC mailbox */ + uint32_t hostbox_offset; /* host initiated IPC mailbox */ + uint32_t dspbox_size; + uint32_t hostbox_size; + struct sof_ipc_fw_version version; + + /* Miscellaneous debug flags showing build/debug features enabled */ + union { + uint64_t reserved; + struct { + uint64_t build:1; + uint64_t locks:1; + uint64_t locks_verbose:1; + uint64_t gdb:1; + } bits; + } debug; + + /* reserved for future use */ + uint32_t reserved[4]; +} __packed; + +/* + * Extended Firmware data. All optional, depends on platform/arch. + */ +enum sof_ipc_region { + SOF_IPC_REGION_DOWNBOX = 0, + SOF_IPC_REGION_UPBOX, + SOF_IPC_REGION_TRACE, + SOF_IPC_REGION_DEBUG, + SOF_IPC_REGION_STREAM, + SOF_IPC_REGION_REGS, + SOF_IPC_REGION_EXCEPTION, +}; + +struct sof_ipc_ext_data_hdr { + struct sof_ipc_cmd_hdr hdr; + uint32_t type; /**< SOF_IPC_EXT_ */ +} __packed; + +struct sof_ipc_dma_buffer_elem { + struct sof_ipc_hdr hdr; + uint32_t type; /**< SOF_IPC_REGION_ */ + uint32_t id; /**< platform specific - used to map to host memory */ + struct sof_ipc_host_buffer buffer; +} __packed; + +/* extended data DMA buffers for IPC, trace and debug */ +struct sof_ipc_dma_buffer_data { + struct sof_ipc_ext_data_hdr ext_hdr; + uint32_t num_buffers; + + /* host files in buffer[n].buffer */ + struct sof_ipc_dma_buffer_elem buffer[]; +} __packed; + +struct sof_ipc_window_elem { + struct sof_ipc_hdr hdr; + uint32_t type; /**< SOF_IPC_REGION_ */ + uint32_t id; /**< platform specific - used to map to host memory */ + uint32_t flags; /**< R, W, RW, etc - to define */ + uint32_t size; /**< size of region in bytes */ + /* offset in window region as windows can be partitioned */ + uint32_t offset; +} __packed; + +/* extended data memory windows for IPC, trace and debug */ +struct sof_ipc_window { + struct sof_ipc_ext_data_hdr ext_hdr; + uint32_t num_windows; + struct sof_ipc_window_elem window[]; +} __packed; + +#endif diff --git a/include/sound/sof/pm.h b/include/sound/sof/pm.h new file mode 100644 index 000000000000..8ae3ad45bdf7 --- /dev/null +++ b/include/sound/sof/pm.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_PM_H__ +#define __INCLUDE_SOUND_SOF_PM_H__ + +#include <sound/sof/header.h> + +/* + * PM + */ + +/* PM context element */ +struct sof_ipc_pm_ctx_elem { + struct sof_ipc_hdr hdr; + uint32_t type; + uint32_t size; + uint64_t addr; +} __packed; + +/* + * PM context - SOF_IPC_PM_CTX_SAVE, SOF_IPC_PM_CTX_RESTORE, + * SOF_IPC_PM_CTX_SIZE + */ +struct sof_ipc_pm_ctx { + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_host_buffer buffer; + uint32_t num_elems; + uint32_t size; + + /* reserved for future use */ + uint32_t reserved[8]; + + struct sof_ipc_pm_ctx_elem elems[]; +} __packed; + +/* enable or disable cores - SOF_IPC_PM_CORE_ENABLE */ +struct sof_ipc_pm_core_config { + struct sof_ipc_cmd_hdr hdr; + uint32_t enable_mask; +} __packed; + +#endif diff --git a/include/sound/sof/stream.h b/include/sound/sof/stream.h new file mode 100644 index 000000000000..0b436649f1e4 --- /dev/null +++ b/include/sound/sof/stream.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_STREAM_H__ +#define __INCLUDE_SOUND_SOF_STREAM_H__ + +#include <sound/sof/header.h> + +/* + * Stream configuration. + */ + +#define SOF_IPC_MAX_CHANNELS 8 + +/* common sample rates for use in masks */ +#define SOF_RATE_8000 (1 << 0) /**< 8000Hz */ +#define SOF_RATE_11025 (1 << 1) /**< 11025Hz */ +#define SOF_RATE_12000 (1 << 2) /**< 12000Hz */ +#define SOF_RATE_16000 (1 << 3) /**< 16000Hz */ +#define SOF_RATE_22050 (1 << 4) /**< 22050Hz */ +#define SOF_RATE_24000 (1 << 5) /**< 24000Hz */ +#define SOF_RATE_32000 (1 << 6) /**< 32000Hz */ +#define SOF_RATE_44100 (1 << 7) /**< 44100Hz */ +#define SOF_RATE_48000 (1 << 8) /**< 48000Hz */ +#define SOF_RATE_64000 (1 << 9) /**< 64000Hz */ +#define SOF_RATE_88200 (1 << 10) /**< 88200Hz */ +#define SOF_RATE_96000 (1 << 11) /**< 96000Hz */ +#define SOF_RATE_176400 (1 << 12) /**< 176400Hz */ +#define SOF_RATE_192000 (1 << 13) /**< 192000Hz */ + +/* continuous and non-standard rates for flexibility */ +#define SOF_RATE_CONTINUOUS (1 << 30) /**< range */ +#define SOF_RATE_KNOT (1 << 31) /**< non-continuous */ + +/* generic PCM flags for runtime settings */ +#define SOF_PCM_FLAG_XRUN_STOP (1 << 0) /**< Stop on any XRUN */ + +/* stream PCM frame format */ +enum sof_ipc_frame { + SOF_IPC_FRAME_S16_LE = 0, + SOF_IPC_FRAME_S24_4LE, + SOF_IPC_FRAME_S32_LE, + SOF_IPC_FRAME_FLOAT, + /* other formats here */ +}; + +/* stream buffer format */ +enum sof_ipc_buffer_format { + SOF_IPC_BUFFER_INTERLEAVED, + SOF_IPC_BUFFER_NONINTERLEAVED, + /* other formats here */ +}; + +/* stream direction */ +enum sof_ipc_stream_direction { + SOF_IPC_STREAM_PLAYBACK = 0, + SOF_IPC_STREAM_CAPTURE, +}; + +/* stream ring info */ +struct sof_ipc_host_buffer { + struct sof_ipc_hdr hdr; + uint32_t phy_addr; + uint32_t pages; + uint32_t size; + uint32_t offset; + uint32_t reserved[2]; +} __packed; + +struct sof_ipc_stream_params { + struct sof_ipc_hdr hdr; + struct sof_ipc_host_buffer buffer; + uint32_t direction; /**< enum sof_ipc_stream_direction */ + uint32_t frame_fmt; /**< enum sof_ipc_frame */ + uint32_t buffer_fmt; /**< enum sof_ipc_buffer_format */ + uint32_t rate; + uint16_t stream_tag; + uint16_t channels; + uint16_t sample_valid_bytes; + uint16_t sample_container_bytes; + + /* for notifying host period has completed - 0 means no period IRQ */ + uint32_t host_period_bytes; + + uint32_t reserved[2]; + uint16_t chmap[SOF_IPC_MAX_CHANNELS]; /**< channel map - SOF_CHMAP_ */ +} __packed; + +/* PCM params info - SOF_IPC_STREAM_PCM_PARAMS */ +struct sof_ipc_pcm_params { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; + uint32_t flags; /**< generic PCM flags - SOF_PCM_FLAG_ */ + uint32_t reserved[2]; + struct sof_ipc_stream_params params; +} __packed; + +/* PCM params info reply - SOF_IPC_STREAM_PCM_PARAMS_REPLY */ +struct sof_ipc_pcm_params_reply { + struct sof_ipc_reply rhdr; + uint32_t comp_id; + uint32_t posn_offset; +} __packed; + +/* free stream - SOF_IPC_STREAM_PCM_PARAMS */ +struct sof_ipc_stream { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; +} __packed; + +/* flags indicating which time stamps are in sync with each other */ +#define SOF_TIME_HOST_SYNC (1 << 0) +#define SOF_TIME_DAI_SYNC (1 << 1) +#define SOF_TIME_WALL_SYNC (1 << 2) +#define SOF_TIME_STAMP_SYNC (1 << 3) + +/* flags indicating which time stamps are valid */ +#define SOF_TIME_HOST_VALID (1 << 8) +#define SOF_TIME_DAI_VALID (1 << 9) +#define SOF_TIME_WALL_VALID (1 << 10) +#define SOF_TIME_STAMP_VALID (1 << 11) + +/* flags indicating time stamps are 64bit else 3use low 32bit */ +#define SOF_TIME_HOST_64 (1 << 16) +#define SOF_TIME_DAI_64 (1 << 17) +#define SOF_TIME_WALL_64 (1 << 18) +#define SOF_TIME_STAMP_64 (1 << 19) + +struct sof_ipc_stream_posn { + struct sof_ipc_reply rhdr; + uint32_t comp_id; /**< host component ID */ + uint32_t flags; /**< SOF_TIME_ */ + uint32_t wallclock_hz; /**< frequency of wallclock in Hz */ + uint32_t timestamp_ns; /**< resolution of timestamp in ns */ + uint64_t host_posn; /**< host DMA position in bytes */ + uint64_t dai_posn; /**< DAI DMA position in bytes */ + uint64_t comp_posn; /**< comp position in bytes */ + uint64_t wallclock; /**< audio wall clock */ + uint64_t timestamp; /**< system time stamp */ + uint32_t xrun_comp_id; /**< comp ID of XRUN component */ + int32_t xrun_size; /**< XRUN size in bytes */ +} __packed; + +#endif diff --git a/include/sound/sof/trace.h b/include/sound/sof/trace.h new file mode 100644 index 000000000000..49f0f310d902 --- /dev/null +++ b/include/sound/sof/trace.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_TRACE_H__ +#define __INCLUDE_SOUND_SOF_TRACE_H__ + +#include <sound/sof/header.h> +#include <sound/sof/stream.h> + +/* + * DMA for Trace + */ + +#define SOF_TRACE_FILENAME_SIZE 32 + +/* DMA for Trace params info - SOF_IPC_DEBUG_DMA_PARAMS */ +struct sof_ipc_dma_trace_params { + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_host_buffer buffer; + uint32_t stream_tag; +} __packed; + +/* DMA for Trace params info - SOF_IPC_DEBUG_DMA_PARAMS */ +struct sof_ipc_dma_trace_posn { + struct sof_ipc_reply rhdr; + uint32_t host_offset; /* Offset of DMA host buffer */ + uint32_t overflow; /* overflow bytes if any */ + uint32_t messages; /* total trace messages */ +} __packed; + +/* + * Commom debug + */ + +/* + * SOF panic codes + */ +#define SOF_IPC_PANIC_MAGIC 0x0dead000 +#define SOF_IPC_PANIC_MAGIC_MASK 0x0ffff000 +#define SOF_IPC_PANIC_CODE_MASK 0x00000fff +#define SOF_IPC_PANIC_MEM (SOF_IPC_PANIC_MAGIC | 0x0) +#define SOF_IPC_PANIC_WORK (SOF_IPC_PANIC_MAGIC | 0x1) +#define SOF_IPC_PANIC_IPC (SOF_IPC_PANIC_MAGIC | 0x2) +#define SOF_IPC_PANIC_ARCH (SOF_IPC_PANIC_MAGIC | 0x3) +#define SOF_IPC_PANIC_PLATFORM (SOF_IPC_PANIC_MAGIC | 0x4) +#define SOF_IPC_PANIC_TASK (SOF_IPC_PANIC_MAGIC | 0x5) +#define SOF_IPC_PANIC_EXCEPTION (SOF_IPC_PANIC_MAGIC | 0x6) +#define SOF_IPC_PANIC_DEADLOCK (SOF_IPC_PANIC_MAGIC | 0x7) +#define SOF_IPC_PANIC_STACK (SOF_IPC_PANIC_MAGIC | 0x8) +#define SOF_IPC_PANIC_IDLE (SOF_IPC_PANIC_MAGIC | 0x9) +#define SOF_IPC_PANIC_WFI (SOF_IPC_PANIC_MAGIC | 0xa) + +/* panic info include filename and line number */ +struct sof_ipc_panic_info { + struct sof_ipc_hdr hdr; + uint32_t code; /* SOF_IPC_PANIC_ */ + char filename[SOF_TRACE_FILENAME_SIZE]; + uint32_t linenum; +} __packed; + +#endif diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c new file mode 100644 index 000000000000..918cd7b732dd --- /dev/null +++ b/sound/soc/sof/ipc.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// +// Generic IPC layer that can work over MMIO and SPI/I2C. PHY layer provided +// by platform driver code. +// + +#include "sof-priv.h" +#include "ops.h" + +/* + * IPC message default size and timeout (msecs). + * TODO: allow platforms to set size and timeout. + */ +#define IPC_TIMEOUT_MSECS 300 +#define IPC_EMPTY_LIST_SIZE 8 + +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id); +static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); + +/* + * IPC message Tx/Rx message handling. + */ + +/* SOF generic IPC data */ +struct snd_sof_ipc { + struct snd_sof_dev *sdev; + + /* TX message work and status */ + wait_queue_head_t wait_txq; + struct work_struct tx_kwork; + u32 msg_pending; + + /* Rx Message work and status */ + struct work_struct rx_kwork; + + /* lists */ + struct list_head tx_list; + struct list_head reply_list; + struct list_head empty_list; +}; + +/* locks held by caller */ +static struct snd_sof_ipc_msg *msg_get_empty(struct snd_sof_ipc *ipc) +{ + struct snd_sof_ipc_msg *msg = NULL; + + /* get first empty message in the list */ + if (!list_empty(&ipc->empty_list)) { + msg = list_first_entry(&ipc->empty_list, struct snd_sof_ipc_msg, + list); + list_del(&msg->list); + } + + return msg; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) +{ + u8 *str; + u32 glb; + u32 type; + + glb = cmd & SOF_GLB_TYPE_MASK; + type = cmd & SOF_CMD_TYPE_MASK; + + switch (glb) { + case SOF_IPC_GLB_REPLY: + str = "GLB_REPLY"; break; + case SOF_IPC_GLB_COMPOUND: + str = "GLB_COMPOUND"; break; + case SOF_IPC_GLB_TPLG_MSG: + switch (type) { + case SOF_IPC_TPLG_COMP_NEW: + str = "GLB_TPLG_MSG: COMP_NEW"; break; + case SOF_IPC_TPLG_COMP_FREE: + str = "GLB_TPLG_MSG: COMP_FREE"; break; + case SOF_IPC_TPLG_COMP_CONNECT: + str = "GLB_TPLG_MSG: COMP_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_NEW: + str = "GLB_TPLG_MSG: PIPE_NEW"; break; + case SOF_IPC_TPLG_PIPE_FREE: + str = "GLB_TPLG_MSG: PIPE_FREE"; break; + case SOF_IPC_TPLG_PIPE_CONNECT: + str = "GLB_TPLG_MSG: PIPE_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_COMPLETE: + str = "GLB_TPLG_MSG: PIPE_COMPLETE"; break; + case SOF_IPC_TPLG_BUFFER_NEW: + str = "GLB_TPLG_MSG: BUFFER_NEW"; break; + case SOF_IPC_TPLG_BUFFER_FREE: + str = "GLB_TPLG_MSG: BUFFER_FREE"; break; + default: + str = "GLB_TPLG_MSG: unknown type"; break; + } + break; + case SOF_IPC_GLB_PM_MSG: + switch (type) { + case SOF_IPC_PM_CTX_SAVE: + str = "GLB_PM_MSG: CTX_SAVE"; break; + case SOF_IPC_PM_CTX_RESTORE: + str = "GLB_PM_MSG: CTX_RESTORE"; break; + case SOF_IPC_PM_CTX_SIZE: + str = "GLB_PM_MSG: CTX_SIZE"; break; + case SOF_IPC_PM_CLK_SET: + str = "GLB_PM_MSG: CLK_SET"; break; + case SOF_IPC_PM_CLK_GET: + str = "GLB_PM_MSG: CLK_GET"; break; + case SOF_IPC_PM_CLK_REQ: + str = "GLB_PM_MSG: CLK_REQ"; break; + case SOF_IPC_PM_CORE_ENABLE: + str = "GLB_PM_MSG: CORE_ENABLE"; break; + default: + str = "GLB_PM_MSG: unknown type"; break; + } + break; + case SOF_IPC_GLB_COMP_MSG: + switch (type) { + case SOF_IPC_COMP_SET_VALUE: + str = "GLB_COMP_MSG: SET_VALUE"; break; + case SOF_IPC_COMP_GET_VALUE: + str = "GLB_COMP_MSG: GET_VALUE"; break; + case SOF_IPC_COMP_SET_DATA: + str = "GLB_COMP_MSG: SET_DATA"; break; + case SOF_IPC_COMP_GET_DATA: + str = "GLB_COMP_MSG: GET_DATA"; break; + default: + str = "GLB_COMP_MSG: unknown type"; break; + } + break; + case SOF_IPC_GLB_STREAM_MSG: + switch (type) { + case SOF_IPC_STREAM_PCM_PARAMS: + str = "GLB_STREAM_MSG: PCM_PARAMS"; break; + case SOF_IPC_STREAM_PCM_PARAMS_REPLY: + str = "GLB_STREAM_MSG: PCM_REPLY"; break; + case SOF_IPC_STREAM_PCM_FREE: + str = "GLB_STREAM_MSG: PCM_FREE"; break; + case SOF_IPC_STREAM_TRIG_START: + str = "GLB_STREAM_MSG: TRIG_START"; break; + case SOF_IPC_STREAM_TRIG_STOP: + str = "GLB_STREAM_MSG: TRIG_STOP"; break; + case SOF_IPC_STREAM_TRIG_PAUSE: + str = "GLB_STREAM_MSG: TRIG_PAUSE"; break; + case SOF_IPC_STREAM_TRIG_RELEASE: + str = "GLB_STREAM_MSG: TRIG_RELEASE"; break; + case SOF_IPC_STREAM_TRIG_DRAIN: + str = "GLB_STREAM_MSG: TRIG_DRAIN"; break; + case SOF_IPC_STREAM_TRIG_XRUN: + str = "GLB_STREAM_MSG: TRIG_XRUN"; break; + case SOF_IPC_STREAM_POSITION: + str = "GLB_STREAM_MSG: POSITION"; break; + case SOF_IPC_STREAM_VORBIS_PARAMS: + str = "GLB_STREAM_MSG: VORBIS_PARAMS"; break; + case SOF_IPC_STREAM_VORBIS_FREE: + str = "GLB_STREAM_MSG: VORBIS_FREE"; break; + default: + str = "GLB_STREAM_MSG: unknown type"; break; + } + break; + case SOF_IPC_FW_READY: + str = "FW_READY"; break; + case SOF_IPC_GLB_DAI_MSG: + switch (type) { + case SOF_IPC_DAI_CONFIG: + str = "GLB_DAI_MSG: CONFIG"; break; + case SOF_IPC_DAI_LOOPBACK: + str = "GLB_DAI_MSG: LOOPBACK"; break; + default: + str = "GLB_DAI_MSG: unknown type"; break; + } + break; + case SOF_IPC_GLB_TRACE_MSG: + str = "GLB_TRACE_MSG"; break; + default: + str = "unknown GLB command"; break; + } + + dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str); +} +#else +static inline void ipc_log_header(struct device *dev, u8 *text, u32 cmd) +{ + dev_dbg(dev, "%s: 0x%x\n", text, cmd); +} +#endif + +/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data; + unsigned long flags; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + + spin_lock_irqsave(&sdev->ipc_lock, flags); + + if (ret == 0) { + dev_err(sdev->dev, "error: ipc timed out for 0x%x size 0x%x\n", + hdr->cmd, hdr->size); + snd_sof_dsp_dbg_dump(ipc->sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_trace_notify_for_error(ipc->sdev); + ret = -ETIMEDOUT; + } else { + /* copy the data returned from DSP */ + ret = snd_sof_dsp_get_reply(sdev, msg); + if (msg->reply_size) + memcpy(reply_data, msg->reply_data, msg->reply_size); + if (ret < 0) + dev_err(sdev->dev, "error: ipc error for 0x%x size 0x%zx\n", + hdr->cmd, msg->reply_size); + else + ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd); + } + + /* return message body to empty list */ + list_move(&msg->list, &ipc->empty_list); + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + + snd_sof_dsp_cmd_done(sdev, SOF_IPC_DSP_REPLY); + + /* continue to schedule any remaining messages... */ + snd_sof_ipc_msgs_tx(sdev); + + return ret; +} + +/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags; + + spin_lock_irqsave(&sdev->ipc_lock, flags); + + /* get an empty message */ + msg = msg_get_empty(ipc); + if (!msg) { + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + return -EBUSY; + } + + msg->header = header; + msg->msg_size = msg_bytes; + msg->reply_size = reply_bytes; + msg->ipc_complete = false; + + /* attach any data */ + if (msg_bytes) + memcpy(msg->msg_data, msg_data, msg_bytes); + + /* add message to transmit list */ + list_add_tail(&msg->list, &ipc->tx_list); + + /* schedule the message if not busy */ + if (snd_sof_dsp_is_ready(sdev)) + schedule_work(&ipc->tx_kwork); + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + + /* now wait for completion */ + return tx_wait_done(ipc, msg, reply_data); +} +EXPORT_SYMBOL(sof_ipc_tx_message); + +/* send next IPC message in list */ +static void ipc_tx_next_msg(struct work_struct *work) +{ + struct snd_sof_ipc *ipc = + container_of(work, struct snd_sof_ipc, tx_kwork); + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags; + + spin_lock_irqsave(&sdev->ipc_lock, flags); + + /* send message if HW read and message in TX list */ + if (list_empty(&ipc->tx_list) || !snd_sof_dsp_is_ready(sdev)) + goto out; + + /* send first message in TX list */ + msg = list_first_entry(&ipc->tx_list, struct snd_sof_ipc_msg, list); + list_move(&msg->list, &ipc->reply_list); + snd_sof_dsp_send_msg(sdev, msg); + + ipc_log_header(sdev->dev, "ipc tx", msg->header); +out: + spin_unlock_irqrestore(&sdev->ipc_lock, flags); +} + +/* find original TX message from DSP reply */ +static struct snd_sof_ipc_msg *sof_ipc_reply_find_msg(struct snd_sof_ipc *ipc, + u32 header) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + + header = SOF_IPC_MESSAGE_ID(header); + + if (list_empty(&ipc->reply_list)) + goto err; + + list_for_each_entry(msg, &ipc->reply_list, list) { + if (SOF_IPC_MESSAGE_ID(msg->header) == header) + return msg; + } + +err: + dev_err(sdev->dev, "error: rx list empty but received 0x%x\n", + header); + return NULL; +} + +/* mark IPC message as complete - locks held by caller */ +static void sof_ipc_tx_msg_reply_complete(struct snd_sof_ipc *ipc, + struct snd_sof_ipc_msg *msg) +{ + msg->ipc_complete = true; + wake_up(&msg->waitq); +} + +/* drop all IPC messages in preparation for DSP stall/reset */ +void sof_ipc_drop_all(struct snd_sof_ipc *ipc) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg, *tmp; + unsigned long flags; + + /* drop all TX and Rx messages before we stall + reset DSP */ + spin_lock_irqsave(&sdev->ipc_lock, flags); + + list_for_each_entry_safe(msg, tmp, &ipc->tx_list, list) { + list_move(&msg->list, &ipc->empty_list); + dev_err(sdev->dev, "error: dropped msg %d\n", msg->header); + } + + list_for_each_entry_safe(msg, tmp, &ipc->reply_list, list) { + list_move(&msg->list, &ipc->empty_list); + dev_err(sdev->dev, "error: dropped reply %d\n", msg->header); + } + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); +} +EXPORT_SYMBOL(sof_ipc_drop_all); + +/* handle reply message from DSP */ +int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_sof_ipc_msg *msg; + + msg = sof_ipc_reply_find_msg(sdev->ipc, msg_id); + if (!msg) { + dev_err(sdev->dev, "error: can't find message header 0x%x", + msg_id); + return -EINVAL; + } + + /* wake up and return the error if we have waiters on this message ? */ + sof_ipc_tx_msg_reply_complete(sdev->ipc, msg); + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_reply); + +/* DSP firmware has sent host a message */ +static void ipc_msgs_rx(struct work_struct *work) +{ + struct snd_sof_ipc *ipc = + container_of(work, struct snd_sof_ipc, rx_kwork); + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr hdr; + u32 cmd, type; + int err = -EINVAL; + + /* read back header */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &hdr, sizeof(hdr)); + 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) { + case SOF_IPC_GLB_REPLY: + dev_err(sdev->dev, "error: ipc reply unknown\n"); + break; + case SOF_IPC_FW_READY: + /* check for FW boot completion */ + if (!sdev->boot_complete) { + if (sdev->ops->fw_ready) + err = sdev->ops->fw_ready(sdev, cmd); + if (err < 0) { + dev_err(sdev->dev, "error: DSP firmware boot timeout %d\n", + err); + } else { + /* firmware boot completed OK */ + sdev->boot_complete = true; + dev_dbg(sdev->dev, "booting DSP firmware completed\n"); + wake_up(&sdev->boot_wait); + } + } + break; + case SOF_IPC_GLB_COMPOUND: + case SOF_IPC_GLB_TPLG_MSG: + case SOF_IPC_GLB_PM_MSG: + case SOF_IPC_GLB_COMP_MSG: + break; + case SOF_IPC_GLB_STREAM_MSG: + /* need to pass msg id into the function */ + ipc_stream_message(sdev, hdr.cmd); + break; + case SOF_IPC_GLB_TRACE_MSG: + ipc_trace_message(sdev, type); + break; + default: + dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); + break; + } + + ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); + + /* tell DSP we are done */ + snd_sof_dsp_cmd_done(sdev, SOF_IPC_HOST_REPLY); +} + +/* schedule work to transmit any IPC in queue */ +void snd_sof_ipc_msgs_tx(struct snd_sof_dev *sdev) +{ + schedule_work(&sdev->ipc->tx_kwork); +} +EXPORT_SYMBOL(snd_sof_ipc_msgs_tx); + +/* schedule work to handle IPC from DSP */ +void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) +{ + schedule_work(&sdev->ipc->rx_kwork); +} +EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); + +/* + * IPC trace mechanism. + */ + +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_dma_trace_posn posn; + + switch (msg_id) { + case SOF_IPC_TRACE_DMA_POSITION: + /* read back full message */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &posn, + sizeof(posn)); + snd_sof_trace_update_pos(sdev, &posn); + break; + default: + dev_err(sdev->dev, "error: unhandled trace message %x\n", + msg_id); + break; + } +} + +/* + * IPC stream position. + */ + +static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + u32 posn_offset; + int direction; + + /* check if we have stream box */ + if (sdev->stream_box.size == 0) { + /* read back full message */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &posn, + sizeof(posn)); + + spcm = snd_sof_find_spcm_comp(sdev, posn.comp_id, &direction); + } else { + spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + } + + if (!spcm) { + dev_err(sdev->dev, + "error: period elapsed for unknown stream, msg_id %d\n", + msg_id); + return; + } + + /* have stream box read from stream box */ + if (sdev->stream_box.size != 0) { + posn_offset = spcm->posn_offset[direction]; + snd_sof_dsp_mailbox_read(sdev, posn_offset, &posn, + sizeof(posn)); + + dev_dbg(sdev->dev, "posn mailbox: posn offset is 0x%x", + posn_offset); + } + + dev_dbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", + posn.host_posn, posn.dai_posn, posn.wallclock); + + memcpy(&spcm->stream[direction].posn, &posn, sizeof(posn)); + + /* only inform ALSA for period_wakeup mode */ + if (!spcm->stream[direction].substream->runtime->no_period_wakeup) + snd_pcm_period_elapsed(spcm->stream[direction].substream); +} + +/* DSP notifies host of an XRUN within FW */ +static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + u32 posn_offset; + int direction; + + /* check if we have stream MMIO on this platform */ + if (sdev->stream_box.size == 0) { + /* read back full message */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, &posn, + sizeof(posn)); + + spcm = snd_sof_find_spcm_comp(sdev, posn.comp_id, &direction); + } else { + spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + } + + if (!spcm) { + dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", + msg_id); + return; + } + + /* have stream box read from stream box */ + if (sdev->stream_box.size != 0) { + posn_offset = spcm->posn_offset[direction]; + snd_sof_dsp_mailbox_read(sdev, posn_offset, &posn, + sizeof(posn)); + + dev_dbg(sdev->dev, "posn mailbox: posn offset is 0x%x", + posn_offset); + } + + dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", + posn.host_posn, posn.xrun_comp_id, posn.xrun_size); + +#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) + /* stop PCM on XRUN - used for pipeline debug */ + memcpy(&spcm->stream[direction].posn, &posn, sizeof(posn)); + snd_pcm_stop_xrun(spcm->stream[direction].substream); +#endif +} + +/* stream notifications from DSP FW */ +static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) +{ + /* 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); + + switch (msg_type) { + case SOF_IPC_STREAM_POSITION: + ipc_period_elapsed(sdev, msg_id); + break; + case SOF_IPC_STREAM_TRIG_XRUN: + ipc_xrun(sdev, msg_id); + break; + default: + dev_err(sdev->dev, "error: unhandled stream message %x\n", + msg_id); + break; + } +} + +/* get stream position IPC - use faster MMIO method if available on platform */ +int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn) +{ + struct sof_ipc_stream stream; + int err; + + /* read position via slower IPC */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_POSITION; + stream.comp_id = spcm->stream[direction].comp_id; + + /* send IPC to the DSP */ + err = sof_ipc_tx_message(sdev->ipc, + stream.hdr.cmd, &stream, sizeof(stream), &posn, + sizeof(*posn)); + if (err < 0) { + dev_err(sdev->dev, "error: failed to get stream %d position\n", + stream.comp_id); + return err; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_stream_posn); + +/* + * IPC get()/set() for kcontrols. + */ + +int snd_sof_ipc_set_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + int err; + + /* read firmware volume */ + if (scontrol->readback_offset != 0) { + /* we can read value header via mmaped region */ + snd_sof_dsp_block_write(sdev, scontrol->readback_offset, + cdata->chanv, + sizeof(struct sof_ipc_ctrl_value_chan) * + cdata->num_elems); + + } else { + /* write value via slower IPC */ + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->cmd = ctrl_cmd; + cdata->type = ctrl_type; + cdata->rhdr.hdr.size = scontrol->size; + cdata->comp_id = scontrol->comp_id; + cdata->num_elems = scontrol->num_channels; + + /* send IPC to the DSP */ + err = sof_ipc_tx_message(sdev->ipc, + cdata->rhdr.hdr.cmd, cdata, + cdata->rhdr.hdr.size, + cdata, cdata->rhdr.hdr.size); + if (err < 0) { + dev_err(sdev->dev, "error: failed to set control %d values\n", + cdata->comp_id); + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_set_comp_data); + +int snd_sof_ipc_get_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + int err; + + /* read firmware byte counters */ + if (scontrol->readback_offset != 0) { + /* we can read values via mmaped region */ + snd_sof_dsp_block_read(sdev, scontrol->readback_offset, + cdata->chanv, + sizeof(struct sof_ipc_ctrl_value_chan) * + cdata->num_elems); + + } else { + /* read position via slower IPC */ + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->cmd = ctrl_cmd; + cdata->type = ctrl_type; + cdata->rhdr.hdr.size = scontrol->size; + cdata->comp_id = scontrol->comp_id; + cdata->num_elems = scontrol->num_channels; + + /* send IPC to the DSP */ + err = sof_ipc_tx_message(sdev->ipc, + cdata->rhdr.hdr.cmd, cdata, + cdata->rhdr.hdr.size, + cdata, cdata->rhdr.hdr.size); + if (err < 0) { + dev_err(sdev->dev, "error: failed to get control %d values\n", + cdata->comp_id); + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_get_comp_data); + +/* + * IPC layer enumeration. + */ + +int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, + size_t dspbox_size, u32 hostbox, + size_t hostbox_size) +{ + sdev->dsp_box.offset = dspbox; + sdev->dsp_box.size = dspbox_size; + sdev->host_box.offset = hostbox; + sdev->host_box.size = hostbox_size; + return 0; +} +EXPORT_SYMBOL(snd_sof_dsp_mailbox_init); + +int snd_sof_ipc_valid(struct snd_sof_dev *sdev) +{ + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + + dev_info(sdev->dev, + " Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, + v->micro, v->tag); + dev_info(sdev->dev, + " Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(v->abi_version), + SOF_ABI_VERSION_MINOR(v->abi_version), + SOF_ABI_VERSION_PATCH(v->abi_version), + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { + dev_err(sdev->dev, "error: incompatible FW ABI version\n"); + return -EINVAL; + } + + if (ready->debug.bits.build) { + dev_info(sdev->dev, + " Firmware debug build %d on %s-%s - options:\n" + " GDB: %s\n" + " lock debug: %s\n" + " lock vdebug: %s\n", + v->build, v->date, v->time, + ready->debug.bits.gdb ? "enabled" : "disabled", + ready->debug.bits.locks ? "enabled" : "disabled", + ready->debug.bits.locks_verbose ? + "enabled" : "disabled"); + } + + /* copy the fw_version into debugfs at first boot */ + memcpy(&sdev->fw_version, v, sizeof(*v)); + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_valid); + +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc *ipc; + struct snd_sof_ipc_msg *msg; + int i; + + ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return NULL; + + INIT_LIST_HEAD(&ipc->tx_list); + INIT_LIST_HEAD(&ipc->reply_list); + INIT_LIST_HEAD(&ipc->empty_list); + init_waitqueue_head(&ipc->wait_txq); + INIT_WORK(&ipc->tx_kwork, ipc_tx_next_msg); + INIT_WORK(&ipc->rx_kwork, ipc_msgs_rx); + ipc->sdev = sdev; + + /* pre-allocate messages */ + dev_dbg(sdev->dev, "pre-allocate %d IPC messages\n", + IPC_EMPTY_LIST_SIZE); + msg = devm_kzalloc(sdev->dev, sizeof(struct snd_sof_ipc_msg) * + IPC_EMPTY_LIST_SIZE, GFP_KERNEL); + if (!msg) + return NULL; + + /* pre-allocate message data */ + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + msg->msg_data = devm_kzalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL); + if (!msg->msg_data) + return NULL; + + msg->reply_data = devm_kzalloc(sdev->dev, PAGE_SIZE, + GFP_KERNEL); + if (!msg->reply_data) + return NULL; + + init_waitqueue_head(&msg->waitq); + list_add(&msg->list, &ipc->empty_list); + msg++; + } + + return ipc; +} +EXPORT_SYMBOL(snd_sof_ipc_init); + +void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{ + cancel_work_sync(&sdev->ipc->tx_kwork); + cancel_work_sync(&sdev->ipc->rx_kwork); +} +EXPORT_SYMBOL(snd_sof_ipc_free);
On Tue, Dec 11, 2018 at 03:23:08PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Define an IPC ABI for all host <--> DSP communication. This ABI should be transport agnostic. i.e. it should work on MMIO and SPI/I2C style interfaces.
- /* ssc1: TINTE */
+#define SOF_DAI_INTEL_SSP_QUIRK_TINTE (1 << 0)
- /* ssc1: PINTE */
+#define SOF_DAI_INTEL_SSP_QUIRK_PINTE (1 << 1)
- /* ssc2: SMTATF */
+#define SOF_DAI_INTEL_SSP_QUIRK_SMTATF (1 << 2)
- /* ssc2: MMRATF */
+#define SOF_DAI_INTEL_SSP_QUIRK_MMRATF (1 << 3)
- /* ssc2: PSPSTWFDFD */
+#define SOF_DAI_INTEL_SSP_QUIRK_PSPSTWFDFD (1 << 4)
- /* ssc2: PSPSRWFDFD */
+#define SOF_DAI_INTEL_SSP_QUIRK_PSPSRWFDFD (1 << 5)
Style mismatch. Looks like a candidate for BIT()
+#define SOF_DAI_FMT_FORMAT_MASK 0x000f +#define SOF_DAI_FMT_CLOCK_MASK 0x00f0 +#define SOF_DAI_FMT_INV_MASK 0x0f00 +#define SOF_DAI_FMT_MASTER_MASK 0xf000
GENMASK() ?
+/* flags indicating which time stamps are in sync with each other */ +#define SOF_TIME_HOST_SYNC (1 << 0) +#define SOF_TIME_DAI_SYNC (1 << 1) +#define SOF_TIME_WALL_SYNC (1 << 2) +#define SOF_TIME_STAMP_SYNC (1 << 3)
+/* flags indicating which time stamps are valid */ +#define SOF_TIME_HOST_VALID (1 << 8) +#define SOF_TIME_DAI_VALID (1 << 9) +#define SOF_TIME_WALL_VALID (1 << 10) +#define SOF_TIME_STAMP_VALID (1 << 11)
+/* flags indicating time stamps are 64bit else 3use low 32bit */ +#define SOF_TIME_HOST_64 (1 << 16) +#define SOF_TIME_DAI_64 (1 << 17) +#define SOF_TIME_WALL_64 (1 << 18) +#define SOF_TIME_STAMP_64 (1 << 19)
BIT() ?
+#define SOF_IPC_PANIC_MAGIC_MASK 0x0ffff000 +#define SOF_IPC_PANIC_CODE_MASK 0x00000fff
GENMASK() ?
+#define IPC_TIMEOUT_MSECS 300
Simple _MS ?
+/* find original TX message from DSP reply */ +static struct snd_sof_ipc_msg *sof_ipc_reply_find_msg(struct snd_sof_ipc *ipc,
u32 header)
+{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct snd_sof_ipc_msg *msg;
- header = SOF_IPC_MESSAGE_ID(header);
- if (list_empty(&ipc->reply_list))
goto err;
Redundant. list_for_each_*() have this check already.
- list_for_each_entry(msg, &ipc->reply_list, list) {
if (SOF_IPC_MESSAGE_ID(msg->header) == header)
return msg;
- }
+err:
- dev_err(sdev->dev, "error: rx list empty but received 0x%x\n",
header);
- return NULL;
+}
+int snd_sof_ipc_valid(struct snd_sof_dev *sdev) +{
- struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
- struct sof_ipc_fw_version *v = &ready->version;
- dev_info(sdev->dev,
" Firmware info: version %d:%d:%d-%s\n", v->major, v->minor,
Indentation issues.
v->micro, v->tag);
- dev_info(sdev->dev,
" Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
SOF_ABI_VERSION_MAJOR(v->abi_version),
SOF_ABI_VERSION_MINOR(v->abi_version),
SOF_ABI_VERSION_PATCH(v->abi_version),
SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) {
dev_err(sdev->dev, "error: incompatible FW ABI version\n");
return -EINVAL;
- }
- if (ready->debug.bits.build) {
dev_info(sdev->dev,
" Firmware debug build %d on %s-%s - options:\n"
" GDB: %s\n"
" lock debug: %s\n"
" lock vdebug: %s\n",
v->build, v->date, v->time,
ready->debug.bits.gdb ? "enabled" : "disabled",
ready->debug.bits.locks ? "enabled" : "disabled",
ready->debug.bits.locks_verbose ?
"enabled" : "disabled");
I would leave it on one line (only 3 characters more, but better readability).
ready->debug.bits.locks_verbose ? "enabled" : "disabled");
- }
- /* copy the fw_version into debugfs at first boot */
- memcpy(&sdev->fw_version, v, sizeof(*v));
- return 0;
+} +EXPORT_SYMBOL(snd_sof_ipc_valid);
- dev_dbg(sdev->dev, "pre-allocate %d IPC messages\n",
IPC_EMPTY_LIST_SIZE);
Noise.
- /* pre-allocate message data */
- for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) {
msg->msg_data = devm_kzalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL);
if (!msg->msg_data)
return NULL;
msg->reply_data = devm_kzalloc(sdev->dev, PAGE_SIZE,
GFP_KERNEL);
if (!msg->reply_data)
return NULL;
Can't you just get enough (non-continuous?) pages at once via get_free_pages interface? I didn't know that one, so, consider rather a way to look into.
init_waitqueue_head(&msg->waitq);
list_add(&msg->list, &ipc->empty_list);
msg++;
- }
- return ipc;
+} +EXPORT_SYMBOL(snd_sof_ipc_init);
On 12/11/18 4:57 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:08PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Define an IPC ABI for all host <--> DSP communication. This ABI should be transport agnostic. i.e. it should work on MMIO and SPI/I2C style interfaces.
- /* ssc1: TINTE */
+#define SOF_DAI_INTEL_SSP_QUIRK_TINTE (1 << 0)
- /* ssc1: PINTE */
+#define SOF_DAI_INTEL_SSP_QUIRK_PINTE (1 << 1)
- /* ssc2: SMTATF */
+#define SOF_DAI_INTEL_SSP_QUIRK_SMTATF (1 << 2)
- /* ssc2: MMRATF */
+#define SOF_DAI_INTEL_SSP_QUIRK_MMRATF (1 << 3)
- /* ssc2: PSPSTWFDFD */
+#define SOF_DAI_INTEL_SSP_QUIRK_PSPSTWFDFD (1 << 4)
- /* ssc2: PSPSRWFDFD */
+#define SOF_DAI_INTEL_SSP_QUIRK_PSPSRWFDFD (1 << 5)
Style mismatch. Looks like a candidate for BIT()
Yes but no. This comes from firmware definitions, so it's easier to leave them alone. Changing the kernel side of the IPC for style reasons just adds more work and chances of divergence.
+#define SOF_DAI_FMT_FORMAT_MASK 0x000f +#define SOF_DAI_FMT_CLOCK_MASK 0x00f0 +#define SOF_DAI_FMT_INV_MASK 0x0f00 +#define SOF_DAI_FMT_MASTER_MASK 0xf000
GENMASK() ?
+/* flags indicating which time stamps are in sync with each other */ +#define SOF_TIME_HOST_SYNC (1 << 0) +#define SOF_TIME_DAI_SYNC (1 << 1) +#define SOF_TIME_WALL_SYNC (1 << 2) +#define SOF_TIME_STAMP_SYNC (1 << 3)
+/* flags indicating which time stamps are valid */ +#define SOF_TIME_HOST_VALID (1 << 8) +#define SOF_TIME_DAI_VALID (1 << 9) +#define SOF_TIME_WALL_VALID (1 << 10) +#define SOF_TIME_STAMP_VALID (1 << 11)
+/* flags indicating time stamps are 64bit else 3use low 32bit */ +#define SOF_TIME_HOST_64 (1 << 16) +#define SOF_TIME_DAI_64 (1 << 17) +#define SOF_TIME_WALL_64 (1 << 18) +#define SOF_TIME_STAMP_64 (1 << 19)
BIT() ?
+#define SOF_IPC_PANIC_MAGIC_MASK 0x0ffff000 +#define SOF_IPC_PANIC_CODE_MASK 0x00000fff
GENMASK() ?
same for all, when the definitions are shared with firmware it's better to keep the values as is.
+#define IPC_TIMEOUT_MSECS 300
Simple _MS ?
yes.
+/* find original TX message from DSP reply */ +static struct snd_sof_ipc_msg *sof_ipc_reply_find_msg(struct snd_sof_ipc *ipc,
u32 header)
+{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct snd_sof_ipc_msg *msg;
- header = SOF_IPC_MESSAGE_ID(header);
- if (list_empty(&ipc->reply_list))
goto err;
Redundant. list_for_each_*() have this check already.
ok, nice catch.
- list_for_each_entry(msg, &ipc->reply_list, list) {
if (SOF_IPC_MESSAGE_ID(msg->header) == header)
return msg;
- }
+err:
- dev_err(sdev->dev, "error: rx list empty but received 0x%x\n",
header);
- return NULL;
+} +int snd_sof_ipc_valid(struct snd_sof_dev *sdev) +{
- struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
- struct sof_ipc_fw_version *v = &ready->version;
- dev_info(sdev->dev,
" Firmware info: version %d:%d:%d-%s\n", v->major, v->minor,
Indentation issues.
I believe they are intentional, but that's Liam's part.
v->micro, v->tag);
- dev_info(sdev->dev,
" Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
SOF_ABI_VERSION_MAJOR(v->abi_version),
SOF_ABI_VERSION_MINOR(v->abi_version),
SOF_ABI_VERSION_PATCH(v->abi_version),
SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) {
dev_err(sdev->dev, "error: incompatible FW ABI version\n");
return -EINVAL;
- }
- if (ready->debug.bits.build) {
dev_info(sdev->dev,
" Firmware debug build %d on %s-%s - options:\n"
" GDB: %s\n"
" lock debug: %s\n"
" lock vdebug: %s\n",
v->build, v->date, v->time,
ready->debug.bits.gdb ? "enabled" : "disabled",
ready->debug.bits.locks ? "enabled" : "disabled",
ready->debug.bits.locks_verbose ?
"enabled" : "disabled");
I would leave it on one line (only 3 characters more, but better readability).
ready->debug.bits.locks_verbose ? "enabled" : "disabled");
That must be a result of me asking folks to run checkpatch.pl --strict...
- }
- /* copy the fw_version into debugfs at first boot */
- memcpy(&sdev->fw_version, v, sizeof(*v));
- return 0;
+} +EXPORT_SYMBOL(snd_sof_ipc_valid);
- dev_dbg(sdev->dev, "pre-allocate %d IPC messages\n",
IPC_EMPTY_LIST_SIZE);
Noise.
ok
- /* pre-allocate message data */
- for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) {
msg->msg_data = devm_kzalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL);
if (!msg->msg_data)
return NULL;
msg->reply_data = devm_kzalloc(sdev->dev, PAGE_SIZE,
GFP_KERNEL);
if (!msg->reply_data)
return NULL;
Can't you just get enough (non-continuous?) pages at once via get_free_pages interface? I didn't know that one, so, consider rather a way to look into.
Dunno either, maybe something for Liam to look at?
init_waitqueue_head(&msg->waitq);
list_add(&msg->list, &ipc->empty_list);
msg++;
- }
- return ipc;
+} +EXPORT_SYMBOL(snd_sof_ipc_init);
On Tue, 11 Dec 2018 22:23:08 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Define an IPC ABI for all host <--> DSP communication. This ABI should be transport agnostic. i.e. it should work on MMIO and SPI/I2C style interfaces.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
include/sound/sof/control.h | 125 ++++++ include/sound/sof/dai-intel.h | 175 ++++++++ include/sound/sof/dai.h | 75 ++++ include/sound/sof/header.h | 158 +++++++ include/sound/sof/info.h | 118 +++++ include/sound/sof/pm.h | 48 ++ include/sound/sof/stream.h | 149 +++++++ include/sound/sof/trace.h | 66 +++ sound/soc/sof/ipc.c | 813 ++++++++++++++++++++++++++++++++++ 9 files changed, 1727 insertions(+) create mode 100644 include/sound/sof/control.h create mode 100644 include/sound/sof/dai-intel.h create mode 100644 include/sound/sof/dai.h create mode 100644 include/sound/sof/header.h create mode 100644 include/sound/sof/info.h create mode 100644 include/sound/sof/pm.h create mode 100644 include/sound/sof/stream.h create mode 100644 include/sound/sof/trace.h create mode 100644 sound/soc/sof/ipc.c
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h new file mode 100644 index 000000000000..7d7d2eba7d76 --- /dev/null +++ b/include/sound/sof/control.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved.
- */
+#ifndef __INCLUDE_SOUND_SOF_CONTROL_H__ +#define __INCLUDE_SOUND_SOF_CONTROL_H__
+#include <uapi/sound/sof/header.h> +#include <sound/sof/header.h>
+/*
- Component Mixers and Controls
- */
+/* channel positions - uses same values as ALSA */ +enum sof_ipc_chmap {
- SOF_CHMAP_UNKNOWN = 0,
- SOF_CHMAP_NA, /**< N/A, silent */
- SOF_CHMAP_MONO, /**< mono stream */
- SOF_CHMAP_FL, /**< front left */
- SOF_CHMAP_FR, /**< front right */
- SOF_CHMAP_RL, /**< rear left */
- SOF_CHMAP_RR, /**< rear right */
- SOF_CHMAP_FC, /**< front centre */
- SOF_CHMAP_LFE, /**< LFE */
- SOF_CHMAP_SL, /**< side left */
- SOF_CHMAP_SR, /**< side right */
- SOF_CHMAP_RC, /**< rear centre */
- SOF_CHMAP_FLC, /**< front left centre */
- SOF_CHMAP_FRC, /**< front right centre */
- SOF_CHMAP_RLC, /**< rear left centre */
- SOF_CHMAP_RRC, /**< rear right centre */
- SOF_CHMAP_FLW, /**< front left wide */
- SOF_CHMAP_FRW, /**< front right wide */
- SOF_CHMAP_FLH, /**< front left high */
- SOF_CHMAP_FCH, /**< front centre high */
- SOF_CHMAP_FRH, /**< front right high */
- SOF_CHMAP_TC, /**< top centre */
- SOF_CHMAP_TFL, /**< top front left */
- SOF_CHMAP_TFR, /**< top front right */
- SOF_CHMAP_TFC, /**< top front centre */
- SOF_CHMAP_TRL, /**< top rear left */
- SOF_CHMAP_TRR, /**< top rear right */
- SOF_CHMAP_TRC, /**< top rear centre */
- SOF_CHMAP_TFLC, /**< top front left centre */
- SOF_CHMAP_TFRC, /**< top front right centre */
- SOF_CHMAP_TSL, /**< top side left */
- SOF_CHMAP_TSR, /**< top side right */
- SOF_CHMAP_LLFE, /**< left LFE */
- SOF_CHMAP_RLFE, /**< right LFE */
- SOF_CHMAP_BC, /**< bottom centre */
- SOF_CHMAP_BLC, /**< bottom left centre */
- SOF_CHMAP_BRC, /**< bottom right centre */
- SOF_CHMAP_LAST = SOF_CHMAP_BRC,
+};
+/* control data type and direction */ +enum sof_ipc_ctrl_type {
- /* per channel data - uses struct sof_ipc_ctrl_value_chan */
- SOF_CTRL_TYPE_VALUE_CHAN_GET = 0,
- SOF_CTRL_TYPE_VALUE_CHAN_SET,
- /* component data - uses struct sof_ipc_ctrl_value_comp */
- SOF_CTRL_TYPE_VALUE_COMP_GET,
- SOF_CTRL_TYPE_VALUE_COMP_SET,
- /* bespoke data - struct struct sof_abi_hdr */
- SOF_CTRL_TYPE_DATA_GET,
- SOF_CTRL_TYPE_DATA_SET,
+};
+/* control command type */ +enum sof_ipc_ctrl_cmd {
- SOF_CTRL_CMD_VOLUME = 0, /**< maps to ALSA volume style controls */
- SOF_CTRL_CMD_ENUM, /**< maps to ALSA enum style controls */
- SOF_CTRL_CMD_SWITCH, /**< maps to ALSA switch style controls */
- SOF_CTRL_CMD_BINARY, /**< maps to ALSA binary style controls */
+};
+/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan {
- uint32_t channel; /**< channel map - enum sof_ipc_chmap */
- uint32_t value;
Any reason to avoid s32 and u32? If this is supposed to be shared with user-space (or user-space may use this as a reference of data struct), we should consider placing in uapi directory, too.
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg,
void *reply_data)
+{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data;
- unsigned long flags;
- int ret;
- /* wait for DSP IPC completion */
- ret = wait_event_timeout(msg->waitq, msg->ipc_complete,
msecs_to_jiffies(IPC_TIMEOUT_MSECS));
- spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
void *msg_data, size_t msg_bytes, void *reply_data,
size_t reply_bytes)
+{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct snd_sof_ipc_msg *msg;
- unsigned long flags;
- spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{
- cancel_work_sync(&sdev->ipc->tx_kwork);
- cancel_work_sync(&sdev->ipc->rx_kwork);
+} +EXPORT_SYMBOL(snd_sof_ipc_free);
Not specific to this function but a general question: why not EXPORT_SYMBOL_GPL() in general in the whole SOF codes?
thanks,
Takashi
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h
+/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan {
- uint32_t channel; /**< channel map - enum sof_ipc_chmap */
- uint32_t value;
Any reason to avoid s32 and u32? If this is supposed to be shared with user-space (or user-space may use this as a reference of data struct), we should consider placing in uapi directory, too.
it's intentional
The includes shared with userspace are in include/uapi/sound/sof.
All the files in include/sound/sof, and this one in particular, are more for host-dsp IPC.
In those two cases, uapi and IPC files, we don't use s32 and u32. we could move this directory under include/uapi/sound/sof-ipc if you prefer?
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg,
void *reply_data)
+{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data;
- unsigned long flags;
- int ret;
- /* wait for DSP IPC completion */
- ret = wait_event_timeout(msg->waitq, msg->ipc_complete,
msecs_to_jiffies(IPC_TIMEOUT_MSECS));
- spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
void *msg_data, size_t msg_bytes, void *reply_data,
size_t reply_bytes)
+{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct snd_sof_ipc_msg *msg;
- unsigned long flags;
- spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
This one is for Keyon and team. I've asked that question multiple times and was told the irqsave was needed. Keyon, can you please chime in?
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{
- cancel_work_sync(&sdev->ipc->tx_kwork);
- cancel_work_sync(&sdev->ipc->rx_kwork);
+} +EXPORT_SYMBOL(snd_sof_ipc_free);
Not specific to this function but a general question: why not EXPORT_SYMBOL_GPL() in general in the whole SOF codes?
We use a dual license (copied below)
// SPDX-License-Identifier: (GPL-2.0 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.
On Wed, 12 Dec 2018 16:19:45 +0100, Pierre-Louis Bossart wrote:
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h
+/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan {
- uint32_t channel; /**< channel map - enum sof_ipc_chmap */
- uint32_t value;
Any reason to avoid s32 and u32? If this is supposed to be shared with user-space (or user-space may use this as a reference of data struct), we should consider placing in uapi directory, too.
it's intentional
The includes shared with userspace are in include/uapi/sound/sof.
All the files in include/sound/sof, and this one in particular, are more for host-dsp IPC.
In those two cases, uapi and IPC files, we don't use s32 and u32. we could move this directory under include/uapi/sound/sof-ipc if you prefer?
I don't mind so much but just wondered since it looks as if a part of ABI definition. In anyway, u32/s32 are nicer for kernel codes in general. When I read int32_t or such lengthy form, automatically my alarm chimes as if an alien visiting. But it's also no big deal to keep such types.
BTW, if a file is moved to uapi as a part of ABI, the types should be __u32, not u32.
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{
- cancel_work_sync(&sdev->ipc->tx_kwork);
- cancel_work_sync(&sdev->ipc->rx_kwork);
+} +EXPORT_SYMBOL(snd_sof_ipc_free);
Not specific to this function but a general question: why not EXPORT_SYMBOL_GPL() in general in the whole SOF codes?
We use a dual license (copied below)
// SPDX-License-Identifier: (GPL-2.0 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.
Ah, that can of worms... I don't know whether it makes so much sense to keep such a dual license, but I know it's a hairy problem enough for keeping my fingers away.
thanks,
Takashi
On 2018/12/12 下午11:19, Pierre-Louis Bossart wrote:
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h
+/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan { + uint32_t channel; /**< channel map - enum sof_ipc_chmap */ + uint32_t value;
Any reason to avoid s32 and u32? If this is supposed to be shared with user-space (or user-space may use this as a reference of data struct), we should consider placing in uapi directory, too.
it's intentional
The includes shared with userspace are in include/uapi/sound/sof.
All the files in include/sound/sof, and this one in particular, are more for host-dsp IPC.
In those two cases, uapi and IPC files, we don't use s32 and u32. we could move this directory under include/uapi/sound/sof-ipc if you prefer?
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data; + unsigned long flags; + int ret;
+ /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS));
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags;
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
This one is for Keyon and team. I've asked that question multiple times and was told the irqsave was needed. Keyon, can you please chime in?
we basically have 3 parts where using this ipc_lock:
1. sof_ipc_tx_message(), get lock, update tx_list, schedule tx_work, put lock, then call tx_wait_done(); 2. ipc_tx_next_msg() (tx_work itself), get lock, send message, put lock; 2.5. ack/reply ipc interrupt arrived, mark ipc_complete in handler. 3. tx_wait_done(), wait until ipc_complete(or timeout), then get lock, handle the ack/reply, and put lock at last.
|1 -[--]-|-> 3------(done)-[--]-| | ^ V | |2-[--]-| | |2.5--|
those []s means holding locks.
So, all those 3 functions can't be called from the spin-locked context as they need to hold the lock inside them.
I admit that we are too conservative that using spin_lock_irqsave/restore() here, as Takashi mentioned, here all 3 functions are actually run in normal thread context, I think we can even run them with interrupt enabled(using spin_lock/unlock() directly).
Thanks, ~Keyon
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{ + cancel_work_sync(&sdev->ipc->tx_kwork); + cancel_work_sync(&sdev->ipc->rx_kwork); +} +EXPORT_SYMBOL(snd_sof_ipc_free);
Not specific to this function but a general question: why not EXPORT_SYMBOL_GPL() in general in the whole SOF codes?
We use a dual license (copied below)
// SPDX-License-Identifier: (GPL-2.0 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.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Thu, 13 Dec 2018 06:24:18 +0100, Keyon Jie wrote:
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data; + unsigned long flags; + int ret;
+ /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS));
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags;
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
This one is for Keyon and team. I've asked that question multiple times and was told the irqsave was needed. Keyon, can you please chime in?
we basically have 3 parts where using this ipc_lock:
- sof_ipc_tx_message(), get lock, update tx_list, schedule tx_work,
put lock, then call tx_wait_done(); 2. ipc_tx_next_msg() (tx_work itself), get lock, send message, put lock; 2.5. ack/reply ipc interrupt arrived, mark ipc_complete in handler. 3. tx_wait_done(), wait until ipc_complete(or timeout), then get lock, handle the ack/reply, and put lock at last.
|1 -[--]-|-> 3------(done)-[--]-| | ^ V | |2-[--]-| | |2.5--|
those []s means holding locks.
So, all those 3 functions can't be called from the spin-locked context as they need to hold the lock inside them.
I admit that we are too conservative that using spin_lock_irqsave/restore() here, as Takashi mentioned, here all 3 functions are actually run in normal thread context, I think we can even run them with interrupt enabled(using spin_lock/unlock() directly).
Well, if we can use spin_lock() variant, mutex is often a better alternative.
The most important point is to know which particular calls may be called in spinlocked / interrupt context beforehand and which are not. This reflects to the API design.
thanks,
Takashi
On 2018/12/13 下午3:48, Takashi Iwai wrote:
On Thu, 13 Dec 2018 06:24:18 +0100, Keyon Jie wrote:
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data; + unsigned long flags; + int ret;
+ /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS));
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags;
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
This one is for Keyon and team. I've asked that question multiple times and was told the irqsave was needed. Keyon, can you please chime in?
we basically have 3 parts where using this ipc_lock:
- sof_ipc_tx_message(), get lock, update tx_list, schedule tx_work,
put lock, then call tx_wait_done(); 2. ipc_tx_next_msg() (tx_work itself), get lock, send message, put lock; 2.5. ack/reply ipc interrupt arrived, mark ipc_complete in handler. 3. tx_wait_done(), wait until ipc_complete(or timeout), then get lock, handle the ack/reply, and put lock at last.
|1 -[--]-|-> 3------(done)-[--]-| | ^ V | |2-[--]-| | |2.5--|
those []s means holding locks.
So, all those 3 functions can't be called from the spin-locked context as they need to hold the lock inside them.
I admit that we are too conservative that using spin_lock_irqsave/restore() here, as Takashi mentioned, here all 3 functions are actually run in normal thread context, I think we can even run them with interrupt enabled(using spin_lock/unlock() directly).
Well, if we can use spin_lock() variant, mutex is often a better alternative.
The most important point is to know which particular calls may be called in spinlocked / interrupt context beforehand and which are not. This reflects to the API design.
Thanks Takashi, we will refine this part and add comments for each function about these context preconditions.
Thanks, ~Keyon
thanks,
Takashi _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On 2018/12/13 下午1:24, Keyon Jie wrote:
On 2018/12/12 下午11:19, Pierre-Louis Bossart wrote:
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h
+/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan { + uint32_t channel; /**< channel map - enum sof_ipc_chmap */ + uint32_t value;
Any reason to avoid s32 and u32? If this is supposed to be shared with user-space (or user-space may use this as a reference of data struct), we should consider placing in uapi directory, too.
it's intentional
The includes shared with userspace are in include/uapi/sound/sof.
All the files in include/sound/sof, and this one in particular, are more for host-dsp IPC.
In those two cases, uapi and IPC files, we don't use s32 and u32. we could move this directory under include/uapi/sound/sof-ipc if you prefer?
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data; + unsigned long flags; + int ret;
+ /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS));
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags;
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
This one is for Keyon and team. I've asked that question multiple times and was told the irqsave was needed. Keyon, can you please chime in?
we basically have 3 parts where using this ipc_lock:
- sof_ipc_tx_message(), get lock, update tx_list, schedule tx_work, put
lock, then call tx_wait_done(); 2. ipc_tx_next_msg() (tx_work itself), get lock, send message, put lock; 2.5. ack/reply ipc interrupt arrived, mark ipc_complete in handler. 3. tx_wait_done(), wait until ipc_complete(or timeout), then get lock, handle the ack/reply, and put lock at last.
|1 -[--]-|-> 3------(done)-[--]-| | ^ V | |2-[--]-| | |2.5--|
those []s means holding locks.
So, all those 3 functions can't be called from the spin-locked context as they need to hold the lock inside them.
I admit that we are too conservative that using spin_lock_irqsave/restore() here, as Takashi mentioned, here all 3 functions are actually run in normal thread context, I think we can even run them with interrupt enabled(using spin_lock/unlock() directly).
Sorry, as #2 is a schedule work where should not sleep, so it is better to use spin_lock/unlock_irq() here.
Thanks, ~Keyon
Thanks, ~Keyon
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{ + cancel_work_sync(&sdev->ipc->tx_kwork); + cancel_work_sync(&sdev->ipc->rx_kwork); +} +EXPORT_SYMBOL(snd_sof_ipc_free);
Not specific to this function but a general question: why not EXPORT_SYMBOL_GPL() in general in the whole SOF codes?
We use a dual license (copied below)
// SPDX-License-Identifier: (GPL-2.0 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.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
在 12/13/2018 4:06 PM, Keyon Jie 写道:
On 2018/12/13 下午1:24, Keyon Jie wrote:
On 2018/12/12 下午11:19, Pierre-Louis Bossart wrote:
diff --git a/include/sound/sof/control.h b/include/sound/sof/control.h
+/* generic channel mapped value data */ +struct sof_ipc_ctrl_value_chan { + uint32_t channel; /**< channel map - enum sof_ipc_chmap */ + uint32_t value;
Any reason to avoid s32 and u32? If this is supposed to be shared with user-space (or user-space may use this as a reference of data struct), we should consider placing in uapi directory, too.
it's intentional
The includes shared with userspace are in include/uapi/sound/sof.
All the files in include/sound/sof, and this one in particular, are more for host-dsp IPC.
In those two cases, uapi and IPC files, we don't use s32 and u32. we could move this directory under include/uapi/sound/sof-ipc if you prefer?
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = (struct sof_ipc_cmd_hdr *)msg->msg_data; + unsigned long flags; + int ret;
+ /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS));
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Since this must be a sleepable context, you can safely use spin_lock_irq() here.
+/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + unsigned long flags;
+ spin_lock_irqsave(&sdev->ipc_lock, flags);
Ditto. This one calls tx_wait_done() later.
It's better to define more strictly which one can be called from the spinlocked context and which not.
This one is for Keyon and team. I've asked that question multiple times and was told the irqsave was needed. Keyon, can you please chime in?
we basically have 3 parts where using this ipc_lock:
- sof_ipc_tx_message(), get lock, update tx_list, schedule tx_work,
put lock, then call tx_wait_done(); 2. ipc_tx_next_msg() (tx_work itself), get lock, send message, put lock; 2.5. ack/reply ipc interrupt arrived, mark ipc_complete in handler. 3. tx_wait_done(), wait until ipc_complete(or timeout), then get lock, handle the ack/reply, and put lock at last.
|1 -[--]-|-> 3------(done)-[--]-| | ^ V | |2-[--]-| | |2.5--|
those []s means holding locks.
So, all those 3 functions can't be called from the spin-locked context as they need to hold the lock inside them.
I admit that we are too conservative that using spin_lock_irqsave/restore() here, as Takashi mentioned, here all 3 functions are actually run in normal thread context, I think we can even run them with interrupt enabled(using spin_lock/unlock() directly).
Sorry, as #2 is a schedule work where should not sleep, so it is better to use spin_lock/unlock_irq() here.
Thanks, ~Keyon
I just discussed with keyon, got a conclusion that spin_lock_irq should be applied here.
Rander
Thanks, ~Keyon
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{ + cancel_work_sync(&sdev->ipc->tx_kwork); + cancel_work_sync(&sdev->ipc->rx_kwork); +} +EXPORT_SYMBOL(snd_sof_ipc_free);
Not specific to this function but a general question: why not EXPORT_SYMBOL_GPL() in general in the whole SOF codes?
We use a dual license (copied below)
// SPDX-License-Identifier: (GPL-2.0 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.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Dec 11, 2018 at 03:23:08PM -0600, Pierre-Louis Bossart wrote:
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg,
void *reply_data)
+{
This has exactly one caller, why not inline it (or make both tx and rx separate functions)?
- spin_lock_irqsave(&sdev->ipc_lock, flags);
- /* get an empty message */
- msg = msg_get_empty(ipc);
- if (!msg) {
spin_unlock_irqrestore(&sdev->ipc_lock, flags);
return -EBUSY;
- }
- msg->header = header;
- msg->msg_size = msg_bytes;
- msg->reply_size = reply_bytes;
- msg->ipc_complete = false;
- /* attach any data */
- if (msg_bytes)
memcpy(msg->msg_data, msg_data, msg_bytes);
How big do these messages get? Do we need to hold the lock while we memcpy()?
- /* schedule the message if not busy */
- if (snd_sof_dsp_is_ready(sdev))
schedule_work(&ipc->tx_kwork);
If the DSP is idle is there a reason this has to happen in another thread?
- spin_unlock_irqrestore(&sdev->ipc_lock, flags);
The thread is also going to take an irq spinlock after all.
- /* send first message in TX list */
- msg = list_first_entry(&ipc->tx_list, struct snd_sof_ipc_msg, list);
- list_move(&msg->list, &ipc->reply_list);
- snd_sof_dsp_send_msg(sdev, msg);
Should the move happen if the send fails (I see it can return an error code, though we ignore it)?
- int err = -EINVAL;
- case SOF_IPC_FW_READY:
/* check for FW boot completion */
if (!sdev->boot_complete) {
if (sdev->ops->fw_ready)
err = sdev->ops->fw_ready(sdev, cmd);
if (err < 0) {
dev_err(sdev->dev, "error: DSP firmware boot timeout %d\n",
err);
err defaults to -EINVAL here which doesn't seem like the right thing if fw_ready() is really optional. Perhaps just validate the ops on registration and call this unconditionally?
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{
- cancel_work_sync(&sdev->ipc->tx_kwork);
- cancel_work_sync(&sdev->ipc->rx_kwork);
+} +EXPORT_SYMBOL(snd_sof_ipc_free);
It might be better to have something that stops any new messages being processed as well, to prevent races on removal.
[consolidated answers from Liam and me]
+/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg,
void *reply_data)
+{
This has exactly one caller, why not inline it (or make both tx and rx separate functions)?
we'll look into it.
- spin_lock_irqsave(&sdev->ipc_lock, flags);
- /* get an empty message */
- msg = msg_get_empty(ipc);
- if (!msg) {
spin_unlock_irqrestore(&sdev->ipc_lock, flags);
return -EBUSY;
- }
- msg->header = header;
- msg->msg_size = msg_bytes;
- msg->reply_size = reply_bytes;
- msg->ipc_complete = false;
- /* attach any data */
- if (msg_bytes)
memcpy(msg->msg_data, msg_data, msg_bytes);
How big do these messages get? Do we need to hold the lock while we memcpy()?
Messages can be as big as the mailbox, which is hardware dependent. It could be from a few bytes to a larger e.g. 4k page or more, and indeed we need to keep the lock.
- /* schedule the message if not busy */
- if (snd_sof_dsp_is_ready(sdev))
schedule_work(&ipc->tx_kwork);
If the DSP is idle is there a reason this has to happen in another thread?
we will rename this as snd_sof_dsp_is_ipc_ready() to avoid any confusion with the DSP state. We only care about IPC registers/doorbells at this point, not the fact that the DSP is in its idle loop.
- spin_unlock_irqrestore(&sdev->ipc_lock, flags);
The thread is also going to take an irq spinlock after all.
didn't get this point, sorry.
- /* send first message in TX list */
- msg = list_first_entry(&ipc->tx_list, struct snd_sof_ipc_msg, list);
- list_move(&msg->list, &ipc->reply_list);
- snd_sof_dsp_send_msg(sdev, msg);
Should the move happen if the send fails (I see it can return an error code, though we ignore it)?
so far neither the HDaudio nor the BYT stuff return an error on send_msg, so we'll need to thing about what happens should this happen with other hardware, or demote the status to void.
We'll look into this.
- int err = -EINVAL;
- case SOF_IPC_FW_READY:
/* check for FW boot completion */
if (!sdev->boot_complete) {
if (sdev->ops->fw_ready)
err = sdev->ops->fw_ready(sdev, cmd);
if (err < 0) {
dev_err(sdev->dev, "error: DSP firmware boot timeout %d\n",
err);
err defaults to -EINVAL here which doesn't seem like the right thing if fw_ready() is really optional. Perhaps just validate the ops on registration and call this unconditionally?
Good catch, err should default to 0, thanks for pointing this out.
+void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{
- cancel_work_sync(&sdev->ipc->tx_kwork);
- cancel_work_sync(&sdev->ipc->rx_kwork);
+} +EXPORT_SYMBOL(snd_sof_ipc_free);
It might be better to have something that stops any new messages being processed as well, to prevent races on removal.
ok, we will add something to stop the IPC, good suggestion indeed.
On Thu, Jan 10, 2019 at 02:11:32PM -0600, Pierre-Louis Bossart wrote:
- /* attach any data */
- if (msg_bytes)
memcpy(msg->msg_data, msg_data, msg_bytes);
How big do these messages get? Do we need to hold the lock while we memcpy()?
Messages can be as big as the mailbox, which is hardware dependent. It could be from a few bytes to a larger e.g. 4k page or more, and indeed we need to keep the lock.
Is this copying into an actual mailbox or into some data structure in memory? It looked like it's copying into a buffer for sending rather than the mailbox.
- /* schedule the message if not busy */
- if (snd_sof_dsp_is_ready(sdev))
schedule_work(&ipc->tx_kwork);
If the DSP is idle is there a reason this has to happen in another thread?
we will rename this as snd_sof_dsp_is_ipc_ready() to avoid any confusion with the DSP state. We only care about IPC registers/doorbells at this point, not the fact that the DSP is in its idle loop.
You're missing the point - why can't we just immediately send the message to the DSP from here, what's the benefit of scheduling some work to do that?
- spin_unlock_irqrestore(&sdev->ipc_lock, flags);
The thread is also going to take an irq spinlock after all.
didn't get this point, sorry.
One reason to bounce to another thread would be to do something that you can't do in this context like take a lock in a place you can't take locks but here we're taking a lock that needs thread context already.
On 1/22/19 1:04 PM, Mark Brown wrote:
On Thu, Jan 10, 2019 at 02:11:32PM -0600, Pierre-Louis Bossart wrote:
- /* attach any data */
- if (msg_bytes)
memcpy(msg->msg_data, msg_data, msg_bytes);
How big do these messages get? Do we need to hold the lock while we memcpy()?
Messages can be as big as the mailbox, which is hardware dependent. It could be from a few bytes to a larger e.g. 4k page or more, and indeed we need to keep the lock.
Is this copying into an actual mailbox or into some data structure in memory? It looked like it's copying into a buffer for sending rather than the mailbox.
I realize between your feedback and Daniel's that we have a terminology issue. On the Intel side we don't have a dedicated hardware mailbox unit, it's only doorbell registers and shared memory windows.
- /* schedule the message if not busy */
- if (snd_sof_dsp_is_ready(sdev))
schedule_work(&ipc->tx_kwork);
If the DSP is idle is there a reason this has to happen in another thread?
we will rename this as snd_sof_dsp_is_ipc_ready() to avoid any confusion with the DSP state. We only care about IPC registers/doorbells at this point, not the fact that the DSP is in its idle loop.
You're missing the point - why can't we just immediately send the message to the DSP from here, what's the benefit of scheduling some work to do that?
I realize this might be Intel-specific and will likely have to evolve.
Keyon/Liam, this is your code, can you comment on Mark's comments (here)
- spin_unlock_irqrestore(&sdev->ipc_lock, flags);
The thread is also going to take an irq spinlock after all.
didn't get this point, sorry.
One reason to bounce to another thread would be to do something that you can't do in this context like take a lock in a place you can't take locks but here we're taking a lock that needs thread context already.
Liam/Keyon comment needed here as well.
I think there are multiple questions that should be better answered to
1. why do we schedule a thread when the DSP is not busy
2. what happens if it is?
3. can we avoid freeing the lock to take it again when we schedule?
On Tue, Jan 22, 2019 at 03:05:23PM -0600, Pierre-Louis Bossart wrote:
On 1/22/19 1:04 PM, Mark Brown wrote:
On Thu, Jan 10, 2019 at 02:11:32PM -0600, Pierre-Louis Bossart wrote:
Messages can be as big as the mailbox, which is hardware dependent. It could be from a few bytes to a larger e.g. 4k page or more, and indeed we need to keep the lock.
Is this copying into an actual mailbox or into some data structure in memory? It looked like it's copying into a buffer for sending rather than the mailbox.
I realize between your feedback and Daniel's that we have a terminology issue. On the Intel side we don't have a dedicated hardware mailbox unit, it's only doorbell registers and shared memory windows.
Ah, that's what I thought was going on. What I was expecting was a sequence of operations along the lines of:
1. Allocate a buffer for the message. 2. Fill in the message. 3. Mark the message as readable by the DSP.
so you only need locking for steps 1 and 3 (and a similar path on return).
On 2019/1/23 上午5:05, Pierre-Louis Bossart wrote:
On 1/22/19 1:04 PM, Mark Brown wrote:
On Thu, Jan 10, 2019 at 02:11:32PM -0600, Pierre-Louis Bossart wrote:
+ /* attach any data */ + if (msg_bytes) + memcpy(msg->msg_data, msg_data, msg_bytes);
How big do these messages get? Do we need to hold the lock while we memcpy()?
Messages can be as big as the mailbox, which is hardware dependent. It could be from a few bytes to a larger e.g. 4k page or more, and indeed we need to keep the lock.
Is this copying into an actual mailbox or into some data structure in memory? It looked like it's copying into a buffer for sending rather than the mailbox.
I realize between your feedback and Daniel's that we have a terminology issue. On the Intel side we don't have a dedicated hardware mailbox unit, it's only doorbell registers and shared memory windows.
+ /* schedule the message if not busy */ + if (snd_sof_dsp_is_ready(sdev)) + schedule_work(&ipc->tx_kwork);
If the DSP is idle is there a reason this has to happen in another thread?
we will rename this as snd_sof_dsp_is_ipc_ready() to avoid any confusion with the DSP state. We only care about IPC registers/doorbells at this point, not the fact that the DSP is in its idle loop.
You're missing the point - why can't we just immediately send the message to the DSP from here, what's the benefit of scheduling some work to do that?
I realize this might be Intel-specific and will likely have to evolve.
Keyon/Liam, this is your code, can you comment on Mark's comments (here)
Hi Mark, I think you are right, we can actually call the message sending immediately here as we already hold the lock which can avoid race condition.
We scheduled another thread to do the sending task, aimed to make sure those messages(required to be sent from different thread) can be sent in sequence one by one(with the FIFO list), but that doesn't means we have to do it in another thread, let's change it.
+ spin_unlock_irqrestore(&sdev->ipc_lock, flags);
The thread is also going to take an irq spinlock after all.
didn't get this point, sorry.
One reason to bounce to another thread would be to do something that you can't do in this context like take a lock in a place you can't take locks but here we're taking a lock that needs thread context already.
Liam/Keyon comment needed here as well.
I think there are multiple questions that should be better answered to
- why do we schedule a thread when the DSP is not busy
As explain above, I think it's not necessary, let's change it.
what happens if it is?
can we avoid freeing the lock to take it again when we schedule?
Then those #2 #3 issues not existed?
Thanks, ~Keyon
Sound-open-firmware mailing list Sound-open-firmware@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/sound-open-firmware
<snip>
+/* send next IPC message in list */ +static void ipc_tx_next_msg(struct work_struct *work) +{
struct snd_sof_ipc *ipc =
container_of(work, struct snd_sof_ipc, tx_kwork);
struct snd_sof_dev *sdev = ipc->sdev;
struct snd_sof_ipc_msg *msg;
unsigned long flags;
spin_lock_irqsave(&sdev->ipc_lock, flags);
/* send message if HW read and message in TX list */
s/HW read/HW ready/ I think.
if (list_empty(&ipc->tx_list) || !snd_sof_dsp_is_ready(sdev))
goto out;
+static void ipc_tx_next_msg(struct work_struct *work) +{
struct snd_sof_ipc *ipc =
container_of(work, struct snd_sof_ipc, tx_kwork);
struct snd_sof_dev *sdev = ipc->sdev;
struct snd_sof_ipc_msg *msg;
unsigned long flags;
spin_lock_irqsave(&sdev->ipc_lock, flags);
/* send message if HW read and message in TX list */
s/HW read/HW ready/ I think.
yes indeed, thanks for the note.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add support for exposing PCMs to userspace. PCMs are defined by topology and the operations in this patch map to SOF IPC calls.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/pcm.c | 755 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 755 insertions(+) create mode 100644 sound/soc/sof/pcm.c
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c new file mode 100644 index 000000000000..1b01383a380d --- /dev/null +++ b/sound/soc/sof/pcm.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// +// PCM Layer, interface between ALSA and IPC. +// + +#include <linux/pm_runtime.h> +#include <sound/pcm_params.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "ops.h" + +#define DRV_NAME "sof-audio" + +/* Create DMA buffer page table for DSP */ +static int create_page_table(struct snd_pcm_substream *substream, + unsigned char *dma_area, size_t size) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); + int stream = substream->stream; + + return snd_sof_create_page_table(sdev, dmab, + spcm->stream[stream].page_table.area, size); +} + +/* this may get called several times by oss emulation */ +static int sof_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + struct sof_ipc_pcm_params pcm; + struct sof_ipc_pcm_params_reply ipc_params_reply; + int posn_offset; + int ret; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + dev_dbg(sdev->dev, "pcm: hw params stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); + + memset(&pcm, 0, sizeof(pcm)); + + /* allocate audio buffer pages */ + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(sdev->dev, "error: could not allocate %d bytes for PCM %d\n", + params_buffer_bytes(params), ret); + return ret; + } + + /* craete compressed page table for audio firmware */ + ret = create_page_table(substream, runtime->dma_area, + runtime->dma_bytes); + if (ret < 0) + return ret; + + /* number of pages should be rounded up */ + if (runtime->dma_bytes % PAGE_SIZE) + pcm.params.buffer.pages = (runtime->dma_bytes / PAGE_SIZE) + 1; + else + pcm.params.buffer.pages = runtime->dma_bytes / PAGE_SIZE; + + /* set IPC PCM parameters */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = spcm->stream[substream->stream].comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.buffer.phy_addr = + spcm->stream[substream->stream].page_table.addr; + pcm.params.buffer.size = runtime->dma_bytes; + pcm.params.buffer.offset = 0; + pcm.params.direction = substream->stream; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* container size */ + switch (params_width(params)) { + case 16: + pcm.params.sample_container_bytes = 2; + break; + case 24: + pcm.params.sample_container_bytes = 4; + break; + case 32: + pcm.params.sample_container_bytes = 4; + break; + default: + return -EINVAL; + } + + /* format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + case SNDRV_PCM_FORMAT_FLOAT: + pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT; + break; + default: + return -EINVAL; + } + + /* firmware already configured host stream */ + ret = snd_sof_pcm_platform_hw_params(sdev, + substream, + params, + &pcm.params); + dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + + /* validate offset */ + posn_offset = ipc_params_reply.posn_offset; + + /* check if offset is overflow or it is not aligned */ + if (posn_offset > sdev->stream_box.size || + posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) { + dev_err(sdev->dev, "error: got wrong posn offset 0x%x for PCM %d\n", + posn_offset, ret); + return ret; + } + spcm->posn_offset[substream->stream] = + sdev->stream_box.offset + posn_offset; + + /* save pcm hw_params */ + memcpy(&spcm->params[substream->stream], params, sizeof(*params)); + + return ret; +} + +static int sof_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + + snd_pcm_lib_free_pages(substream); + return ret; +} + +static int sof_restore_hw_params(struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, + struct snd_sof_dev *sdev) +{ + snd_pcm_uframes_t host = 0; + u64 host_posn; + int ret = 0; + + /* resume stream */ + host_posn = spcm->stream[substream->stream].posn.host_posn; + host = bytes_to_frames(substream->runtime, host_posn); + dev_dbg(sdev->dev, + "PCM: resume stream %d dir %d DMA position %lu\n", + spcm->pcm.pcm_id, substream->stream, host); + + /* set hw_params */ + ret = sof_pcm_hw_params(substream, + &spcm->params[substream->stream]); + if (ret < 0) { + dev_err(sdev->dev, + "error: set pcm hw_params after resume\n"); + return ret; + } + + return 0; +} + +static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret = 0; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + dev_dbg(sdev->dev, "pcm: trigger stream %d dir %d cmd %d\n", + spcm->pcm.pcm_id, substream->stream, cmd); + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + + /* + * Check if stream was marked for restore before suspend + */ + if (spcm->restore_stream[substream->stream]) { + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + + /* do not send ipc as the stream hasn't been set up */ + return 0; + } + + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + + /* check if the stream hw_params needs to be restored */ + if (spcm->restore_stream[substream->stream]) { + + /* restore hw_params */ + ret = sof_restore_hw_params(substream, spcm, sdev); + if (ret < 0) + return ret; + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + + /* trigger start */ + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + } else { + + /* trigger pause release */ + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; + } + break; + case SNDRV_PCM_TRIGGER_START: + /* fall through */ + case SNDRV_PCM_TRIGGER_RESUME: + + /* check if the stream hw_params needs to be restored */ + if (spcm->restore_stream[substream->stream]) { + + /* restore hw_params */ + ret = sof_restore_hw_params(substream, spcm, sdev); + if (ret < 0) + return ret; + + /* unset restore_stream */ + spcm->restore_stream[substream->stream] = 0; + } + + /* trigger stream */ + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + default: + dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd); + return -EINVAL; + } + + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + + return ret; +} + +static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + snd_pcm_uframes_t host = 0, dai = 0; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + /* if have dsp ops pointer callback, use that directly */ + if (sdev->hda && sdev->ops->pcm_pointer) + return sdev->ops->pcm_pointer(sdev, substream); + + /* read position from DSP */ + host = bytes_to_frames(substream->runtime, + spcm->stream[substream->stream].posn.host_posn); + dai = bytes_to_frames(substream->runtime, + spcm->stream[substream->stream].posn.dai_posn); + + dev_dbg(sdev->dev, "PCM: stream %d dir %d DMA position %lu DAI position %lu\n", + spcm->pcm.pcm_id, substream->stream, host, dai); + + return host; +} + +static int sof_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + struct snd_soc_tplg_stream_caps *caps = + &spcm->pcm.caps[substream->stream]; + int ret; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + dev_dbg(sdev->dev, "pcm: open stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + mutex_lock(&spcm->mutex); + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: pcm open failed to resume %d\n", + ret); + mutex_unlock(&spcm->mutex); + return ret; + } + + /* set any runtime constraints based on topology */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + le32_to_cpu(caps->period_size_min)); + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + le32_to_cpu(caps->period_size_min)); + + /* set runtime config */ + runtime->hw.info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP; + runtime->hw.formats = le64_to_cpu(caps->formats); + runtime->hw.period_bytes_min = le32_to_cpu(caps->period_size_min); + runtime->hw.period_bytes_max = le32_to_cpu(caps->period_size_max); + runtime->hw.periods_min = le32_to_cpu(caps->periods_min); + runtime->hw.periods_max = le32_to_cpu(caps->periods_max); + runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max); + + dev_dbg(sdev->dev, "period min %zd max %zd bytes\n", + runtime->hw.period_bytes_min, + runtime->hw.period_bytes_max); + dev_dbg(sdev->dev, "period count %d max %d\n", + runtime->hw.periods_min, + runtime->hw.periods_max); + dev_dbg(sdev->dev, "buffer max %zd bytes\n", + runtime->hw.buffer_bytes_max); + + /* set wait time - TODO: come from topology */ + substream->wait_time = 500; + + spcm->stream[substream->stream].posn.host_posn = 0; + spcm->stream[substream->stream].posn.dai_posn = 0; + spcm->stream[substream->stream].substream = substream; + + snd_sof_pcm_platform_open(sdev, substream); + + mutex_unlock(&spcm->mutex); + return 0; +} + +static int sof_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = rtd->private; + int err; + + /* nothing todo for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + dev_dbg(sdev->dev, "pcm: close stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + snd_sof_pcm_platform_close(sdev, substream); + + mutex_lock(&spcm->mutex); + pm_runtime_mark_last_busy(sdev->dev); + + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: pcm close failed to idle %d\n", + err); + + mutex_unlock(&spcm->mutex); + return 0; +} + +static struct snd_pcm_ops sof_pcm_ops = { + .open = sof_pcm_open, + .close = sof_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sof_pcm_hw_params, + .hw_free = sof_pcm_hw_free, + .trigger = sof_pcm_trigger, + .pointer = sof_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_tplg_stream_caps *caps; + int ret = 0, stream = SNDRV_PCM_STREAM_PLAYBACK; + + /* find SOF PCM for this RTD */ + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) { + dev_warn(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + rtd->private = spcm; + + dev_dbg(sdev->dev, "creating new PCM %s\n", spcm->pcm.pcm_name); + + /* do we need to allocate playback PCM DMA pages */ + if (!spcm->pcm.playback) + goto capture; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate playback audio buffer pages */ + dev_dbg(sdev->dev, "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n", + caps->name, caps->buffer_size_min, caps->buffer_size_max); + + ret = snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->parent, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); + if (ret) { + dev_err(sdev->dev, "error: can't alloc DMA buffer size 0x%x/0x%x for %s %d\n", + caps->buffer_size_min, caps->buffer_size_max, + caps->name, ret); + return ret; + } + + /* allocate playback page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->parent, + PAGE_SIZE, &spcm->stream[stream].page_table); + if (ret < 0) { + dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + caps->name, ret); + goto free_playback_dma_buffer; + } + +capture: + stream = SNDRV_PCM_STREAM_CAPTURE; + + /* do we need to allocate capture PCM DMA pages */ + if (!spcm->pcm.capture) + return ret; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate capture audio buffer pages */ + dev_dbg(sdev->dev, "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n", + caps->name, caps->buffer_size_min, caps->buffer_size_max); + + ret = snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->parent, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); + if (ret) { + dev_err(sdev->dev, "error: can't alloc DMA buffer size 0x%x/0x%x for %s %d\n", + caps->buffer_size_min, caps->buffer_size_max, + caps->name, ret); + goto free_playback_tables; + } + + /* allocate capture page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->parent, + PAGE_SIZE, &spcm->stream[stream].page_table); + if (ret < 0) { + dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + caps->name, ret); + goto free_playback_tables; + } + + /* TODO: assign channel maps from topology */ + + return ret; + +free_playback_tables: + if (spcm->pcm.playback) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].page_table); + +free_playback_dma_buffer: + /* + * no need to explicitly release preallocated memory, + * snd_pcm_lib_preallocate_free_for_all() is called by the core + */ + + return ret; +} + +static void sof_pcm_free(struct snd_pcm *pcm) +{ + struct snd_sof_pcm *spcm; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) { + dev_warn(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return; + } + + if (spcm->pcm.playback) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].page_table); + + if (spcm->pcm.capture) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_CAPTURE].page_table); + + /* + * no need to explicitly release preallocated memory, + * snd_pcm_lib_preallocate_free_for_all() is called by the core + */ +} + +/* fixup the BE DAI link to match any values from topology */ +static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_dai *dai = + snd_sof_find_dai(sdev, (char *)rtd->dai_link->name); + + /* no topology exists for this BE, try a common configuration */ + if (!dai) { + dev_warn(sdev->dev, "warning: no topology found for BE DAI %s config\n", + rtd->dai_link->name); + + /* set 48k, stereo, 16bits by default */ + rate->min = 48000; + rate->max = 48000; + + channels->min = 2; + channels->max = 2; + + snd_mask_none(fmt); + snd_mask_set(fmt, (__force int)SNDRV_PCM_FORMAT_S16_LE); + + return 0; + } + + /* read format from topology */ + snd_mask_none(fmt); + + switch (dai->comp_dai.config.frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + snd_mask_set(fmt, (__force int)SNDRV_PCM_FORMAT_S16_LE); + break; + case SOF_IPC_FRAME_S24_4LE: + snd_mask_set(fmt, (__force int)SNDRV_PCM_FORMAT_S24_LE); + break; + case SOF_IPC_FRAME_S32_LE: + snd_mask_set(fmt, (__force int)SNDRV_PCM_FORMAT_S32_LE); + break; + default: + dev_err(sdev->dev, "error: No available DAI format!\n"); + return -EINVAL; + } + + /* read rate and channels from topology */ + switch (dai->dai_config->type) { + case SOF_DAI_INTEL_SSP: + rate->min = dai->dai_config->ssp.fsync_rate; + rate->max = dai->dai_config->ssp.fsync_rate; + channels->min = dai->dai_config->ssp.tdm_slots; + channels->max = dai->dai_config->ssp.tdm_slots; + + dev_dbg(sdev->dev, + "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(sdev->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + + break; + case SOF_DAI_INTEL_DMIC: + /* DMIC only supports 16 or 32 bit formats */ + if (dai->comp_dai.config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { + dev_err(sdev->dev, + "error: invalid fmt %d for DAI type %d\n", + dai->comp_dai.config.frame_fmt, + dai->dai_config->type); + } + /* TODO: add any other DMIC specific fixups */ + break; + case SOF_DAI_INTEL_HDA: + /* do nothing for HDA dai_link */ + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", + dai->dai_config->type); + break; + } + + return 0; +} + +static int sof_pcm_probe(struct snd_soc_component *component) +{ + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + struct snd_sof_pdata *plat_data = dev_get_platdata(component->dev); + const char *tplg_filename; + int ret, err; + + /* load the default topology */ + sdev->component = component; + tplg_filename = plat_data->machine->sof_tplg_filename; + + ret = snd_sof_load_topology(sdev, tplg_filename); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP topology %d\n", + ret); + goto err; + } + + /* enable runtime PM with auto suspend */ + pm_runtime_set_autosuspend_delay(component->dev, + SND_SOF_SUSPEND_DELAY); + pm_runtime_use_autosuspend(component->dev); + pm_runtime_enable(component->dev); + + pm_runtime_mark_last_busy(component->dev); + err = pm_runtime_put_autosuspend(component->dev); + if (err < 0) + dev_err(sdev->dev, "error: failed to enter PM idle %d\n", err); + +err: + return ret; +} + +static void sof_pcm_remove(struct snd_soc_component *component) +{ + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(component); + + pm_runtime_disable(component->dev); + snd_sof_free_topology(sdev); + +} + +void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) +{ + struct snd_soc_component_driver *pd = &sdev->plat_drv; + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *plat_name, *drv_name; + + plat_name = plat_data->machine->asoc_plat_name; + drv_name = plat_data->machine->drv_name; + + dev_dbg(sdev->dev, "using platform alias %s\n", plat_name); + + pd->name = "sof-audio"; + pd->probe = sof_pcm_probe; + pd->remove = sof_pcm_remove; + pd->ops = &sof_pcm_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + pd->compr_ops = &sof_compressed_ops; +#endif + pd->pcm_new = sof_pcm_new; + pd->pcm_free = sof_pcm_free; + pd->ignore_machine = drv_name; + pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; + pd->be_pcm_base = SOF_BE_PCM_BASE; + pd->use_dai_pcm_id = true; + pd->topology_name_prefix = "sof"; +} +
On Tue, 11 Dec 2018 22:23:09 +0100, Pierre-Louis Bossart wrote:
- /* number of pages should be rounded up */
- if (runtime->dma_bytes % PAGE_SIZE)
pcm.params.buffer.pages = (runtime->dma_bytes / PAGE_SIZE) + 1;
- else
pcm.params.buffer.pages = runtime->dma_bytes / PAGE_SIZE;
There is likely some nice macro for this :)
- /* firmware already configured host stream */
- ret = snd_sof_pcm_platform_hw_params(sdev,
substream,
params,
&pcm.params);
- dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag);
This error can be ignored?
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
&ipc_params_reply, sizeof(ipc_params_reply));
This error value isn't evaluated immediately but passed until the last. Is it intentional?
- /* validate offset */
- posn_offset = ipc_params_reply.posn_offset;
- /* check if offset is overflow or it is not aligned */
- if (posn_offset > sdev->stream_box.size ||
posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) {
dev_err(sdev->dev, "error: got wrong posn offset 0x%x for PCM %d\n",
posn_offset, ret);
return ret;
Here you return an error after checking more things.
- }
- spcm->posn_offset[substream->stream] =
sdev->stream_box.offset + posn_offset;
- /* save pcm hw_params */
- memcpy(&spcm->params[substream->stream], params, sizeof(*params));
- return ret;
Even here returns an error after saving as if done properly.
+static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{
....
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/* check if the stream hw_params needs to be restored */
if (spcm->restore_stream[substream->stream]) {
/* restore hw_params */
ret = sof_restore_hw_params(substream, spcm, sdev);
This calls the hw_params that is supposed to be non-atomic.
- snd_sof_pcm_platform_trigger(sdev, substream, cmd);
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
- return ret;
... so the whole trigger action is non-atomic PCM only?
thanks,
Takashi
On Wed, Dec 12, 2018 at 09:04:38AM +0100, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:09 +0100, Pierre-Louis Bossart wrote:
- /* number of pages should be rounded up */
- if (runtime->dma_bytes % PAGE_SIZE)
pcm.params.buffer.pages = (runtime->dma_bytes / PAGE_SIZE) + 1;
- else
pcm.params.buffer.pages = runtime->dma_bytes / PAGE_SIZE;
There is likely some nice macro for this :)
DIV_ROUND_UP() ?
On 12/12/18 2:04 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:09 +0100, Pierre-Louis Bossart wrote:
- /* number of pages should be rounded up */
- if (runtime->dma_bytes % PAGE_SIZE)
pcm.params.buffer.pages = (runtime->dma_bytes / PAGE_SIZE) + 1;
- else
pcm.params.buffer.pages = runtime->dma_bytes / PAGE_SIZE;
There is likely some nice macro for this :)
Yes, will fix.
- /* firmware already configured host stream */
- ret = snd_sof_pcm_platform_hw_params(sdev,
substream,
params,
&pcm.params);
- dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag);
This error can be ignored?
will double-check.
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
&ipc_params_reply, sizeof(ipc_params_reply));
This error value isn't evaluated immediately but passed until the last. Is it intentional?
Not sure, will double-check. Now that I read the code again this looks odds, and if it was intentional then we need an explicit comment. Thanks for the sighting.
- /* validate offset */
- posn_offset = ipc_params_reply.posn_offset;
- /* check if offset is overflow or it is not aligned */
- if (posn_offset > sdev->stream_box.size ||
posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) {
dev_err(sdev->dev, "error: got wrong posn offset 0x%x for PCM %d\n",
posn_offset, ret);
return ret;
Here you return an error after checking more things.
- }
- spcm->posn_offset[substream->stream] =
sdev->stream_box.offset + posn_offset;
- /* save pcm hw_params */
- memcpy(&spcm->params[substream->stream], params, sizeof(*params));
- return ret;
Even here returns an error after saving as if done properly.
will double-check this entire sequence.
+static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{
....
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/* check if the stream hw_params needs to be restored */
if (spcm->restore_stream[substream->stream]) {
/* restore hw_params */
ret = sof_restore_hw_params(substream, spcm, sdev);
This calls the hw_params that is supposed to be non-atomic.
- snd_sof_pcm_platform_trigger(sdev, substream, cmd);
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
- return ret;
... so the whole trigger action is non-atomic PCM only?
Not sure if I fully understand your point here. The trigger does indeed need an IPC to proceed, but the front-ends are marked as such with the .nonatomic field set to true. Not sure how different this is from existing atom/sst or Skylake drivers.
On Wed, 12 Dec 2018 16:29:23 +0100, Pierre-Louis Bossart wrote:
- snd_sof_pcm_platform_trigger(sdev, substream, cmd);
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
- return ret;
... so the whole trigger action is non-atomic PCM only?
Not sure if I fully understand your point here. The trigger does indeed need an IPC to proceed, but the front-ends are marked as such with the .nonatomic field set to true. Not sure how different this is from existing atom/sst or Skylake drivers.
Hm, so this relies purely on FE, and it seems that we have no check whether FE and BE match wrt nonatomic PCM ops. It has to be addressed, but it's a different topic.
At most, you can put some notes in the code that all these are non-atomic ops. This makes it clearer, for example, that spinlock with irqsave is often redundant.
thanks,
Takashi
On 12/12/18 9:43 AM, Takashi Iwai wrote:
On Wed, 12 Dec 2018 16:29:23 +0100, Pierre-Louis Bossart wrote:
- snd_sof_pcm_platform_trigger(sdev, substream, cmd);
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
- return ret;
... so the whole trigger action is non-atomic PCM only?
Not sure if I fully understand your point here. The trigger does indeed need an IPC to proceed, but the front-ends are marked as such with the .nonatomic field set to true. Not sure how different this is from existing atom/sst or Skylake drivers.
Hm, so this relies purely on FE, and it seems that we have no check whether FE and BE match wrt nonatomic PCM ops. It has to be addressed, but it's a different topic.
At most, you can put some notes in the code that all these are non-atomic ops. This makes it clearer, for example, that spinlock with irqsave is often redundant.
Yes, noted, we'll check the spinlock use and add comments as need when operations are non-atomic. Thanks for the suggestions, much appreciated.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
SOF uses topology to define the DAPM graphs and widgets, DAIs, PCMs and set parameters for init and run time usage. This patch loads topology and maps it to IPC commands that are build the topology on the DSP.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof/topology.h | 256 ++++ sound/soc/sof/topology.c | 2775 ++++++++++++++++++++++++++++++++++ 2 files changed, 3031 insertions(+) create mode 100644 include/sound/sof/topology.h create mode 100644 sound/soc/sof/topology.c
diff --git a/include/sound/sof/topology.h b/include/sound/sof/topology.h new file mode 100644 index 000000000000..155758598ed5 --- /dev/null +++ b/include/sound/sof/topology.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_TOPOLOGY_H__ +#define __INCLUDE_SOUND_SOF_TOPOLOGY_H__ + +#include <sound/sof/header.h> + +/* + * Component + */ + +/* types of component */ +enum sof_comp_type { + SOF_COMP_NONE = 0, + SOF_COMP_HOST, + SOF_COMP_DAI, + SOF_COMP_SG_HOST, /**< scatter gather variant */ + SOF_COMP_SG_DAI, /**< scatter gather variant */ + SOF_COMP_VOLUME, + SOF_COMP_MIXER, + SOF_COMP_MUX, + SOF_COMP_SRC, + SOF_COMP_SPLITTER, + SOF_COMP_TONE, + SOF_COMP_SWITCH, + SOF_COMP_BUFFER, + SOF_COMP_EQ_IIR, + SOF_COMP_EQ_FIR, + SOF_COMP_FILEREAD, /**< host test based file IO */ + SOF_COMP_FILEWRITE, /**< host test based file IO */ +}; + +/* XRUN action for component */ +#define SOF_XRUN_STOP 1 /**< stop stream */ +#define SOF_XRUN_UNDER_ZERO 2 /**< send 0s to sink */ +#define SOF_XRUN_OVER_NULL 4 /**< send data to NULL */ + +/* create new generic component - SOF_IPC_TPLG_COMP_NEW */ +struct sof_ipc_comp { + struct sof_ipc_cmd_hdr hdr; + uint32_t id; + enum sof_comp_type type; + uint32_t pipeline_id; + + /* reserved for future use */ + uint32_t reserved[2]; +} __packed; + +/* + * Component Buffers + */ + +/* + * SOF memory capabilities, add new ones at the end + */ +#define SOF_MEM_CAPS_RAM (1 << 0) +#define SOF_MEM_CAPS_ROM (1 << 1) +#define SOF_MEM_CAPS_EXT (1 << 2) /**< external */ +#define SOF_MEM_CAPS_LP (1 << 3) /**< low power */ +#define SOF_MEM_CAPS_HP (1 << 4) /**< high performance */ +#define SOF_MEM_CAPS_DMA (1 << 5) /**< DMA'able */ +#define SOF_MEM_CAPS_CACHE (1 << 6) /**< cacheable */ +#define SOF_MEM_CAPS_EXEC (1 << 7) /**< executable */ + +/* create new component buffer - SOF_IPC_TPLG_BUFFER_NEW */ +struct sof_ipc_buffer { + struct sof_ipc_comp comp; + uint32_t size; /**< buffer size in bytes */ + uint32_t caps; /**< SOF_MEM_CAPS_ */ +} __packed; + +/* generic component config data - must always be after struct sof_ipc_comp */ +struct sof_ipc_comp_config { + struct sof_ipc_cmd_hdr hdr; + uint32_t periods_sink; /**< 0 means variable */ + uint32_t periods_source; /**< 0 means variable */ + uint32_t preload_count; /**< how many periods to preload */ + uint32_t frame_fmt; /**< SOF_IPC_FRAME_ */ + uint32_t xrun_action; + + /* reserved for future use */ + uint32_t reserved[2]; +} __packed; + +/* generic host component */ +struct sof_ipc_comp_host { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t direction; /**< SOF_IPC_STREAM_ */ + uint32_t no_irq; /**< don't send periodic IRQ to host/DSP */ + uint32_t dmac_config; /**< DMA engine specific */ +} __packed; + +/* generic DAI component */ +struct sof_ipc_comp_dai { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t direction; /**< SOF_IPC_STREAM_ */ + uint32_t dai_index; /**< index of this type dai */ + uint32_t type; /**< DAI type - SOF_DAI_ */ + uint32_t dmac_config; /**< DMA engine specific */ +} __packed; + +/* generic mixer component */ +struct sof_ipc_comp_mixer { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; +} __packed; + +/* volume ramping types */ +enum sof_volume_ramp { + SOF_VOLUME_LINEAR = 0, + SOF_VOLUME_LOG, + SOF_VOLUME_LINEAR_ZC, + SOF_VOLUME_LOG_ZC, +}; + +/* generic volume component */ +struct sof_ipc_comp_volume { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t channels; + uint32_t min_value; + uint32_t max_value; + uint32_t ramp; /**< SOF_VOLUME_ */ + uint32_t initial_ramp; /**< ramp space in ms */ +} __packed; + +/* generic SRC component */ +struct sof_ipc_comp_src { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + /* either source or sink rate must be non zero */ + uint32_t source_rate; /**< source rate or 0 for variable */ + uint32_t sink_rate; /**< sink rate or 0 for variable */ + uint32_t rate_mask; /**< SOF_RATE_ supported rates */ +} __packed; + +/* generic MUX component */ +struct sof_ipc_comp_mux { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; +} __packed; + +/* generic tone generator component */ +struct sof_ipc_comp_tone { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + int32_t sample_rate; + int32_t frequency; + int32_t amplitude; + int32_t freq_mult; + int32_t ampl_mult; + int32_t length; + int32_t period; + int32_t repeats; + int32_t ramp_step; +} __packed; + +/** \brief Types of EFFECT */ +enum sof_ipc_effect_type { + SOF_EFFECT_NONE = 0, /**< None */ + SOF_EFFECT_INTEL_EQFIR, /**< Intel FIR */ + SOF_EFFECT_INTEL_EQIIR, /**< Intel IIR */ +}; + +/* general purpose EFFECT configuration */ +struct sof_ipc_comp_effect { + struct sof_ipc_hdr hdr; + uint32_t type; /** sof_ipc_effect_type */ +} __packed; + +/* FIR equalizer component */ +struct sof_ipc_comp_eq_fir { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t size; + + /* reserved for future use */ + uint32_t reserved[8]; + + unsigned char data[0]; +} __packed; + +/* IIR equalizer component */ +struct sof_ipc_comp_eq_iir { + struct sof_ipc_comp comp; + struct sof_ipc_comp_config config; + uint32_t size; + + /* reserved for future use */ + uint32_t reserved[8]; + + unsigned char data[0]; +} __packed; + +/* frees components, buffers and pipelines + * SOF_IPC_TPLG_COMP_FREE, SOF_IPC_TPLG_PIPE_FREE, SOF_IPC_TPLG_BUFFER_FREE + */ +struct sof_ipc_free { + struct sof_ipc_cmd_hdr hdr; + uint32_t id; +} __packed; + +struct sof_ipc_comp_reply { + struct sof_ipc_reply rhdr; + uint32_t id; + uint32_t offset; +} __packed; + +/* + * Pipeline + */ + +/* new pipeline - SOF_IPC_TPLG_PIPE_NEW */ +struct sof_ipc_pipe_new { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; /**< component id for pipeline */ + uint32_t pipeline_id; /**< pipeline id */ + uint32_t sched_id; /**< Scheduling component id */ + uint32_t core; /**< core we run on */ + uint32_t deadline; /**< execution completion deadline in us*/ + uint32_t priority; /**< priority level 0 (low) to 10 (max) */ + uint32_t period_mips; /**< worst case instruction count per period */ + uint32_t frames_per_sched;/**< output frames of pipeline, 0 is variable */ + uint32_t xrun_limit_usecs; /**< report xruns greater than limit */ + + /* non zero if timer scheduled, otherwise DAI DMA irq scheduled */ + uint32_t timer_delay; +} __packed; + +/* pipeline construction complete - SOF_IPC_TPLG_PIPE_COMPLETE */ +struct sof_ipc_pipe_ready { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; +} __packed; + +struct sof_ipc_pipe_free { + struct sof_ipc_cmd_hdr hdr; + uint32_t comp_id; +} __packed; + +/* connect two components in pipeline - SOF_IPC_TPLG_COMP_CONNECT */ +struct sof_ipc_pipe_comp_connect { + struct sof_ipc_cmd_hdr hdr; + uint32_t source_id; + uint32_t sink_id; +} __packed; + +#endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c new file mode 100644 index 000000000000..b3883e75084f --- /dev/null +++ b/sound/soc/sof/topology.c @@ -0,0 +1,2775 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/firmware.h> +#include <sound/tlv.h> +#include <uapi/sound/sof/tokens.h> +#include "sof-priv.h" +#include "ops.h" + +#define COMP_ID_UNASSIGNED 0xffffffff +/* Constants used in the computation of linear volume gain from dB gain */ +/* 20th root of 10 in Q1.16 fixed-point notation*/ +#define VOL_TWENTIETH_ROOT_OF_TEN 73533 +/* 40th root of 10 in Q1.16 fixed-point notation*/ +#define VOL_FORTIETH_ROOT_OF_TEN 69419 +/* Volume fractional word length */ +/* Define to 16 sets the volume linear gain value to use Qx.16 format */ +#define VOLUME_FWL 16 +/* 0.5 dB step value in topology TLV */ +#define VOL_HALF_DB_STEP 50 +/* Full volume for default values */ +#define VOL_ZERO_DB BIT(VOLUME_FWL) + +/* TLV data items */ +#define TLV_ITEMS 3 +#define TLV_MIN 0 +#define TLV_STEP 1 +#define TLV_MUTE 2 + +static inline int get_tlv_data(const int *p, int tlv[TLV_ITEMS]) +{ + /* we only support dB scale TLV type at the moment */ + if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) + return -EINVAL; + + /* min value in topology tlv data is multiplied by 100 */ + tlv[TLV_MIN] = (int)p[SNDRV_CTL_TLVO_DB_SCALE_MIN] / 100; + + /* volume steps */ + tlv[TLV_STEP] = (int)(p[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & + TLV_DB_SCALE_MASK); + + /* mute ON/OFF */ + if ((p[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & + TLV_DB_SCALE_MUTE) == 0) + tlv[TLV_MUTE] = 0; + else + tlv[TLV_MUTE] = 1; + + return 0; +} + +/* Function to truncate an unsigned 64-bit number + * by x bits and return 32-bit unsigned number + * This function also takes care of rounding while truncating + */ +static inline u32 vol_shift_64(u64 i, u32 x) +{ + /* do not truncate more than 32 bits */ + if (x > 32) + x = 32; + + if (x == 0) + return (u32)i; + + return (u32)(((i >> (x - 1)) + 1) >> 1); +} + +/* Function to compute a ^ exp where, + * a is a fractional number represented by a fixed-point integer + * with a fractional world length of "fwl" + * exp is an integer + * fwl is the fractional word length + * Return value is a fractional number represented by a fixed-point + * integer with a fractional word length of "fwl" + */ +static u32 vol_pow32(u32 a, int exp, u32 fwl) +{ + int i, iter; + u32 power = 1 << fwl; + u64 numerator; + + /* if exponent is 0, return 1 */ + if (exp == 0) + return power; + + /* determine the number of iterations based on the exponent */ + if (exp < 0) + iter = exp * -1; + else + iter = exp; + + /* mutiply a "iter" times to compute power */ + for (i = 0; i < iter; i++) { + /* Product of 2 Qx.fwl fixed-point numbers yields a Q2*x.2*fwl + * Truncate product back to fwl fractional bits with rounding + */ + power = vol_shift_64((u64)power * a, fwl); + } + + if (exp > 0) { + /* if exp is positive, return the result */ + return power; + } + + /* if exp is negative, return the multiplicative inverse */ + numerator = (u64)1 << (fwl << 1); + do_div(numerator, power); + + return (u32)numerator; +} + +/* Function to calculate volume gain from TLV data + * This function can only handle gain steps that are multiples of 0.5 dB + */ +static u32 vol_compute_gain(u32 value, int *tlv) +{ + int dB_gain; + u32 linear_gain; + int f_step; + + /* mute volume */ + if (value == 0 && tlv[TLV_MUTE]) + return 0; + + /* compute dB gain from tlv + * tlv_step in topology is multiplied by 100 + */ + dB_gain = tlv[TLV_MIN] + (value * tlv[TLV_STEP]) / 100; + + /* compute linear gain + * represented by fixed-point int with VOLUME_FWL fractional bits + */ + linear_gain = vol_pow32(VOL_TWENTIETH_ROOT_OF_TEN, dB_gain, VOLUME_FWL); + + /* extract the fractional part of volume step */ + f_step = tlv[TLV_STEP] - (tlv[TLV_STEP] / 100); + + /* if volume step is an odd multiple of 0.5 dB */ + if (f_step == VOL_HALF_DB_STEP && (value & 1)) + linear_gain = vol_shift_64((u64)linear_gain * + VOL_FORTIETH_ROOT_OF_TEN, + VOLUME_FWL); + + return linear_gain; +} + +/* Set up volume table for kcontrols from tlv data + * "size" specifies the number of entries in the table + */ +static int set_up_volume_table(struct snd_sof_control *scontrol, + int tlv[TLV_ITEMS], int size) +{ + int j; + + /* init the volume table */ + scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!scontrol->volume_table) + return -ENOMEM; + + /* populate the volume table */ + for (j = 0; j < size ; j++) + scontrol->volume_table[j] = vol_compute_gain(j, tlv); + + return 0; +} + +struct sof_dai_types { + const char *name; + enum sof_ipc_dai_type type; +}; + +static const struct sof_dai_types sof_dais[] = { + {"SSP", SOF_DAI_INTEL_SSP}, + {"HDA", SOF_DAI_INTEL_HDA}, + {"DMIC", SOF_DAI_INTEL_DMIC}, +}; + +static enum sof_ipc_dai_type find_dai(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_dais); i++) { + if (strcmp(name, sof_dais[i].name) == 0) + return sof_dais[i].type; + } + + return SOF_DAI_INTEL_NONE; +} + +/* + * Supported Frame format types and lookup, add new ones to end of list. + */ + +struct sof_frame_types { + const char *name; + enum sof_ipc_frame frame; +}; + +static const struct sof_frame_types sof_frames[] = { + {"s16le", SOF_IPC_FRAME_S16_LE}, + {"s24le", SOF_IPC_FRAME_S24_4LE}, + {"s32le", SOF_IPC_FRAME_S32_LE}, + {"float", SOF_IPC_FRAME_FLOAT}, +}; + +static enum sof_ipc_frame find_format(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_frames); i++) { + if (strcmp(name, sof_frames[i].name) == 0) + return sof_frames[i].frame; + } + + /* use s32le if nothing is specified */ + return SOF_IPC_FRAME_S32_LE; +} + +struct sof_effect_types { + const char *name; + enum sof_ipc_effect_type type; +}; + +static const struct sof_effect_types sof_effects[] = { + {"EQFIR", SOF_EFFECT_INTEL_EQFIR}, + {"EQIIR", SOF_EFFECT_INTEL_EQIIR}, +}; + +static enum sof_ipc_effect_type find_effect(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_effects); i++) { + if (strcmp(name, sof_effects[i].name) == 0) + return sof_effects[i].type; + } + + return SOF_EFFECT_NONE; +} + +/* + * Standard Kcontrols. + */ + +static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_mixer_control *mc = + (struct snd_soc_tplg_mixer_control *)hdr; + struct sof_ipc_ctrl_data *cdata; + + /* validate topology data */ + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + + /* init the volume get/put data */ + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_ipc_ctrl_value_chan) * + le32_to_cpu(mc->num_channels); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + cdata = scontrol->control_data; + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + return 0; +} + +static int sof_control_load_bytes(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_ctrl_data *cdata; + struct snd_soc_tplg_bytes_control *control = + (struct snd_soc_tplg_bytes_control *)hdr; + const int max_size = SOF_IPC_MSG_MAX_SIZE - + sizeof(const struct sof_ipc_ctrl_data); + + /* init the get/put bytes data */ + scontrol->size = SOF_IPC_MSG_MAX_SIZE; + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + cdata = scontrol->control_data; + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->cmd = SOF_CTRL_CMD_BINARY; + + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + if (le32_to_cpu(control->priv.size) > max_size) { + dev_err(sdev->dev, "error: bytes priv data size %d exceeds max %d.\n", + control->priv.size, max_size); + return -EINVAL; + } + + if (le32_to_cpu(control->priv.size) > 0) { + memcpy(cdata->data, control->priv.data, + le32_to_cpu(control->priv.size)); + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err(sdev->dev, "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + return -EINVAL; + } + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, + cdata->data->abi)) { + dev_err(sdev->dev, + "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + return -EINVAL; + } + if (cdata->data->size + sizeof(const struct sof_abi_hdr) != + le32_to_cpu(control->priv.size)) { + dev_err(sdev->dev, + "error: Conflict in bytes vs. priv size.\n"); + return -EINVAL; + } + } + return 0; +} + +/* + * Topology Token Parsing. + * New tokens should be added to headers and parsing tables below. + */ + +struct sof_topology_token { + u32 token; + u32 type; + int (*get_token)(void *elem, void *object, u32 offset, u32 size); + u32 offset; + u32 size; +}; + +static int get_token_u32(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + u32 *val = object + offset; + + *val = le32_to_cpu(velem->value); + return 0; +} + +static int get_token_u16(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + u16 *val = object + offset; + + *val = (u16)le32_to_cpu(velem->value); + return 0; +} + +static int get_token_comp_format(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = object + offset; + + *val = find_format(velem->string); + return 0; +} + +static int get_token_dai_type(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = object + offset; + + *val = find_dai(velem->string); + return 0; +} + +static int get_token_effect_type(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = object + offset; + + *val = find_effect(velem->string); + return 0; +} + +/* Buffers */ +static const struct sof_topology_token buffer_tokens[] = { + {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, size), 0}, + {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, caps), 0}, +}; + +/* DAI */ +static const struct sof_topology_token dai_tokens[] = { + {SOF_TKN_DAI_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, dmac_config), 0}, + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_comp_dai, type), 0}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, dai_index), 0}, + {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, direction), 0}, +}; + +/* BE DAI link */ +static const struct sof_topology_token dai_link_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_dai_config, type), 0}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_config, dai_index), 0}, +}; + +/* scheduling */ +static const struct sof_topology_token sched_tokens[] = { + {SOF_TKN_SCHED_DEADLINE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, deadline), 0}, + {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, priority), 0}, + {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period_mips), 0}, + {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, core), 0}, + {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, frames_per_sched), 0}, + {SOF_TKN_SCHED_TIMER, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, timer_delay), 0}, +}; + +/* volume */ +static const struct sof_topology_token volume_tokens[] = { + {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc_comp_volume, ramp), 0}, + {SOF_TKN_VOLUME_RAMP_STEP_MS, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_volume, initial_ramp), 0}, +}; + +/* SRC */ +static const struct sof_topology_token src_tokens[] = { + {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, source_rate), 0}, + {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, sink_rate), 0}, +}; + +/* Tone */ +static const struct sof_topology_token tone_tokens[] = { +}; + +/* EFFECT */ +static const struct sof_topology_token effect_tokens[] = { + {SOF_TKN_EFFECT_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, + get_token_effect_type, + offsetof(struct sof_ipc_comp_effect, type), 0}, +}; + +/* PCM */ +static const struct sof_topology_token pcm_tokens[] = { + {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_host, dmac_config), 0}, +}; + +/* Generic components */ +static const struct sof_topology_token comp_tokens[] = { + {SOF_TKN_COMP_PERIOD_SINK_COUNT, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_sink), 0}, + {SOF_TKN_COMP_PERIOD_SOURCE_COUNT, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_source), 0}, + {SOF_TKN_COMP_FORMAT, + SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format, + offsetof(struct sof_ipc_comp_config, frame_fmt), 0}, + {SOF_TKN_COMP_PRELOAD_COUNT, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, preload_count), 0}, +}; + +/* SSP */ +static const struct sof_topology_token ssp_tokens[] = { + {SOF_TKN_INTEL_SSP_CLKS_CONTROL, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, clks_control), 0}, + {SOF_TKN_INTEL_SSP_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, mclk_id), 0}, + {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits), 0}, + {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, + get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width), 0}, + {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, quirks), 0}, + {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, + get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, + tdm_per_slot_padding_flag), 0}, + +}; + +/* DMIC */ +static const struct sof_topology_token dmic_tokens[] = { + {SOF_TKN_INTEL_DMIC_DRIVER_VERSION, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version), + 0}, + {SOF_TKN_INTEL_DMIC_CLK_MIN, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min), 0}, + {SOF_TKN_INTEL_DMIC_CLK_MAX, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max), 0}, + {SOF_TKN_INTEL_DMIC_SAMPLE_RATE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, fifo_fs_a), 0}, + {SOF_TKN_INTEL_DMIC_DUTY_MIN, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_min), 0}, + {SOF_TKN_INTEL_DMIC_DUTY_MAX, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_max), 0}, + {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, + num_pdm_active), 0}, + {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, fifo_bits_a), 0}, +}; + +/* + * DMIC PDM Tokens + * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token + * as it increments the index while parsing the array of pdm tokens + * and determines the correct offset + */ +static const struct sof_topology_token dmic_pdm_tokens[] = { + {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_SKEW, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew), + 0}, +}; + +/* HDA */ +static const struct sof_topology_token hda_tokens[] = { +}; + +static void sof_parse_uuid_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_soc_tplg_vendor_uuid_elem *elem; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->uuid[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* matched - now load token */ + tokens[j].get_token(elem, object, tokens[j].offset, + tokens[j].size); + } + } +} + +static void sof_parse_string_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_soc_tplg_vendor_string_elem *elem; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->string[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* matched - now load token */ + tokens[j].get_token(elem, object, tokens[j].offset, + tokens[j].size); + } + } +} + +static void sof_parse_word_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_vendor_value_elem *elem; + size_t size = sizeof(struct sof_ipc_dai_dmic_pdm_ctrl); + int i, j; + u32 offset; + u32 *index = NULL; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->value[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT)) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* pdm config array index */ + if (sdev->private) + index = (u32 *)sdev->private; + + /* matched - determine offset */ + switch (tokens[j].token) { + case SOF_TKN_INTEL_DMIC_PDM_CTRL_ID: + + /* inc number of pdm array index */ + if (index) + (*index)++; + /* fallthrough */ + case SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable: + case SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable: + case SOF_TKN_INTEL_DMIC_PDM_POLARITY_A: + case SOF_TKN_INTEL_DMIC_PDM_POLARITY_B: + case SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE: + case SOF_TKN_INTEL_DMIC_PDM_SKEW: + + /* check if array index is valid */ + if (!index || *index == 0) { + dev_err(sdev->dev, + "error: invalid array offset\n"); + continue; + } else { + /* offset within the pdm config array */ + offset = size * (*index - 1); + } + break; + default: + offset = 0; + break; + } + + /* load token */ + tokens[j].get_token(elem, object, + offset + tokens[j].offset, + tokens[j].size); + } + } +} + +static int sof_parse_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + int priv_size) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + int asize; + + while (priv_size > 0) { + asize = le32_to_cpu(array->size); + + /* validate asize */ + if (asize < 0) { /* FIXME: A zero-size array makes no sense */ + dev_err(sdev->dev, "error: invalid array size 0x%x\n", + asize); + return -EINVAL; + } + + /* make sure there is enough data before parsing */ + priv_size -= asize; + if (priv_size < 0) { + dev_err(sdev->dev, "error: invalid array size 0x%x\n", + asize); + return -EINVAL; + } + + /* call correct parser depending on type */ + switch (le32_to_cpu(array->type)) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + sof_parse_uuid_tokens(scomp, object, tokens, count, + array); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + sof_parse_string_tokens(scomp, object, tokens, count, + array); + break; + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + sof_parse_word_tokens(scomp, object, tokens, count, + array); + break; + default: + dev_err(sdev->dev, "error: unknown token type %d\n", + array->type); + return -EINVAL; + } + + /* next array */ + array = (void *)array + asize; + } + return 0; +} + +static void sof_dbg_comp_config(struct snd_soc_component *scomp, + struct sof_ipc_comp_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + + dev_dbg(sdev->dev, " config: periods snk %d src %d fmt %d\n", + config->periods_sink, config->periods_source, + config->frame_fmt); +} + +/* external kcontrol init - used for any driver specific init */ +static int sof_control_load(struct snd_soc_component *scomp, int index, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dobj *dobj = NULL; + struct snd_sof_control *scontrol; + int ret = -EINVAL; + + dev_dbg(sdev->dev, "tplg: load control type %d name : %s\n", + hdr->type, hdr->name); + + scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); + if (!scontrol) + return -ENOMEM; + + scontrol->sdev = sdev; + mutex_init(&scontrol->mutex); + + switch (le32_to_cpu(hdr->ops.info)) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + sm = (struct soc_mixer_control *)kc->private_value; + dobj = &sm->dobj; + ret = sof_control_load_volume(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + dobj = &sbe->dobj; + ret = sof_control_load_bytes(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_PIN: + default: + dev_warn(sdev->dev, "control type not supported %d:%d:%d\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info); + kfree(scontrol); + return 0; + } + + dobj->private = scontrol; + list_add(&scontrol->list, &sdev->kcontrol_list); + return ret; +} + +static int sof_control_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_free fcomp; + struct snd_sof_control *scontrol = dobj->private; + + dev_dbg(sdev->dev, "tplg: unload control name : %s\n", scomp->name); + + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE; + fcomp.hdr.size = sizeof(fcomp); + fcomp.id = scontrol->comp_id; + + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + NULL, 0); +} + +/* + * DAI Topology + */ + +static int sof_connect_dai_widget(struct snd_soc_component *scomp, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw, + struct snd_sof_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_card *card = scomp->card; + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + dev_vdbg(sdev->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", + w->name, w->sname, rtd->dai_link->stream_name); + + if (!w->sname || !rtd->dai_link->stream_name) + continue; + + /* does stream match DAI link ? */ + if (strcmp(w->sname, rtd->dai_link->stream_name)) + continue; + + switch (w->id) { + case snd_soc_dapm_dai_out: + rtd->cpu_dai->capture_widget = w; + if (dai) + dai->name = rtd->dai_link->name; + dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + w->name, rtd->dai_link->name); + break; + case snd_soc_dapm_dai_in: + rtd->cpu_dai->playback_widget = w; + if (dai) + dai->name = rtd->dai_link->name; + dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + w->name, rtd->dai_link->name); + break; + default: + break; + } + } + + /* check we have a connection */ + if (!dai->name) { + dev_err(sdev->dev, "error: can't connect DAI %s stream %s\n", + w->name, w->sname); + return -EINVAL; + } + + return 0; +} + +static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r, + struct snd_sof_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_dai comp_dai; + int ret; + + /* configure dai IPC message */ + memset(&comp_dai, 0, sizeof(comp_dai)); + comp_dai.comp.hdr.size = sizeof(comp_dai); + comp_dai.comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + comp_dai.comp.id = swidget->comp_id; + comp_dai.comp.type = SOF_COMP_DAI; + comp_dai.comp.pipeline_id = index; + comp_dai.config.hdr.size = sizeof(comp_dai.config); + + ret = sof_parse_tokens(scomp, &comp_dai, dai_tokens, + ARRAY_SIZE(dai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + ret = sof_parse_tokens(scomp, &comp_dai.config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dai.cfg tokens failed %d\n", + private->size); + return ret; + } + + dev_dbg(sdev->dev, "dai %s: type %d index %d\n", + swidget->widget->name, comp_dai.type, comp_dai.dai_index); + sof_dbg_comp_config(scomp, &comp_dai.config); + + ret = sof_ipc_tx_message(sdev->ipc, comp_dai.comp.hdr.cmd, + &comp_dai, sizeof(comp_dai), r, sizeof(*r)); + + if (ret == 0 && dai) { + dai->sdev = sdev; + memcpy(&dai->comp_dai, &comp_dai, sizeof(comp_dai)); + } + + return ret; +} + +/* + * Buffer topology + */ + +static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* configure dai IPC message */ + buffer->comp.hdr.size = sizeof(*buffer); + buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW; + buffer->comp.id = swidget->comp_id; + buffer->comp.type = SOF_COMP_BUFFER; + buffer->comp.pipeline_id = index; + + ret = sof_parse_tokens(scomp, buffer, buffer_tokens, + ARRAY_SIZE(buffer_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse buffer tokens failed %d\n", + private->size); + kfree(buffer); + return ret; + } + + dev_dbg(sdev->dev, "buffer %s: size %d caps 0x%x\n", + swidget->widget->name, buffer->size, buffer->caps); + + swidget->private = (void *)buffer; + + ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, + sizeof(*buffer), r, sizeof(*r)); + if (ret < 0) { + dev_err(sdev->dev, "error: buffer %s load failed\n", + swidget->widget->name); + kfree(buffer); + } + + return ret; +} + +/* + * PCM Topology + */ + +static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + enum sof_ipc_stream_direction dir, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_host *host; + int ret; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + /* configure mixer IPC message */ + host->comp.hdr.size = sizeof(*host); + host->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + host->comp.id = swidget->comp_id; + host->comp.type = SOF_COMP_HOST; + host->comp.pipeline_id = index; + host->direction = dir; + host->config.hdr.size = sizeof(host->config); + + ret = sof_parse_tokens(scomp, host, pcm_tokens, + ARRAY_SIZE(pcm_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse host tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &host->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse host.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "loaded host %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &host->config); + + swidget->private = (void *)host; + + ret = sof_ipc_tx_message(sdev->ipc, host->comp.hdr.cmd, host, + sizeof(*host), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(host); + return ret; +} + +/* + * Pipeline Topology + */ +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r) +{ + struct sof_ipc_pm_core_config pm_core_config; + int ret = 0; + + ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, + sizeof(*pipeline), r, sizeof(*r)); + if (ret < 0) { + dev_err(sdev->dev, "error: load pipeline ipc failure\n"); + return ret; + } + + /* power up the core that this pipeline is scheduled on */ + ret = snd_sof_dsp_core_power_up(sdev, 1 << pipeline->core); + if (ret < 0) { + dev_err(sdev->dev, "error: powering up pipeline schedule core %d\n", + pipeline->core); + return ret; + } + + /* + * Now notify DSP that the core that this pipeline is scheduled on + * has been powered up + */ + memset(&pm_core_config, 0, sizeof(pm_core_config)); + pm_core_config.enable_mask = 1 << pipeline->core; + + /* configure CORE_ENABLE ipc message */ + pm_core_config.hdr.size = sizeof(pm_core_config); + pm_core_config.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE; + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, pm_core_config.hdr.cmd, + &pm_core_config, sizeof(pm_core_config), + &pm_core_config, sizeof(pm_core_config)); + if (ret < 0) + dev_err(sdev->dev, "error: core enable ipc failure\n"); + + return ret; +} + +static int sof_widget_load_pipeline(struct snd_soc_component *scomp, + int index, struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_widget *comp_swidget; + int ret; + + pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return -ENOMEM; + + /* configure dai IPC message */ + pipeline->hdr.size = sizeof(*pipeline); + pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW; + pipeline->pipeline_id = index; + pipeline->comp_id = swidget->comp_id; + + /* component at start of pipeline is our stream id */ + comp_swidget = snd_sof_find_swidget(sdev, tw->sname); + if (!comp_swidget) { + dev_err(sdev->dev, "error: widget %s refers to non existent widget %s\n", + tw->name, tw->sname); + ret = -EINVAL; + goto err; + } + + pipeline->sched_id = comp_swidget->comp_id; + + dev_dbg(sdev->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n", + pipeline->pipeline_id, pipeline->comp_id, pipeline->sched_id); + + ret = sof_parse_tokens(scomp, pipeline, sched_tokens, + ARRAY_SIZE(sched_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse pipeline tokens failed %d\n", + private->size); + goto err; + } + + dev_dbg(sdev->dev, "pipeline %s: deadline %d pri %d mips %d core %d frames %d\n", + swidget->widget->name, pipeline->deadline, pipeline->priority, + pipeline->period_mips, pipeline->core, pipeline->frames_per_sched); + + swidget->private = (void *)pipeline; + + /* send ipc's to create pipeline comp and power up schedule core */ + ret = sof_load_pipeline_ipc(sdev, pipeline, r); + if (ret >= 0) + return ret; +err: + kfree(pipeline); + return ret; +} + +/* + * Mixer topology + */ + +static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_mixer *mixer; + int ret; + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + + /* configure mixer IPC message */ + mixer->comp.hdr.size = sizeof(*mixer); + mixer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + mixer->comp.id = swidget->comp_id; + mixer->comp.type = SOF_COMP_MIXER; + mixer->comp.pipeline_id = index; + mixer->config.hdr.size = sizeof(mixer->config); + + ret = sof_parse_tokens(scomp, &mixer->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse mixer.cfg tokens failed %d\n", + private->size); + kfree(mixer); + return ret; + } + + sof_dbg_comp_config(scomp, &mixer->config); + + swidget->private = (void *)mixer; + + ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer, + sizeof(*mixer), r, sizeof(*r)); + if (ret < 0) + kfree(mixer); + + return ret; +} + +/* + * PGA Topology + */ + +static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_volume *volume; + struct snd_soc_dapm_widget *widget = swidget->widget; + const struct snd_kcontrol_new *kc = NULL; + struct soc_mixer_control *sm; + struct snd_sof_control *scontrol; + struct sof_ipc_ctrl_data *cdata; + const unsigned int *p; + int ret, tlv[TLV_ITEMS]; + unsigned int i; + + volume = kzalloc(sizeof(*volume), GFP_KERNEL); + if (!volume) + return -ENOMEM; + + if (le32_to_cpu(tw->num_kcontrols) != 1) { + dev_err(sdev->dev, "error: invalid kcontrol count %d for volume\n", + tw->num_kcontrols); + ret = -EINVAL; + goto err; + } + + /* set up volume gain tables for kcontrol */ + kc = &widget->kcontrol_news[0]; + sm = (struct soc_mixer_control *)kc->private_value; + + /* get volume control */ + scontrol = sm->dobj.private; + + /* set cmd for pga kcontrol */ + scontrol->cmd = SOF_CTRL_CMD_VOLUME; + + /* get topology tlv data */ + p = kc->tlv.p; + + /* extract tlv data */ + if (get_tlv_data(p, tlv) < 0) { + dev_err(sdev->dev, "error: invalid TLV data\n"); + ret = -EINVAL; + goto err; + } + + /* set up volume table */ + ret = set_up_volume_table(scontrol, tlv, sm->max + 1); + if (ret < 0) { + dev_err(sdev->dev, "error: setting up volume table\n"); + goto err; + } + + /* configure dai IPC message */ + volume->comp.hdr.size = sizeof(*volume); + volume->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + volume->comp.id = swidget->comp_id; + volume->comp.type = SOF_COMP_VOLUME; + volume->comp.pipeline_id = index; + volume->config.hdr.size = sizeof(volume->config); + + ret = sof_parse_tokens(scomp, volume, volume_tokens, + ARRAY_SIZE(volume_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse volume tokens failed %d\n", + private->size); + goto err; + } + ret = sof_parse_tokens(scomp, &volume->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse volume.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + /* set default volume values to 0dB in control */ + cdata = scontrol->control_data; + for (i = 0; i < scontrol->num_channels; i++) { + cdata->chanv[i].channel = i; + cdata->chanv[i].value = VOL_ZERO_DB; + } + + sof_dbg_comp_config(scomp, &volume->config); + + swidget->private = (void *)volume; + + ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume, + sizeof(*volume), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(volume); + return ret; +} + +/* + * SRC Topology + */ + +static int sof_widget_load_src(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_src *src; + int ret; + + src = kzalloc(sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + /* configure mixer IPC message */ + src->comp.hdr.size = sizeof(*src); + src->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + src->comp.id = swidget->comp_id; + src->comp.type = SOF_COMP_SRC; + src->comp.pipeline_id = index; + src->config.hdr.size = sizeof(src->config); + + ret = sof_parse_tokens(scomp, src, src_tokens, + ARRAY_SIZE(src_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse src tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &src->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse src.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "src %s: source rate %d sink rate %d\n", + swidget->widget->name, src->source_rate, src->sink_rate); + sof_dbg_comp_config(scomp, &src->config); + + swidget->private = (void *)src; + + ret = sof_ipc_tx_message(sdev->ipc, src->comp.hdr.cmd, src, + sizeof(*src), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(src); + return ret; +} + +/* + * Signal Generator Topology + */ + +static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_tone *tone; + int ret; + + tone = kzalloc(sizeof(*tone), GFP_KERNEL); + if (!tone) + return -ENOMEM; + + /* configure mixer IPC message */ + tone->comp.hdr.size = sizeof(*tone); + tone->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + tone->comp.id = swidget->comp_id; + tone->comp.type = SOF_COMP_TONE; + tone->comp.pipeline_id = index; + tone->config.hdr.size = sizeof(tone->config); + + ret = sof_parse_tokens(scomp, tone, tone_tokens, + ARRAY_SIZE(tone_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse tone tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + ret = sof_parse_tokens(scomp, &tone->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse tone.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n", + swidget->widget->name, tone->frequency, tone->amplitude); + sof_dbg_comp_config(scomp, &tone->config); + + swidget->private = (void *)tone; + + ret = sof_ipc_tx_message(sdev->ipc, tone->comp.hdr.cmd, tone, + sizeof(*tone), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(tone); + return ret; +} + +static int sof_effect_fir_load(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) + +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct snd_sof_control *scontrol = NULL; + struct snd_soc_dapm_widget *widget = swidget->widget; + const struct snd_kcontrol_new *kc = NULL; + struct soc_bytes_ext *sbe; + struct sof_abi_hdr *pdata = NULL; + struct sof_ipc_comp_eq_fir *fir; + size_t ipc_size = 0, fir_data_size = 0; + int ret; + + /* get possible eq controls */ + kc = &widget->kcontrol_news[0]; + if (kc) { + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + } + + /* + * Check if there's eq parameters in control's private member and set + * data size accordingly. If there's no parameters eq will use defaults + * in firmware (which in this case is passthrough). + */ + if (scontrol && scontrol->cmd == SOF_CTRL_CMD_BINARY) { + pdata = scontrol->control_data->data; + if (pdata->size > 0 && pdata->magic == SOF_ABI_MAGIC) + fir_data_size = pdata->size; + } + + ipc_size = sizeof(struct sof_ipc_comp_eq_fir) + + le32_to_cpu(private->size) + + fir_data_size; + + fir = kzalloc(ipc_size, GFP_KERNEL); + if (!fir) + return -ENOMEM; + + /* configure fir IPC message */ + fir->comp.hdr.size = ipc_size; + fir->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + fir->comp.id = swidget->comp_id; + fir->comp.type = SOF_COMP_EQ_FIR; + fir->comp.pipeline_id = index; + fir->config.hdr.size = sizeof(fir->config); + + + ret = sof_parse_tokens(scomp, &fir->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse fir.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &fir->config); + + /* we have a private data found in control, so copy it */ + if (fir_data_size > 0) { + memcpy(&fir->data, pdata->data, pdata->size); + fir->size = fir_data_size; + } + + swidget->private = (void *)fir; + + ret = sof_ipc_tx_message(sdev->ipc, fir->comp.hdr.cmd, fir, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(fir); + return ret; +} + +static int sof_effect_iir_load(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct snd_soc_dapm_widget *widget = swidget->widget; + const struct snd_kcontrol_new *kc = NULL; + struct soc_bytes_ext *sbe; + struct snd_sof_control *scontrol = NULL; + struct sof_abi_hdr *pdata = NULL; + struct sof_ipc_comp_eq_iir *iir; + size_t ipc_size = 0, iir_data_size = 0; + int ret; + + /* get possible eq controls */ + kc = &widget->kcontrol_news[0]; + if (kc) { + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + } + + /* + * Check if there's eq parameters in control's private member and set + * data size accordingly. If there's no parameters eq will use defaults + * in firmware (which in this case is passthrough). + */ + if (scontrol && scontrol->cmd == SOF_CTRL_CMD_BINARY) { + pdata = scontrol->control_data->data; + if (pdata->size > 0 && pdata->magic == SOF_ABI_MAGIC) + iir_data_size = pdata->size; + } + + ipc_size = sizeof(struct sof_ipc_comp_eq_iir) + + le32_to_cpu(private->size) + + iir_data_size; + + iir = kzalloc(ipc_size, GFP_KERNEL); + if (!iir) + return -ENOMEM; + + /* configure iir IPC message */ + iir->comp.hdr.size = ipc_size; + iir->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + iir->comp.id = swidget->comp_id; + iir->comp.type = SOF_COMP_EQ_IIR; + iir->comp.pipeline_id = index; + iir->config.hdr.size = sizeof(iir->config); + + ret = sof_parse_tokens(scomp, &iir->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse iir.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &iir->config); + + /* we have a private data found in control, so copy it */ + if (iir_data_size > 0) { + memcpy(&iir->data, pdata->data, pdata->size); + iir->size = iir_data_size; + } + + swidget->private = (void *)iir; + + ret = sof_ipc_tx_message(sdev->ipc, iir->comp.hdr.cmd, iir, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(iir); + return ret; +} + +/* + * Effect Topology + */ + +static int sof_widget_load_effect(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_effect config; + int ret; + + /* check we have some tokens - we need at least effect type */ + if (le32_to_cpu(private->size) == 0) { + dev_err(sdev->dev, "error: effect tokens not found\n"); + return -EINVAL; + } + + memset(&config, 0, sizeof(config)); + + /* get the effect token */ + ret = sof_parse_tokens(scomp, &config, effect_tokens, + ARRAY_SIZE(effect_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse effect tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* now load effect specific data and send IPC */ + switch (config.type) { + case SOF_EFFECT_INTEL_EQFIR: + ret = sof_effect_fir_load(scomp, index, swidget, tw, r); + break; + case SOF_EFFECT_INTEL_EQIIR: + ret = sof_effect_iir_load(scomp, index, swidget, tw, r); + break; + default: + dev_err(sdev->dev, "error: invalid effect type %d\n", + config.type); + ret = -EINVAL; + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "error: effect loading failed\n"); + return ret; + } + + return 0; +} + +/* external widget init - used for any driver specific init */ +static int sof_widget_ready(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + struct snd_sof_dai *dai; + struct sof_ipc_comp_reply reply; + struct snd_sof_control *scontrol = NULL; + int ret = 0; + + swidget = kzalloc(sizeof(*swidget), GFP_KERNEL); + if (!swidget) + return -ENOMEM; + + swidget->sdev = sdev; + swidget->widget = w; + swidget->comp_id = sdev->next_comp_id++; + swidget->complete = 0; + swidget->id = w->id; + swidget->pipeline_id = index; + swidget->private = NULL; + memset(&reply, 0, sizeof(reply)); + + dev_dbg(sdev->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n", + swidget->comp_id, index, swidget->id, tw->name, + strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 + ? tw->sname : "none"); + + /* handle any special case widgets */ + switch (w->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = kzalloc(sizeof(*dai), GFP_KERNEL); + if (!dai) { + kfree(swidget); + return -ENOMEM; + } + + ret = sof_widget_load_dai(scomp, index, swidget, tw, &reply, + dai); + if (ret == 0) { + sof_connect_dai_widget(scomp, w, tw, dai); + list_add(&dai->list, &sdev->dai_list); + swidget->private = dai; + } else { + kfree(dai); + } + break; + case snd_soc_dapm_mixer: + ret = sof_widget_load_mixer(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_pga: + ret = sof_widget_load_pga(scomp, index, swidget, tw, &reply); + /* Find scontrol for this pga and set readback offset*/ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + if (scontrol->comp_id == swidget->comp_id) { + scontrol->readback_offset = reply.offset; + break; + } + } + break; + case snd_soc_dapm_buffer: + ret = sof_widget_load_buffer(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_scheduler: + ret = sof_widget_load_pipeline(scomp, index, swidget, tw, + &reply); + break; + case snd_soc_dapm_aif_out: + ret = sof_widget_load_pcm(scomp, index, swidget, + SOF_IPC_STREAM_CAPTURE, tw, &reply); + break; + case snd_soc_dapm_aif_in: + ret = sof_widget_load_pcm(scomp, index, swidget, + SOF_IPC_STREAM_PLAYBACK, tw, &reply); + break; + case snd_soc_dapm_src: + ret = sof_widget_load_src(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_siggen: + ret = sof_widget_load_siggen(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_effect: + ret = sof_widget_load_effect(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_mux: + case snd_soc_dapm_demux: + case snd_soc_dapm_switch: + case snd_soc_dapm_dai_link: + case snd_soc_dapm_kcontrol: + default: + dev_warn(sdev->dev, "warning: widget type %d name %s not handled\n", + swidget->id, tw->name); + break; + } + + /* check IPC reply */ + if (ret < 0 || reply.rhdr.error < 0) { + dev_err(sdev->dev, + "error: DSP failed to add widget id %d type %d name : %s stream %s reply %d\n", + tw->shift, swidget->id, tw->name, + strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 + ? tw->sname : "none", reply.rhdr.error); + kfree(swidget); + return ret; + } + + w->dobj.private = swidget; + mutex_init(&swidget->mutex); + list_add(&swidget->list, &sdev->widget_list); + return ret; +} + +static int sof_widget_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct snd_kcontrol_new *kc = NULL; + struct snd_soc_dapm_widget *widget; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_control *scontrol; + struct snd_sof_widget *swidget; + struct soc_mixer_control *sm; + struct snd_sof_dai *dai; + int ret = 0; + + swidget = dobj->private; + if (!swidget) + return 0; + + widget = swidget->widget; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = (struct snd_sof_dai *)swidget->private; + + if (dai) { + /* free dai config */ + kfree(dai->dai_config); + list_del(&dai->list); + } + break; + case snd_soc_dapm_scheduler: + + /* power down the pipeline schedule core */ + pipeline = (struct sof_ipc_pipe_new *)swidget->private; + ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); + if (ret < 0) + dev_err(sdev->dev, "error: powering down pipeline schedule core %d\n", + pipeline->core); + break; + case snd_soc_dapm_pga: + + /* get volume kcontrol */ + kc = &widget->kcontrol_news[0]; + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + + /* free volume table */ + kfree(scontrol->volume_table); + break; + default: + break; + } + + /* free private value */ + kfree(swidget->private); + + /* remove and free swidget object */ + list_del(&swidget->list); + kfree(swidget); + + return ret; +} + +/* + * DAI HW configuration. + */ + +/* FE DAI - used for any driver specific init */ +static int sof_dai_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_driver *dai_drv, + struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pcm *spcm; + + /* don't need to do anything for BEs atm */ + if (!pcm) + return 0; + + spcm = kzalloc(sizeof(*spcm), GFP_KERNEL); + if (!spcm) + return -ENOMEM; + + spcm->sdev = sdev; + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = COMP_ID_UNASSIGNED; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = COMP_ID_UNASSIGNED; + if (pcm) { + spcm->pcm = *pcm; + dev_dbg(sdev->dev, "tplg: load pcm %s\n", pcm->dai_name); + } + dai_drv->dobj.private = spcm; + mutex_init(&spcm->mutex); + list_add(&spcm->list, &sdev->pcm_list); + + return 0; +} + +static int sof_dai_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_pcm *spcm = dobj->private; + + list_del(&spcm->list); + kfree(spcm); + + return 0; +} + +static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + /* clock directions wrt codec */ + if (hw_config->bclk_master == SND_SOC_TPLG_BCLK_CM) { + /* codec is bclk master */ + if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM) + config->format |= SOF_DAI_FMT_CBM_CFM; + else + config->format |= SOF_DAI_FMT_CBM_CFS; + } else { + /* codec is bclk slave */ + if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM) + config->format |= SOF_DAI_FMT_CBS_CFM; + else + config->format |= SOF_DAI_FMT_CBS_CFS; + } + + /* inverted clocks ? */ + if (hw_config->invert_bclk) { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_IB_IF; + else + config->format |= SOF_DAI_FMT_IB_NF; + } else { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_NB_IF; + else + config->format |= SOF_DAI_FMT_NB_NF; + } +} + +/* set config for all DAI's with name matching the link name */ +static int sof_set_dai_config(struct snd_sof_dev *sdev, u32 size, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dai *dai; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name) + continue; + + if (strcmp(link->name, dai->name) == 0) { + dai->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!dai->dai_config) + return -ENOMEM; + } + } + + return 0; +} + +static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->ssp, 0, sizeof(struct sof_ipc_dai_ssp_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->ssp, ssp_tokens, + ARRAY_SIZE(ssp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse ssp tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->ssp.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->ssp.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->ssp.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->ssp.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->ssp.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->ssp.mclk_direction = hw_config->mclk_direction; + config->ssp.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->ssp.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_dbg(sdev->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d\n", + config->dai_index, config->format, + config->ssp.mclk_rate, config->ssp.bclk_rate, + config->ssp.fsync_rate, config->ssp.sample_valid_bits, + config->ssp.tdm_slot_width, config->ssp.tdm_slots, + config->ssp.mclk_id); + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for SSP%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for SSP%d\n", + config->dai_index); + + return ret; +} + +static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_dai_config *ipc_config; + struct sof_ipc_reply reply; + u32 size; + int ret, j; + + /* + * config is only used for the common params in dmic_params structure + * that does not include the PDM controller config array + * Set the common params to 0. + */ + memset(&config->dmic, 0, sizeof(struct sof_ipc_dai_dmic_params)); + + /* get DMIC tokens */ + ret = sof_parse_tokens(scomp, &config->dmic, dmic_tokens, + ARRAY_SIZE(dmic_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dmic tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* + * allocate memory for dmic dai config accounting for the + * variable number of active pdm controllers + * This will be the ipc payload for setting dai config + */ + size = sizeof(*config) + sizeof(struct sof_ipc_dai_dmic_pdm_ctrl) * + config->dmic.num_pdm_active; + + ipc_config = kzalloc(size, GFP_KERNEL); + if (!ipc_config) { + dev_err(sdev->dev, "error: allocating memory for config\n"); + return -ENOMEM; + } + + /* copy the common dai config and dmic params */ + memcpy(ipc_config, config, sizeof(*config)); + + /* + * alloc memory for private member + * Used to track the pdm config array index currently being parsed + */ + sdev->private = kzalloc(sizeof(u32), GFP_KERNEL); + if (!sdev->private) { + kfree(ipc_config); + return -ENOMEM; + } + + /* get DMIC PDM tokens */ + ret = sof_parse_tokens(scomp, &ipc_config->dmic.pdm[0], dmic_pdm_tokens, + ARRAY_SIZE(dmic_pdm_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dmic pdm tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + /* set IPC header size */ + ipc_config->hdr.size = size; + + /* debug messages */ + dev_dbg(sdev->dev, "tplg: config DMIC%d driver version %d\n", + ipc_config->dai_index, ipc_config->dmic.driver_ipc_version); + dev_dbg(sdev->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", + ipc_config->dmic.pdmclk_min, ipc_config->dmic.pdmclk_max, + ipc_config->dmic.duty_min); + dev_dbg(sdev->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", + ipc_config->dmic.duty_max, ipc_config->dmic.fifo_fs_a, + ipc_config->dmic.num_pdm_active); + dev_dbg(sdev->dev, "fifo word length %hd\n", + ipc_config->dmic.fifo_bits_a); + + for (j = 0; j < ipc_config->dmic.num_pdm_active; j++) { + dev_dbg(sdev->dev, "pdm %hd mic a %hd mic b %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].enable_mic_a, + ipc_config->dmic.pdm[j].enable_mic_b); + dev_dbg(sdev->dev, "pdm %hd polarity a %hd polarity b %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].polarity_mic_a, + ipc_config->dmic.pdm[j].polarity_mic_b); + dev_dbg(sdev->dev, "pdm %hd clk_edge %hd skew %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].clk_edge, + ipc_config->dmic.pdm[j].skew); + } + + /* TODO: check if fifo_b word length is needed */ + ipc_config->dmic.fifo_bits_b = ipc_config->dmic.fifo_bits_a; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + ipc_config->hdr.cmd, ipc_config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for DMIC%d\n", + config->dai_index); + goto err; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, ipc_config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for DMIC%d\n", + config->dai_index); + +err: + kfree(sdev->private); + kfree(ipc_config); + + return ret; +} + +static int sof_link_hda_process(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config, + int slot, + int direction) +{ + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + struct snd_sof_dai *dai; + int found = 0; + int ret; + + /* for hda link, playback and capture are supported by different + * dai in FW. Here get the dai_index of each dai and send config + * to FW. In FW, each dai sets config by dai_index + */ + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name) + continue; + + if (strcmp(link->name, dai->name) == 0 && + dai->comp_dai.direction == direction) { + config->dai_index = dai->comp_dai.dai_index; + found = 1; + break; + } + } + + if (!found) { + dev_err(sdev->dev, "error: failed to find dai %s", link->name); + return -EINVAL; + } + + config->hda.link_dma_ch = slot; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + return ret; +} + +static int sof_link_hda_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link_component dai_component; + struct snd_soc_tplg_private *private = &cfg->priv; + struct snd_soc_dai *dai; + + u32 size = sizeof(*config); + u32 tx_num = 0; + u32 tx_slot = 0; + u32 rx_num = 0; + u32 rx_slot = 0; + int ret; + + /* init IPC */ + memset(&dai_component, 0, sizeof(dai_component)); + memset(&config->hda, 0, sizeof(struct sof_ipc_dai_hda_params)); + config->hdr.size = size; + + /* get any bespoke DAI tokens */ + ret = sof_parse_tokens(scomp, config, hda_tokens, + ARRAY_SIZE(hda_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse hda tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s", + dai_component.dai_name); + return -EINVAL; + } + + if (link->dpcm_playback) + tx_num = 1; + + if (link->dpcm_capture) + rx_num = 1; + + ret = snd_soc_dai_get_channel_map(dai, &tx_num, &tx_slot, + &rx_num, &rx_slot); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to get dma channel for HDA%d\n", + config->dai_index); + + return ret; + } + + /* for hda link, playback and capture are supported by different + * dai in FW. Here send dai config according to capability of dai. + */ + if (link->dpcm_playback) { + ret = sof_link_hda_process(sdev, link, config, tx_slot, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for playback dai HDA%d\n", + config->dai_index); + + return ret; + } + } + + if (link->dpcm_capture) { + ret = sof_link_hda_process(sdev, link, config, rx_slot, + SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for capture dai HDA%d\n", + config->dai_index); + + return ret; + } + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) { + if (link->dpcm_playback) + dev_err(sdev->dev, "error: failed to save DAI config for playback HDA%d\n", + config->dai_index); + + if (link->dpcm_capture) + dev_err(sdev->dev, "error: failed to save DAI config for capture HDA%d\n", + config->dai_index); + } + + return ret; +} + +/* DAI link - used for any driver specific init */ +static int sof_link_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_dai_config config; + struct snd_soc_tplg_hw_config *hw_config; + int ret = 0; + + link->platform_name = "sof-audio"; + link->nonatomic = true; + + /* send BE configurations to DSP */ + if (!link->no_pcm) + return 0; + + /* only support 1 config atm */ + if (le32_to_cpu(cfg->num_hw_configs) != 1) { + dev_err(sdev->dev, "error: unexpected DAI config count %d\n", + le32_to_cpu(cfg->num_hw_configs)); + return -EINVAL; + } + + /* check we have some tokens - we need at least DAI type */ + if (le32_to_cpu(private->size) == 0) { + dev_err(sdev->dev, "error: expected tokens for DAI, none found\n"); + return -EINVAL; + } + + memset(&config, 0, sizeof(config)); + + /* get any common DAI tokens */ + ret = sof_parse_tokens(scomp, &config, dai_link_tokens, + ARRAY_SIZE(dai_link_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse link tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* configure dai IPC message */ + hw_config = &cfg->hw_config[0]; + + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.format = le32_to_cpu(hw_config->fmt); + + /* now load DAI specific data and send IPC - type comes from token */ + switch (config.type) { + case SOF_DAI_INTEL_SSP: + ret = sof_link_ssp_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_INTEL_DMIC: + ret = sof_link_dmic_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_load(scomp, index, link, cfg, hw_config, + &config); + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", config.type); + ret = -EINVAL; + break; + } + if (ret < 0) + return ret; + + return 0; +} + +static int sof_link_hda_unload(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link) +{ + struct snd_soc_dai_link_component dai_component; + struct snd_soc_dai *dai; + int ret = 0; + + memset(&dai_component, 0, sizeof(dai_component)); + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s", + dai_component.dai_name); + return -EINVAL; + } + + /* + * FIXME: this call to hw_free is mainly to release the link DMA ID. + * This is abusing the API and handling SOC internals is not + * recommended. This part will be reworked. + */ + if (dai->driver->ops->hw_free) + ret = dai->driver->ops->hw_free(NULL, dai); + if (ret < 0) + dev_err(sdev->dev, "error: failed to free hda resource for %s\n", + link->name); + + return ret; +} + +static int sof_link_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link *link = + container_of(dobj, struct snd_soc_dai_link, dobj); + + struct snd_sof_dai *sof_dai = NULL; + int ret = 0; + + list_for_each_entry(sof_dai, &sdev->dai_list, list) { + if (!sof_dai->name) + continue; + + if (strcmp(link->name, sof_dai->name) == 0) + goto found; + } + + dev_err(sdev->dev, "error: failed to find dai %s", link->name); + return -EINVAL; +found: + + switch (sof_dai->dai_config->type) { + case SOF_DAI_INTEL_SSP: + case SOF_DAI_INTEL_DMIC: + /* no resource needs to be released for SSP and DMIC */ + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_unload(sdev, link); + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", + sof_dai->dai_config->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/* bind PCM ID to host component ID */ +static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, + const char *host) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *host_widget; + + host_widget = snd_sof_find_swidget(sdev, (char *)host); + if (!host_widget) { + dev_err(sdev->dev, "error: can't find host component %s\n", + host); + return -ENODEV; + } + + switch (host_widget->id) { + case snd_soc_dapm_aif_in: + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = + host_widget->comp_id; + break; + case snd_soc_dapm_aif_out: + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = + host_widget->comp_id; + break; + default: + dev_err(sdev->dev, "error: host is wrong type %d\n", + host_widget->id); + return -EINVAL; + } + + return 0; +} + +/* Used for free route in topology free stage */ +static void sof_route_remove(struct snd_soc_dapm_route *route) +{ + if (!route) + return; + + kfree(route->source); + kfree(route->sink); + kfree(route->control); +} + +/* DAI link - used for any driver specific init */ +static int sof_route_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_route *route) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_pipe_comp_connect *connect; + struct snd_sof_widget *source_swidget, *sink_swidget; + struct snd_sof_pcm *spcm; + struct snd_sof_route *sroute; + struct sof_ipc_reply reply; + int ret = 0; + + /* allocate memory for sroute and connect */ + sroute = kzalloc(sizeof(*sroute), GFP_KERNEL); + if (!sroute) + return -ENOMEM; + + sroute->sdev = sdev; + + connect = kzalloc(sizeof(*connect), GFP_KERNEL); + if (!connect) { + kfree(sroute); + return -ENOMEM; + } + + connect->hdr.size = sizeof(*connect); + connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT; + + dev_dbg(sdev->dev, "sink %s control %s source %s\n", + route->sink, route->control ? route->control : "none", + route->source); + + /* source component */ + source_swidget = snd_sof_find_swidget(sdev, (char *)route->source); + if (!source_swidget) { + /* don't send any routes to DSP that include a driver PCM */ + spcm = snd_sof_find_spcm_name(sdev, (char *)route->source); + if (spcm) { + ret = spcm_bind(scomp, spcm, route->sink); + goto err; + } + + dev_err(sdev->dev, "error: source %s not found\n", + route->source); + ret = -EINVAL; + goto err; + } + + connect->source_id = source_swidget->comp_id; + + /* sink component */ + sink_swidget = snd_sof_find_swidget(sdev, (char *)route->sink); + if (!sink_swidget) { + /* don't send any routes to DSP that include a driver PCM */ + spcm = snd_sof_find_spcm_name(sdev, (char *)route->sink); + if (spcm) { + ret = spcm_bind(scomp, spcm, route->source); + goto err; + } + + dev_err(sdev->dev, "error: sink %s not found\n", + route->sink); + ret = -EINVAL; + goto err; + } + + connect->sink_id = sink_swidget->comp_id; + + /* Some virtual routes and widgets may been added in topology for + * compatibility. For virtual routes, both sink and source are not + * buffer. Since only buffer linked to component is supported by + * FW, others are reported as error, add check in route function, + * do not send it to FW when both source and sink are not buffer + */ + if (source_swidget->id != snd_soc_dapm_buffer && + sink_swidget->id != snd_soc_dapm_buffer) { + dev_dbg(sdev->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n", + route->source, route->sink); + ret = 0; + goto err; + } else { + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + + /* check IPC return value */ + if (ret < 0) { + dev_err(sdev->dev, "error: failed to add route sink %s control %s source %s\n", + route->sink, + route->control ? route->control : "none", + route->source); + goto err; + } + + /* check IPC reply */ + if (reply.error < 0) { + dev_err(sdev->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n", + route->sink, + route->control ? route->control : "none", + route->source, reply.error); + ret = reply.error; + goto err; + } + + sroute->route.source = kstrdup(route->source, GFP_KERNEL); + if (!sroute->route.source) + goto err; + + sroute->route.sink = kstrdup(route->sink, GFP_KERNEL); + if (!sroute->route.sink) { + kfree(sroute->route.source); + goto err; + } + + if (route->control) { + sroute->route.control = kstrdup(route->control, + GFP_KERNEL); + if (!sroute->route.control) { + kfree(sroute->route.source); + kfree(sroute->route.sink); + goto err; + } + } + + sroute->private = connect; + + /* add route to route list */ + list_add(&sroute->list, &sdev->route_list); + + return ret; + } + +err: + kfree(connect); + kfree(sroute); + return ret; +} + +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct sof_ipc_pipe_ready ready; + struct sof_ipc_reply reply; + int ret; + + dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", + swidget->widget->name, swidget->comp_id); + + memset(&ready, 0, sizeof(ready)); + ready.hdr.size = sizeof(ready); + ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; + ready.comp_id = swidget->comp_id; + + ret = sof_ipc_tx_message(sdev->ipc, + ready.hdr.cmd, &ready, sizeof(ready), &reply, + sizeof(reply)); + if (ret < 0) + return ret; + return 1; +} + +/* completion - called at completion of firmware loading */ +static void sof_complete(struct snd_soc_component *scomp) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + + /* some widget types require completion notificattion */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->complete) + continue; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(sdev, swidget); + break; + default: + break; + } + } +} + +/* manifest - optional to inform component of manifest */ +static int sof_manifest(struct snd_soc_component *scomp, int index, + struct snd_soc_tplg_manifest *man) +{ + /* not currently parsed */ + return 0; +} + +/* vendor specific kcontrol handlers available for binding */ +static const struct snd_soc_tplg_kcontrol_ops sof_io_ops[] = { + {SOF_TPLG_KCTL_VOL_ID, snd_sof_volume_get, snd_sof_volume_put}, + {SOF_TPLG_KCTL_ENUM_ID, snd_sof_enum_get, snd_sof_enum_put}, + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_get, snd_sof_bytes_put}, +}; + +/* vendor specific bytes ext handlers available for binding */ +static const struct snd_soc_tplg_bytes_ext_ops sof_bytes_ext_ops[] = { + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_ext_get, snd_sof_bytes_ext_put}, +}; + +static struct snd_soc_tplg_ops sof_tplg_ops = { + /* external kcontrol init - used for any driver specific init */ + .control_load = sof_control_load, + .control_unload = sof_control_unload, + + /* external kcontrol init - used for any driver specific init */ + .dapm_route_load = sof_route_load, + /* + * .dapm_route_unload is not currently used, will be needed when + * topology is changed + */ + + /* external widget init - used for any driver specific init */ + /* .widget_load is not currently used */ + .widget_ready = sof_widget_ready, + .widget_unload = sof_widget_unload, + + /* FE DAI - used for any driver specific init */ + .dai_load = sof_dai_load, + .dai_unload = sof_dai_unload, + + /* DAI link - used for any driver specific init */ + .link_load = sof_link_load, + .link_unload = sof_link_unload, + + /* completion - called at completion of firmware loading */ + .complete = sof_complete, + + /* manifest - optional to inform component of manifest */ + .manifest = sof_manifest, + + /* vendor specific kcontrol handlers available for binding */ + .io_ops = sof_io_ops, + .io_ops_count = ARRAY_SIZE(sof_io_ops), + + /* vendor specific bytes ext handlers available for binding */ + .bytes_ext_ops = sof_bytes_ext_ops, + .bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops), +}; + +int snd_sof_init_topology(struct snd_sof_dev *sdev, + struct snd_soc_tplg_ops *ops) +{ + /* TODO: support linked list of topologies */ + sdev->tplg_ops = ops; + return 0; +} +EXPORT_SYMBOL(snd_sof_init_topology); + +int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file) +{ + const struct firmware *fw; + struct snd_soc_tplg_hdr *hdr; + int ret; + + if (sdev->tplg_loaded) { + dev_err(sdev->dev, "error: topology already loaded ?\n"); + return -EINVAL; + } + + dev_dbg(sdev->dev, "loading topology:%s\n", file); + + ret = request_firmware(&fw, file, sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: tplg %s load failed with %d\n", + file, ret); + return ret; + } + + hdr = (struct snd_soc_tplg_hdr *)fw->data; + ret = snd_soc_tplg_component_load(sdev->component, + &sof_tplg_ops, fw, + SND_SOC_TPLG_INDEX_ALL); + if (ret < 0) { + dev_err(sdev->dev, "error: tplg component load failed %d\n", + ret); + ret = -EINVAL; + } + + sdev->tplg_loaded = true; + + release_firmware(fw); + return ret; +} +EXPORT_SYMBOL(snd_sof_load_topology); + +void snd_sof_free_topology(struct snd_sof_dev *sdev) +{ + struct snd_sof_route *sroute, *temp; + int ret; + + dev_dbg(sdev->dev, "free topology...\n"); + if (!sdev->tplg_loaded) { + dev_dbg(sdev->dev, "No topology loaded, nothing to free ...\n"); + return; + } + + /* remove routes */ + list_for_each_entry_safe(sroute, temp, &sdev->route_list, list) { + sof_route_remove(&sroute->route); + + /* free sroute and its private data */ + kfree(sroute->private); + kfree(sroute); + } + + ret = snd_soc_tplg_component_remove(sdev->component, + SND_SOC_TPLG_INDEX_ALL); + if (ret < 0) + dev_err(sdev->dev, + "error: tplg component free failed %d\n", ret); + + sdev->tplg_loaded = false; +} +EXPORT_SYMBOL(snd_sof_free_topology);
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
Signed-off-by: Pan Xiuli xiuli.pan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/trace.c | 295 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 sound/soc/sof/trace.c
diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c new file mode 100644 index 000000000000..9871a174f9ca --- /dev/null +++ b/sound/soc/sof/trace.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include "sof-priv.h" +#include "ops.h" + +static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + wait_queue_entry_t wait; + + /* + * If host offset is less than local pos, it means write pointer of + * host DMA buffer has been wrapped. We should output the trace data + * at the end of host DMA buffer at first. + */ + if (sdev->host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (sdev->host_offset > pos) + return sdev->host_offset - pos; + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&sdev->trace_sleep, &wait); + + if (signal_pending(current)) { + remove_wait_queue(&sdev->trace_sleep, &wait); + goto out; + } + + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + remove_wait_queue(&sdev->trace_sleep, &wait); + +out: + /* return bytes available for copy */ + if (sdev->host_offset < pos) + return buffer_size - pos; + + return sdev->host_offset - pos; +} + +static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry_buf *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + unsigned long rem; + loff_t lpos = *ppos; + size_t avail, buffer_size = dfse->size; + u64 lpos_64; + + /* make sure we know about any failures on the DSP side */ + sdev->dtrace_error = false; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count) + return 0; + + /* check for buffer wrap and count overflow */ + lpos_64 = lpos; + lpos = do_div(lpos_64, buffer_size); + + if (count > buffer_size - lpos) + count = buffer_size - lpos; + + /* get available count based on current host offset */ + avail = sof_wait_trace_avail(sdev, lpos, buffer_size); + if (sdev->dtrace_error) { + dev_err(sdev->dev, "error: trace IO error\n"); + return -EIO; + } + + /* make sure count is <= avail */ + count = avail > count ? count : avail; + + /* copy available trace data to debugfs */ + rem = copy_to_user(buffer, dfse->buf + lpos, count); + if (rem == count) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static const struct file_operations sof_dfs_trace_fops = { + .open = simple_open, + .read = sof_dfsentry_trace_read, + .llseek = default_llseek, +}; + +static int trace_debugfs_create(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry_buf *dfse; + + if (!sdev) + return -EINVAL; + + dfse = kzalloc(sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->buf = sdev->dmatb.area; + dfse->size = sdev->dmatb.bytes; + dfse->sdev = sdev; + + dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root, + dfse, &sof_dfs_trace_fops); + if (!dfse->dfsentry) { + dev_err(sdev->dev, + "error: cannot create debugfs entry for trace\n"); + kfree(dfse); + return -ENODEV; + } + + return 0; +} + +int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) +{ + struct sof_ipc_dma_trace_params params; + struct sof_ipc_reply ipc_reply; + int ret; + + if (sdev->dtrace_is_enabled) + return -EINVAL; + + /* set IPC parameters */ + params.hdr.size = sizeof(params); + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_PARAMS; + params.buffer.phy_addr = sdev->dmatp.addr; + params.buffer.size = sdev->dmatb.bytes; + params.buffer.offset = 0; + params.buffer.pages = sdev->dma_trace_pages; + params.stream_tag = 0; + + sdev->host_offset = 0; + + ret = snd_sof_dma_trace_init(sdev, ¶ms.stream_tag); + if (ret < 0) { + dev_err(sdev->dev, + "error: fail in snd_sof_dma_trace_init %d\n", ret); + return ret; + } + dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + params.hdr.cmd, ¶ms, sizeof(params), + &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't set params for DMA for trace %d\n", ret); + return ret; + } + + ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, + "error: snd_sof_dma_trace_trigger: start: %d\n", ret); + return ret; + } + + sdev->dtrace_is_enabled = true; + + return 0; +} + +int snd_sof_init_trace(struct snd_sof_dev *sdev) +{ + int ret; + + /* set false before start initialization */ + sdev->dtrace_is_enabled = false; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->parent, + PAGE_SIZE, &sdev->dmatp); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->parent, + DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't alloc buffer for trace %d\n", ret); + goto page_err; + } + + /* craete compressed page table for audio firmware */ + ret = snd_sof_create_page_table(sdev, &sdev->dmatb, sdev->dmatp.area, + sdev->dmatb.bytes); + if (ret < 0) + goto table_err; + + sdev->dma_trace_pages = ret; + dev_dbg(sdev->dev, "dma_trace_pages: %d\n", sdev->dma_trace_pages); + + if (sdev->first_boot) { + ret = trace_debugfs_create(sdev); + if (ret < 0) + goto table_err; + } + + init_waitqueue_head(&sdev->trace_sleep); + + ret = snd_sof_init_trace_ipc(sdev); + if (ret < 0) + goto table_err; + + return 0; +table_err: + snd_dma_free_pages(&sdev->dmatb); +page_err: + snd_dma_free_pages(&sdev->dmatp); + return ret; +} +EXPORT_SYMBOL(snd_sof_init_trace); + +int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn) +{ + if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) { + sdev->host_offset = posn->host_offset; + wake_up(&sdev->trace_sleep); + } + + if (posn->overflow != 0) + dev_err(sdev->dev, + "error: DSP trace buffer overflow %u bytes. Total messages %d\n", + posn->overflow, posn->messages); + + return 0; +} + +/* an error has occurred within the DSP that prevents further trace */ +void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev) +{ + if (sdev->dtrace_is_enabled) { + dev_err(sdev->dev, "error: waking up any trace sleepers\n"); + sdev->dtrace_error = true; + wake_up(&sdev->trace_sleep); + } +} +EXPORT_SYMBOL(snd_sof_trace_notify_for_error); + +void snd_sof_release_trace(struct snd_sof_dev *sdev) +{ + int ret; + + if (!sdev->dtrace_is_enabled) + return; + + ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + dev_err(sdev->dev, + "error: snd_sof_dma_trace_trigger: stop: %d\n", ret); + + ret = snd_sof_dma_trace_release(sdev); + if (ret < 0) + dev_err(sdev->dev, + "error: fail in snd_sof_dma_trace_release %d\n", ret); + + sdev->dtrace_is_enabled = false; +} +EXPORT_SYMBOL(snd_sof_release_trace); + +void snd_sof_free_trace(struct snd_sof_dev *sdev) +{ + snd_sof_release_trace(sdev); + + snd_dma_free_pages(&sdev->dmatb); + snd_dma_free_pages(&sdev->dmatp); +} +EXPORT_SYMBOL(snd_sof_free_trace);
On Tue, Dec 11, 2018 at 03:23:11PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
- if (count > buffer_size - lpos)
count = buffer_size - lpos;
min() / max() ?
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
ditto. Check entire series for such.
- dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root,
dfse, &sof_dfs_trace_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev,
"error: cannot create debugfs entry for trace\n");
kfree(dfse);
return -ENODEV;
Should not return an error. We must be fine w/o debugfs files.
- }
- return 0;
+}
On 12/11/18 5:21 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:11PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
- if (count > buffer_size - lpos)
count = buffer_size - lpos;
min() / max() ?
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
ditto. Check entire series for such.
yep.
- dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root,
dfse, &sof_dfs_trace_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev,
"error: cannot create debugfs entry for trace\n");
kfree(dfse);
return -ENODEV;
Should not return an error. We must be fine w/o debugfs files.
Not sure I fully agree with the 'must'.
yes execution should proceed, but without debugfs we cannot, well, debug, so this is a real show-stopper
- }
- return 0;
+}
On Wed, 12 Dec 2018 00:43:26 +0100, Pierre-Louis Bossart wrote:
On 12/11/18 5:21 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:11PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
- if (count > buffer_size - lpos)
count = buffer_size - lpos;
min() / max() ?
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
ditto. Check entire series for such.
yep.
- dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root,
dfse, &sof_dfs_trace_fops);
- if (!dfse->dfsentry) {
dev_err(sdev->dev,
"error: cannot create debugfs entry for trace\n");
kfree(dfse);
return -ENODEV;
Should not return an error. We must be fine w/o debugfs files.
Not sure I fully agree with the 'must'.
yes execution should proceed, but without debugfs we cannot, well, debug, so this is a real show-stopper
So the caller would ignore the error from this function?
Note that debugfs is a pure optional matter, and you can build a kernel without CONFIG_DEBUGFS, too. Then you'll always get this error...
thanks,
Takashi
On Tue, 11 Dec 2018 22:23:11 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
So what's the reason not to use Linux standard tracing infrastructure?
thanks,
Takashi
On 12/12/18 5:11 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:11 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
So what's the reason not to use Linux standard tracing infrastructure?
I obviously failed to convey the intent in the commit message :-(
What we have today is just a DMA-based transfers of 'trace' data into a ring buffer. That's it. it's very similar to what always existed on Atom and Skylake, just more transparent and released for upstream reviews this time.
Is it optimal or final? Absolutely not. There will be evolutions such as
A) support for multi-cores on the DSP side, each with their own 'trace' capability.
B) support for other hardware platforms which may not have a DMA.
C) support for 'probes' to retrieve and inject PCM data into specific firmware nodes.
This patch does not create a new generic tracing infrastructure for Linux. We are exploring ways by which this standard tracing infrastructure can be used, we just haven't had time to look into it as we focused on runtime_pm and new platforms first.
Also we need to make sure the DSP traces are not defined for Linux only, it's intended that the SOF firmware is used in non-Linux environments, so we want to use what Linux provides, but not constrain SOF to work for Linux only.
Does this help?
On Wed, 12 Dec 2018 17:04:42 +0100, Pierre-Louis Bossart wrote:
On 12/12/18 5:11 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:11 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
So what's the reason not to use Linux standard tracing infrastructure?
I obviously failed to convey the intent in the commit message :-(
What we have today is just a DMA-based transfers of 'trace' data into a ring buffer. That's it. it's very similar to what always existed on Atom and Skylake, just more transparent and released for upstream reviews this time.
Is it optimal or final? Absolutely not. There will be evolutions such as
A) support for multi-cores on the DSP side, each with their own 'trace' capability.
B) support for other hardware platforms which may not have a DMA.
C) support for 'probes' to retrieve and inject PCM data into specific firmware nodes.
This patch does not create a new generic tracing infrastructure for Linux. We are exploring ways by which this standard tracing infrastructure can be used, we just haven't had time to look into it as we focused on runtime_pm and new platforms first.
Also we need to make sure the DSP traces are not defined for Linux only, it's intended that the SOF firmware is used in non-Linux environments, so we want to use what Linux provides, but not constrain SOF to work for Linux only.
Does this help?
OK, that's fine. Liam and I talked about Linux tracing for SOF, and I thought this might be that. But this is rather a hardware data logging, so it's a different beast.
thanks,
Takashi
On 12/12/18 10:12 AM, Takashi Iwai wrote:
On Wed, 12 Dec 2018 17:04:42 +0100, Pierre-Louis Bossart wrote:
On 12/12/18 5:11 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:11 +0100, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
This patch adds support for real-time DSP logging (timestamped events and bespoke binary data) for firmware debug. The current solution relies on DMA transfers to system memory that is then accessed by userspace tools such as sof-logger. For Intel platforms, two types of DMAs are currently used (GP-DMA for Baytrail/CherryTrail and HDaudio DMA for SKL+)
Due to historical reasons, the driver code follows the DSP firmware conventions and refers to 'traces', but it is currently unrelated to the Linux trace subsystem. Future solutions will include support for more advanced hardware (e.g. MIPI Sys-T), additional formats and the ability to enable/disable specific traces dynamically.
So what's the reason not to use Linux standard tracing infrastructure?
I obviously failed to convey the intent in the commit message :-(
What we have today is just a DMA-based transfers of 'trace' data into a ring buffer. That's it. it's very similar to what always existed on Atom and Skylake, just more transparent and released for upstream reviews this time.
Is it optimal or final? Absolutely not. There will be evolutions such as
A) support for multi-cores on the DSP side, each with their own 'trace' capability.
B) support for other hardware platforms which may not have a DMA.
C) support for 'probes' to retrieve and inject PCM data into specific firmware nodes.
This patch does not create a new generic tracing infrastructure for Linux. We are exploring ways by which this standard tracing infrastructure can be used, we just haven't had time to look into it as we focused on runtime_pm and new platforms first.
Also we need to make sure the DSP traces are not defined for Linux only, it's intended that the SOF firmware is used in non-Linux environments, so we want to use what Linux provides, but not constrain SOF to work for Linux only.
Does this help?
OK, that's fine. Liam and I talked about Linux tracing for SOF, and I thought this might be that. But this is rather a hardware data logging, so it's a different beast.
Thanks. I'll rework the commit message for the third time to be clearer...
On 1/9/19 2:44 PM, Mark Brown wrote:
On Tue, Dec 11, 2018 at 03:23:11PM -0600, Pierre-Louis Bossart wrote:
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
min()?
I tried to use min() but then Sparse started complaining so went back to an explicit test+assign.
I don't have a strong opinion on this, but we've found so many improvements with the tools that I tend to favor warning-free code.
On Wed, Jan 09, 2019 at 03:39:54PM -0600, Pierre-Louis Bossart wrote:
On 1/9/19 2:44 PM, Mark Brown wrote:
On Tue, Dec 11, 2018 at 03:23:11PM -0600, Pierre-Louis Bossart wrote:
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
min()?
I tried to use min() but then Sparse started complaining so went back to an explicit test+assign.
I don't have a strong opinion on this, but we've found so many improvements with the tools that I tend to favor warning-free code.
Why was sparse complaining - was it spotting something that is an actual issue here and you've just masked the warning?
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
min()?
I tried to use min() but then Sparse started complaining so went back to an explicit test+assign. I don't have a strong opinion on this, but we've found so many improvements with the tools that I tend to favor warning-free code.
Why was sparse complaining - was it spotting something that is an actual issue here and you've just masked the warning?
It was about the use of typeof/sizeof() in min(), not an actual issue in the code
sound/soc/sof/trace.c:90:17: warning: expression using sizeof(void) sound/soc/sof/trace.c:90:17: warning: expression using sizeof(void)
On Tue, Jan 22, 2019 at 02:33:55PM -0600, Pierre-Louis Bossart wrote:
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
min()?
Why was sparse complaining - was it spotting something that is an actual issue here and you've just masked the warning?
It was about the use of typeof/sizeof() in min(), not an actual issue in the code
sound/soc/sof/trace.c:90:17: warning: expression using sizeof(void) sound/soc/sof/trace.c:90:17: warning: expression using sizeof(void)
I don't understand that warning - avail and count are both size_t so where's it deciding that there's a void involved?
Why was sparse complaining - was it spotting something that is an actual issue here and you've just masked the warning?
It was about the use of typeof/sizeof() in min(), not an actual issue in the code sound/soc/sof/trace.c:90:17: warning: expression using sizeof(void) sound/soc/sof/trace.c:90:17: warning: expression using sizeof(void)
I don't understand that warning - avail and count are both size_t so where's it deciding that there's a void involved?
no idea really, I am only a user of sparse, I don't know how it generated that warning.
there are equally confusing ones such as
include/linux/slab.h:332:43: warning: dubious: x & !y
include/linux/slab.h:665:13: warning: call with no type!
include/linux/slab.h:665:13: error: undefined identifier '__builtin_mul_overflow
The main benefit of sparse is that it is the only tool that found endianess and iomem space issues, but it's often buried in heaps of false alarms in include files.
On Tue, Jan 22, 2019 at 02:52:25PM -0600, Pierre-Louis Bossart wrote:
I don't understand that warning - avail and count are both size_t so where's it deciding that there's a void involved?
no idea really, I am only a user of sparse, I don't know how it generated that warning.
Yeah, I use it quite a bit as well.
there are equally confusing ones such as
include/linux/slab.h:332:43: warning: dubious: x & !y
include/linux/slab.h:665:13: warning: call with no type!
include/linux/slab.h:665:13: error: undefined identifier '__builtin_mul_overflow
The main benefit of sparse is that it is the only tool that found endianess and iomem space issues, but it's often buried in heaps of false alarms in include files.
It's got less and less useful as time goes on I think :( For this one did you try reporting the bug to the authors?
It's got less and less useful as time goes on I think :( For this one did you try reporting the bug to the authors?
Nope, mostly because I don't even know who the contributors are these days.
Will report this when I get a chance (trying to submit another SOF update before Chinese New Year).
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add operation pointers that can be called by core to control a wide variety of DSP targets. The DSP HW drivers will fill in these operations.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/ops.c | 200 +++++++++++++++++++++++ sound/soc/sof/ops.h | 379 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 579 insertions(+) create mode 100644 sound/soc/sof/ops.c create mode 100644 sound/soc/sof/ops.h
diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c new file mode 100644 index 000000000000..41143fd97772 --- /dev/null +++ b/sound/soc/sof/ops.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/pci.h> +#include "ops.h" + +int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value) +{ + bool change; + unsigned int old, new; + u32 ret = ~0; /* explicit init to remove uninitialized use warnings */ + + pci_read_config_dword(sdev->pci, offset, &ret); + dev_dbg(sdev->dev, "Debug PCIR: %8.8x at %8.8x\n", + pci_read_config_dword(sdev->pci, offset, &ret), offset); + + old = ret; + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) { + pci_write_config_dword(sdev->pci, offset, new); + dev_dbg(sdev->dev, "Debug PCIW: %8.8x at %8.8x\n", value, + offset); + } + + return change; +} +EXPORT_SYMBOL(snd_sof_pci_update_bits_unlocked); + +int snd_sof_pci_update_bits(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_pci_update_bits_unlocked(sdev, offset, mask, value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_pci_update_bits); + +int snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + bool change; + unsigned int old, new; + u32 ret; + + ret = snd_sof_dsp_read(sdev, bar, offset); + + old = ret; + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) + snd_sof_dsp_write(sdev, bar, offset, new); + + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_unlocked); + +int snd_sof_dsp_update_bits64_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value) +{ + bool change; + u64 old, new; + + old = snd_sof_dsp_read64(sdev, bar, offset); + + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) + snd_sof_dsp_write64(sdev, bar, offset, new); + + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits64_unlocked); + +/* This is for registers bits with attribute RWC */ +void snd_sof_dsp_update_bits_forced_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned int old, new; + u32 ret; + + ret = snd_sof_dsp_read(sdev, bar, offset); + + old = ret; + new = (old & (~mask)) | (value & mask); + + snd_sof_dsp_write(sdev, bar, offset, new); +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_forced_unlocked); + +int snd_sof_dsp_update_bits(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_dsp_update_bits_unlocked(sdev, bar, offset, mask, + value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits); + +int snd_sof_dsp_update_bits64(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u64 mask, u64 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_dsp_update_bits64_unlocked(sdev, bar, offset, mask, + value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits64); + +/* This is for registers bits with attribute RWC */ +void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sdev->hw_lock, flags); + snd_sof_dsp_update_bits_forced_unlocked(sdev, bar, offset, mask, value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_forced); + +int snd_sof_dsp_register_poll(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 target, u32 timeout) +{ + int time, ret; + + /* + * we will poll for couple of ms using mdelay, if not successful + * then go to longer sleep using usleep_range + */ + + /* check if set state successful */ + for (time = 5; time > 0; time--) { + if ((snd_sof_dsp_read(sdev, bar, offset) & mask) == target) + break; + msleep(20); + } + + if (!time) { + /* sleeping in 10ms steps so adjust timeout value */ + timeout /= 10; + + for (time = timeout; time > 0; time--) { + if ((snd_sof_dsp_read(sdev, bar, offset) & mask) == + target) + break; + + usleep_range(5000, 10000); + } + } + + ret = time ? 0 : -ETIME; + + return ret; +} +EXPORT_SYMBOL(snd_sof_dsp_register_poll); + +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset) +{ + dev_err(sdev->dev, "error : DSP panic!\n"); + + /* check if DSP is not ready and did not set the dsp_oops_offset. + * if the dsp_oops_offset is not set, set it from the panic message. + * Also add a check to memory window setting with panic message. + */ + if (!sdev->dsp_oops_offset) + sdev->dsp_oops_offset = offset; + else + dev_dbg(sdev->dev, "panic: dsp_oops_offset %zu offset %d\n", + sdev->dsp_oops_offset, offset); + + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_trace_notify_for_error(sdev); + snd_sof_dsp_cmd_done(sdev, SOF_IPC_HOST_REPLY); +} +EXPORT_SYMBOL(snd_sof_dsp_panic); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h new file mode 100644 index 000000000000..405e39155645 --- /dev/null +++ b/sound/soc/sof/ops.h @@ -0,0 +1,379 @@ +/* SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood liam.r.girdwood@linux.intel.com + */ + +#ifndef __SOUND_SOC_SOF_IO_H +#define __SOUND_SOC_SOF_IO_H + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <sound/pcm.h> +#include "sof-priv.h" + +/* init */ +static inline int snd_sof_probe(struct snd_sof_dev *sdev) +{ + if (sdev->ops->probe) + return sdev->ops->probe(sdev); + + return 0; +} + +static inline int snd_sof_remove(struct snd_sof_dev *sdev) +{ + if (sdev->ops->remove) + return sdev->ops->remove(sdev); + + return 0; +} + +/* control */ +static inline int snd_sof_dsp_run(struct snd_sof_dev *sdev) +{ + if (sdev->ops->run) + return sdev->ops->run(sdev); + + return 0; +} + +static inline int snd_sof_dsp_stall(struct snd_sof_dev *sdev) +{ + if (sdev->ops->stall) + return sdev->ops->stall(sdev); + + return 0; +} + +static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev) +{ + if (sdev->ops->reset) + return sdev->ops->reset(sdev); + + return 0; +} + +/* dsp core power up/power down */ +static inline int snd_sof_dsp_core_power_up(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + if (sdev->ops->core_power_up) + return sdev->ops->core_power_up(sdev, core_mask); + + return 0; +} + +static inline int snd_sof_dsp_core_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + if (sdev->ops->core_power_down) + return sdev->ops->core_power_down(sdev, core_mask); + + return 0; +} + +/* pre/post fw load */ +static inline int snd_sof_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + if (sdev->ops->pre_fw_run) + return sdev->ops->pre_fw_run(sdev); + + return 0; +} + +static inline int snd_sof_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + if (sdev->ops->post_fw_run) + return sdev->ops->post_fw_run(sdev); + + return 0; +} + +/* power management */ +static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev) +{ + if (sdev->ops->resume) + return sdev->ops->resume(sdev); + + return 0; +} + +static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev, int state) +{ + if (sdev->ops->suspend) + return sdev->ops->suspend(sdev, state); + + return 0; +} + +static inline int snd_sof_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + if (sdev->ops->runtime_resume) + return sdev->ops->runtime_resume(sdev); + + return 0; +} + +static inline int snd_sof_dsp_runtime_suspend(struct snd_sof_dev *sdev, + int state) +{ + if (sdev->ops->runtime_suspend) + return sdev->ops->runtime_suspend(sdev, state); + + return 0; +} + +static inline int snd_sof_dsp_set_clk(struct snd_sof_dev *sdev, u32 freq) +{ + if (sdev->ops->set_clk) + return sdev->ops->set_clk(sdev, freq); + + return 0; +} + +/* debug */ +static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) +{ + if (sdev->ops->dbg_dump) + return sdev->ops->dbg_dump(sdev, flags); +} + +/* register IO */ +static inline void snd_sof_dsp_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 value) +{ + if (sdev->ops->write) + sdev->ops->write(sdev, sdev->bar[bar] + offset, value); +} + +static inline void snd_sof_dsp_write64(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 value) +{ + if (sdev->ops->write64) + sdev->ops->write64(sdev, + sdev->bar[bar] + offset, value); +} + +static inline u32 snd_sof_dsp_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset) +{ + if (sdev->ops->read) + return sdev->ops->read(sdev, sdev->bar[bar] + offset); + + return 0; +} + +static inline u64 snd_sof_dsp_read64(struct snd_sof_dev *sdev, u32 bar, + u32 offset) +{ + if (sdev->ops->read64) + return sdev->ops->read64(sdev, sdev->bar[bar] + offset); + + return 0; +} + +/* block IO */ +static inline void snd_sof_dsp_block_read(struct snd_sof_dev *sdev, + u32 offset, void *dest, size_t bytes) +{ + if (sdev->ops->block_read) + sdev->ops->block_read(sdev, offset, dest, bytes); +} + +static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, + u32 offset, void *src, size_t bytes) +{ + if (sdev->ops->block_write) + sdev->ops->block_write(sdev, offset, src, bytes); +} + +/* mailbox */ +static inline void snd_sof_dsp_mailbox_read(struct snd_sof_dev *sdev, + u32 offset, void *message, + size_t bytes) +{ + if (sdev->ops->mailbox_read) + sdev->ops->mailbox_read(sdev, offset, message, bytes); +} + +static inline void snd_sof_dsp_mailbox_write(struct snd_sof_dev *sdev, + u32 offset, void *message, + size_t bytes) +{ + if (sdev->ops->mailbox_write) + sdev->ops->mailbox_write(sdev, offset, message, bytes); +} + +/* ipc */ +static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + if (sdev->ops->send_msg) + return sdev->ops->send_msg(sdev, msg); + + return 0; +} + +static inline int snd_sof_dsp_get_reply(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + if (sdev->ops->get_reply) + return sdev->ops->get_reply(sdev, msg); + + return 0; +} + +static inline int snd_sof_dsp_is_ready(struct snd_sof_dev *sdev) +{ + if (sdev->ops->is_ready) + return sdev->ops->is_ready(sdev); + + return 0; +} + +static inline int snd_sof_dsp_cmd_done(struct snd_sof_dev *sdev, + int dir) +{ + if (sdev->ops->cmd_done) + return sdev->ops->cmd_done(sdev, dir); + + return 0; +} + +/* host DMA trace */ +static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev, + u32 *stream_tag) +{ + if (sdev->ops->trace_init) + return sdev->ops->trace_init(sdev, stream_tag); + + return 0; +} + +static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev) +{ + if (sdev->ops->trace_release) + return sdev->ops->trace_release(sdev); + + return 0; +} + +static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd) +{ + if (sdev->ops->trace_trigger) + return sdev->ops->trace_trigger(sdev, cmd); + + return 0; +} + +/* host PCM ops */ +static inline int +snd_sof_pcm_platform_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sdev->ops && sdev->ops->pcm_open) + return sdev->ops->pcm_open(sdev, substream); + + return 0; +} + +/* disconnect pcm substream to a host stream */ +static inline int +snd_sof_pcm_platform_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sdev->ops && sdev->ops->pcm_close) + return sdev->ops->pcm_close(sdev, substream); + + return 0; +} + +/* host stream hw params */ +static inline int +snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params) +{ + if (sdev->ops && sdev->ops->pcm_hw_params) + return sdev->ops->pcm_hw_params(sdev, substream, + params, ipc_params); + + return 0; +} + +/* host stream trigger */ +static inline int +snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd) +{ + if (sdev->ops && sdev->ops->pcm_trigger) + return sdev->ops->pcm_trigger(sdev, substream, cmd); + + return 0; +} + +/* host stream pointer */ +static inline snd_pcm_uframes_t +snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sdev->ops && sdev->ops->pcm_pointer) + return sdev->ops->pcm_pointer(sdev, substream); + + return 0; +} + +static inline struct snd_sof_dsp_ops +*sof_get_ops(const struct sof_dev_desc *d, + const struct sof_ops_table mach_ops[], int asize) +{ + int i; + + for (i = 0; i < asize; i++) { + if (d == mach_ops[i].desc) + return mach_ops[i].ops; + } + + /* not found */ + return NULL; +} + +int snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +int snd_sof_dsp_update_bits64_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value); + +/* This is for registers bits with attribute RWC */ +void snd_sof_dsp_update_bits_forced_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +int snd_sof_dsp_update_bits(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 value); + +int snd_sof_dsp_update_bits64(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value); + +/* This is for registers bits with attribute RWC */ +void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value); + +int snd_sof_pci_update_bits(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value); + +int snd_sof_dsp_register_poll(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 target, u32 timeout); + +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset); +#endif
On Tue, Dec 11, 2018 at 03:23:12PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add operation pointers that can be called by core to control a wide variety of DSP targets. The DSP HW drivers will fill in these operations.
+int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset,
u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret = ~0; /* explicit init to remove uninitialized use warnings */
- pci_read_config_dword(sdev->pci, offset, &ret);
- dev_dbg(sdev->dev, "Debug PCIR: %8.8x at %8.8x\n",
pci_read_config_dword(sdev->pci, offset, &ret), offset);
- old = ret;
- new = (old & (~mask)) | (value & mask);
- change = (old != new);
- if (change) {
pci_write_config_dword(sdev->pci, offset, new);
dev_dbg(sdev->dev, "Debug PCIW: %8.8x at %8.8x\n", value,
offset);
- }
- return change;
What about dropping change completely?
if (old == new) return false;
... return true;
+} +EXPORT_SYMBOL(snd_sof_pci_update_bits_unlocked);
+int snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar,
u32 offset, u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret;
- ret = snd_sof_dsp_read(sdev, bar, offset);
- old = ret;
- new = (old & (~mask)) | (value & mask);
- change = (old != new);
- if (change)
snd_sof_dsp_write(sdev, bar, offset, new);
- return change;
Ditto. Everywhere in similar places.
+} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_unlocked);
- /* check if set state successful */
- for (time = 5; time > 0; time--) {
if ((snd_sof_dsp_read(sdev, bar, offset) & mask) == target)
break;
msleep(20);
- }
- if (!time) {
/* sleeping in 10ms steps so adjust timeout value */
timeout /= 10;
for (time = timeout; time > 0; time--) {
if ((snd_sof_dsp_read(sdev, bar, offset) & mask) ==
target)
I would leave it on one line.
break;
usleep_range(5000, 10000);
}
- }
- ret = time ? 0 : -ETIME;
- return ret;
+} +EXPORT_SYMBOL(snd_sof_dsp_register_poll);
+static inline void snd_sof_dsp_write64(struct snd_sof_dev *sdev, u32 bar,
u32 offset, u64 value)
+{
- if (sdev->ops->write64)
sdev->ops->write64(sdev,
sdev->bar[bar] + offset, value);
Why not one line?
+}
On 12/11/18 5:16 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:12PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add operation pointers that can be called by core to control a wide variety of DSP targets. The DSP HW drivers will fill in these operations. +int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset,
u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret = ~0; /* explicit init to remove uninitialized use warnings */
- pci_read_config_dword(sdev->pci, offset, &ret);
- dev_dbg(sdev->dev, "Debug PCIR: %8.8x at %8.8x\n",
pci_read_config_dword(sdev->pci, offset, &ret), offset);
- old = ret;
- new = (old & (~mask)) | (value & mask);
- change = (old != new);
- if (change) {
pci_write_config_dword(sdev->pci, offset, new);
dev_dbg(sdev->dev, "Debug PCIW: %8.8x at %8.8x\n", value,
offset);
- }
- return change;
What about dropping change completely?
if (old == new) return false;
... return true;
ok for all cases
+} +EXPORT_SYMBOL(snd_sof_pci_update_bits_unlocked); +int snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar,
u32 offset, u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret;
- ret = snd_sof_dsp_read(sdev, bar, offset);
- old = ret;
- new = (old & (~mask)) | (value & mask);
- change = (old != new);
- if (change)
snd_sof_dsp_write(sdev, bar, offset, new);
- return change;
Ditto. Everywhere in similar places.
+} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_unlocked);
- /* check if set state successful */
- for (time = 5; time > 0; time--) {
if ((snd_sof_dsp_read(sdev, bar, offset) & mask) == target)
break;
msleep(20);
- }
- if (!time) {
/* sleeping in 10ms steps so adjust timeout value */
timeout /= 10;
for (time = timeout; time > 0; time--) {
if ((snd_sof_dsp_read(sdev, bar, offset) & mask) ==
target)
I would leave it on one line.
ok
break;
usleep_range(5000, 10000);
}
- }
- ret = time ? 0 : -ETIME;
- return ret;
+} +EXPORT_SYMBOL(snd_sof_dsp_register_poll); +static inline void snd_sof_dsp_write64(struct snd_sof_dev *sdev, u32 bar,
u32 offset, u64 value)
+{
- if (sdev->ops->write64)
sdev->ops->write64(sdev,
sdev->bar[bar] + offset, value);
Why not one line?
ok
+}
On Tue, Dec 11, 2018 at 03:23:12PM -0600, Pierre-Louis Bossart wrote:
+int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset,
u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret = ~0; /* explicit init to remove uninitialized use warnings */
This looks a lot like you want to write regmap_pci_config...
+/* control */ +static inline int snd_sof_dsp_run(struct snd_sof_dev *sdev) +{
- if (sdev->ops->run)
return sdev->ops->run(sdev);
- return 0;
+}
Do we really want to return 0 for all these ops if they're not implemented? For some that seems sensible but there's others where it seems like the caller might want to know they got ignored and an error code like -ENOTSUPP might be better.
On 1/9/19 2:51 PM, Mark Brown wrote:
On Tue, Dec 11, 2018 at 03:23:12PM -0600, Pierre-Louis Bossart wrote:
+int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset,
u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret = ~0; /* explicit init to remove uninitialized use warnings */
This looks a lot like you want to write regmap_pci_config...
I think you made that note for the v2 a long time ago, not sure if I replied at the time.
We only use this for 4 cases (power/clock gating on/off, traffic class, etc) and only during the hardware initialization. This is similar to the legacy and Skylake driver, I don't see a significant benefit with a regmap?
intel/hda-ctrl.c: snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val); intel/hda-ctrl.c: snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); intel/hda-ctrl.c: snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val); intel/hda-dsp.c: snd_sof_pci_update_bits(sdev, PCI_PGCTL, intel/hda-dsp.c: snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0);
+/* control */ +static inline int snd_sof_dsp_run(struct snd_sof_dev *sdev) +{
- if (sdev->ops->run)
return sdev->ops->run(sdev);
- return 0;
+}
Do we really want to return 0 for all these ops if they're not implemented? For some that seems sensible but there's others where it seems like the caller might want to know they got ignored and an error code like -ENOTSUPP might be better.
Good point indeed. There are a set of ops that are really mandatory, we should rework this instead. Thanks for the comment.
On Wed, Jan 09, 2019 at 03:37:26PM -0600, Pierre-Louis Bossart wrote:
On 1/9/19 2:51 PM, Mark Brown wrote:
On Tue, Dec 11, 2018 at 03:23:12PM -0600, Pierre-Louis Bossart wrote:
+int snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset,
u32 mask, u32 value)
+{
- bool change;
- unsigned int old, new;
- u32 ret = ~0; /* explicit init to remove uninitialized use warnings */
This looks a lot like you want to write regmap_pci_config...
I think you made that note for the v2 a long time ago, not sure if I replied at the time.
We only use this for 4 cases (power/clock gating on/off, traffic class, etc) and only during the hardware initialization. This is similar to the legacy and Skylake driver, I don't see a significant benefit with a regmap?
Mainly the debugfs stuff and trace I'd expect. It's not a requirement.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The firmware loader exports APIs that can be called by core to load and process multiple different file formats.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/loader.c | 322 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 sound/soc/sof/loader.c
diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c new file mode 100644 index 000000000000..dbadd8c521e6 --- /dev/null +++ b/sound/soc/sof/loader.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// +// Generic firmware loader. +// + +#include <linux/firmware.h> +#include <sound/sof.h> +#include "ops.h" + +static int get_ext_windows(struct snd_sof_dev *sdev, + struct sof_ipc_ext_data_hdr *ext_hdr) +{ + struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr; + + int ret = 0; + size_t size; + + if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) + return -EINVAL; + + size = sizeof(*w) + sizeof(struct sof_ipc_window_elem) * w->num_windows; + + /* keep a local copy of the data */ + sdev->info_window = kmemdup(w, size, GFP_KERNEL); + if (!sdev->info_window) + return -ENOMEM; + + return ret; +} + +/* parse the extended FW boot data structures from FW boot message */ +int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset) +{ + struct sof_ipc_ext_data_hdr *ext_hdr; + void *ext_data; + int ret = 0; + + ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!ext_data) + return -ENOMEM; + + /* get first header */ + snd_sof_dsp_block_read(sdev, offset, ext_data, sizeof(*ext_hdr)); + ext_hdr = (struct sof_ipc_ext_data_hdr *)ext_data; + + while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { + /* read in ext structure */ + offset += sizeof(*ext_hdr); + snd_sof_dsp_block_read(sdev, offset, + ext_data + sizeof(*ext_hdr), + ext_hdr->hdr.size - sizeof(*ext_hdr)); + + dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + + /* process structure data */ + switch (ext_hdr->type) { + case SOF_IPC_EXT_DMA_BUFFER: + break; + case SOF_IPC_EXT_WINDOW: + ret = get_ext_windows(sdev, ext_hdr); + break; + default: + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to parse ext data type %d\n", + ext_hdr->type); + goto out; + } + + /* move to next header */ + offset += ext_hdr->hdr.size; + snd_sof_dsp_block_read(sdev, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = (struct sof_ipc_ext_data_hdr *)ext_data; + } +out: + kfree(ext_data); + return ret; +} +EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); + +/* generic module parser for mmaped DSPs */ +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module) +{ + struct snd_sof_blk_hdr *block; + int count; + u32 offset; + + dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n", + module->size, module->num_blocks, module->type); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->num_blocks; count++) { + if (block->size == 0) { + dev_warn(sdev->dev, + "warning: block %d size zero\n", count); + dev_warn(sdev->dev, " type 0x%x offset 0x%x\n", + block->type, block->offset); + continue; + } + + switch (block->type) { + case SOF_BLK_IMAGE: + case SOF_BLK_CACHE: + case SOF_BLK_REGS: + case SOF_BLK_SIG: + case SOF_BLK_ROM: + continue; /* not handled atm */ + case SOF_BLK_TEXT: + case SOF_BLK_DATA: + offset = block->offset; + break; + default: + dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", + block->type, count); + return -EINVAL; + } + + dev_dbg(sdev->dev, + "block %d type 0x%x size 0x%x ==> offset 0x%x\n", + count, block->type, block->size, offset); + + snd_sof_dsp_block_write(sdev, offset, + (void *)block + sizeof(*block), + block->size); + + /* next block */ + block = (void *)block + sizeof(*block) + block->size; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_parse_module_memcpy); + +static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw) +{ + struct snd_sof_fw_header *header; + + /* Read the header information from the data pointer */ + header = (struct snd_sof_fw_header *)fw->data; + + /* verify FW sig */ + if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { + dev_err(sdev->dev, "error: invalid firmware signature\n"); + return -EINVAL; + } + + /* check size is valid */ + if (fw->size != header->file_size + sizeof(*header)) { + dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n", + fw->size, header->file_size + sizeof(*header)); + return -EINVAL; + } + + dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", + header->file_size, header->num_modules, + header->abi, sizeof(*header)); + + return 0; +} + +static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw) +{ + struct snd_sof_fw_header *header; + struct snd_sof_mod_hdr *module; + int (*load_module)(struct snd_sof_dev *sof_dev, + struct snd_sof_mod_hdr *hdr); + int ret, count; + + header = (struct snd_sof_fw_header *)fw->data; + load_module = sdev->ops->load_module; + if (!load_module) + return -EINVAL; + + /* parse each module */ + module = (void *)fw->data + sizeof(*header); + for (count = 0; count < header->num_modules; count++) { + /* module */ + ret = load_module(sdev, module); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid module %d\n", count); + return ret; + } + module = (void *)module + sizeof(*module) + module->size; + } + + return 0; +} + +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = dev_get_platdata(sdev->dev); + const char *fw_filename; + int ret; + + /* set code loading condition to true */ + sdev->code_loading = 1; + fw_filename = plat_data->machine->sof_fw_filename; + + ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev); + + if (ret < 0) { + dev_err(sdev->dev, "error: request firmware failed err: %d\n", + ret); + return ret; + } + + /* make sure the FW header and file is valid */ + ret = check_header(sdev, plat_data->fw); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid FW header\n"); + goto error; + } + + /* prepare the DSP for FW loading */ + ret = snd_sof_dsp_reset(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset DSP\n"); + goto error; + } + + /* parse and load firmware modules to DSP */ + ret = load_modules(sdev, plat_data->fw); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid FW modules\n"); + goto error; + } + + return 0; + +error: + release_firmware(plat_data->fw); + return ret; + +} +EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); + +int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{ + dev_dbg(sdev->dev, "loading firmware\n"); + + if (sdev->ops->load_firmware) + return sdev->ops->load_firmware(sdev); + return 0; +} +EXPORT_SYMBOL(snd_sof_load_firmware); + +int snd_sof_run_firmware(struct snd_sof_dev *sdev) +{ + int ret; + + init_waitqueue_head(&sdev->boot_wait); + sdev->boot_complete = false; + + /* create fw_version debugfs to store boot version info */ + if (sdev->first_boot) { + ret = snd_sof_debugfs_buf_create_item(sdev, &sdev->fw_version, + sizeof(sdev->fw_version), + "fw_version"); + + if (ret < 0) { + dev_err(sdev->dev, "error: cannot create debugfs for fw_version\n"); + return ret; + } + } + + /* perform pre fw run operations */ + ret = snd_sof_dsp_pre_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed pre fw run op\n"); + return ret; + } + + dev_dbg(sdev->dev, "booting DSP firmware\n"); + + /* boot the firmware on the DSP */ + ret = snd_sof_dsp_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset DSP\n"); + return ret; + } + + /* now wait for the DSP to boot */ + ret = wait_event_timeout(sdev->boot_wait, sdev->boot_complete, + msecs_to_jiffies(sdev->boot_timeout)); + if (ret == 0) { + dev_err(sdev->dev, "error: firmware boot timeout\n"); + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX | + SOF_DBG_TEXT | SOF_DBG_PCI); + return -EIO; + } + + dev_info(sdev->dev, "firmware boot complete\n"); + + /* perform post fw run operations */ + ret = snd_sof_dsp_post_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed post fw run op\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_run_firmware); + +void snd_sof_fw_unload(struct snd_sof_dev *sdev) +{ + /* TODO: support module unloading at runtime */ +} +EXPORT_SYMBOL(snd_sof_fw_unload);
On Tue, Dec 11, 2018 at 03:23:13PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The firmware loader exports APIs that can be called by core to load and process multiple different file formats.
+static int get_ext_windows(struct snd_sof_dev *sdev,
struct sof_ipc_ext_data_hdr *ext_hdr)
+{
- struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr;
- int ret = 0;
I don't see how it's used. Perhaps you need to check code with `make W=1`.
- size_t size;
- if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
return -EINVAL;
- size = sizeof(*w) + sizeof(struct sof_ipc_window_elem) * w->num_windows;
- /* keep a local copy of the data */
- sdev->info_window = kmemdup(w, size, GFP_KERNEL);
- if (!sdev->info_window)
return -ENOMEM;
- return ret;
+}
dev_warn(sdev->dev,
"warning: block %d size zero\n", count);
dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
block->type, block->offset);
Hmm... Why do we need a kernel level duplication in words?
+int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{
- dev_dbg(sdev->dev, "loading firmware\n");
Noise. Better to introduce a trace points and drop all these kind of messages.
- if (sdev->ops->load_firmware)
return sdev->ops->load_firmware(sdev);
- return 0;
+} +EXPORT_SYMBOL(snd_sof_load_firmware);
On 12/11/18 4:38 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:13PM -0600, Pierre-Louis Bossart wrote:
From: Liam Girdwood liam.r.girdwood@linux.intel.com
The firmware loader exports APIs that can be called by core to load and process multiple different file formats. +static int get_ext_windows(struct snd_sof_dev *sdev,
struct sof_ipc_ext_data_hdr *ext_hdr)
+{
- struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr;
- int ret = 0;
I don't see how it's used. Perhaps you need to check code with `make W=1`.
Darn, we missed this one. I thought we were using W=1 on github but we aren't, this will be fixed.
Though W=1 doesn't report this one, so need to re-inspect this. Thanks for the sighting.
- size_t size;
- if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
return -EINVAL;
- size = sizeof(*w) + sizeof(struct sof_ipc_window_elem) * w->num_windows;
- /* keep a local copy of the data */
- sdev->info_window = kmemdup(w, size, GFP_KERNEL);
- if (!sdev->info_window)
return -ENOMEM;
- return ret;
+}
dev_warn(sdev->dev,
"warning: block %d size zero\n", count);
dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
block->type, block->offset);
Hmm... Why do we need a kernel level duplication in words?
Not sure I get this one, it's just a warning message where you don't copy a block of size zero into the target memory.
+int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{
- dev_dbg(sdev->dev, "loading firmware\n");
Noise. Better to introduce a trace points and drop all these kind of messages.
it's not that bad, most people understand what dmesg is, it happens once and only if you have dynamic debug.
At some point we also thought about something like drm_debug where you can specify which parts you are interested in, in addition to the verbosity level.
- if (sdev->ops->load_firmware)
return sdev->ops->load_firmware(sdev);
- return 0;
+} +EXPORT_SYMBOL(snd_sof_load_firmware);
On Tue, Dec 11, 2018 at 05:54:02PM -0600, Pierre-Louis Bossart wrote:
On 12/11/18 4:38 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:13PM -0600, Pierre-Louis Bossart wrote:
- struct sof_ipc_window *w = (struct sof_ipc_window *)ext_hdr;
- int ret = 0;
I don't see how it's used. Perhaps you need to check code with `make W=1`.
Darn, we missed this one. I thought we were using W=1 on github but we aren't, this will be fixed.
Though W=1 doesn't report this one, so need to re-inspect this. Thanks for the sighting.
This is one reason not to unconditionally init stuff on declaration - it masks some warnings.
+int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{
- dev_dbg(sdev->dev, "loading firmware\n");
Noise. Better to introduce a trace points and drop all these kind of messages.
it's not that bad, most people understand what dmesg is, it happens once and only if you have dynamic debug.
Then everyone adds their print on boot and the log gets huge (and slow if you push debug through serial during development). As a rule of thumb I tend to suggest that if you're not reporting something you discovered at runtime there's probably already other ways of getting equivalent trace.
On Tue, 11 Dec 2018 22:23:13 +0100, Pierre-Louis Bossart wrote:
+/* generic module parser for mmaped DSPs */ +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
struct snd_sof_mod_hdr *module)
+{
- struct snd_sof_blk_hdr *block;
- int count;
- u32 offset;
- dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n",
module->size, module->num_blocks, module->type);
- block = (void *)module + sizeof(*module);
- for (count = 0; count < module->num_blocks; count++) {
Need a sanity check that it won't go beyond the actual firmware size. User may pass a malicious module data, e.g. with extra large num_blocks.
if (block->size == 0) {
dev_warn(sdev->dev,
"warning: block %d size zero\n", count);
dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
block->type, block->offset);
continue;
}
switch (block->type) {
case SOF_BLK_IMAGE:
case SOF_BLK_CACHE:
case SOF_BLK_REGS:
case SOF_BLK_SIG:
case SOF_BLK_ROM:
continue; /* not handled atm */
case SOF_BLK_TEXT:
case SOF_BLK_DATA:
offset = block->offset;
break;
default:
dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n",
block->type, count);
return -EINVAL;
}
dev_dbg(sdev->dev,
"block %d type 0x%x size 0x%x ==> offset 0x%x\n",
count, block->type, block->size, offset);
snd_sof_dsp_block_write(sdev, offset,
(void *)block + sizeof(*block),
block->size);
/* next block */
block = (void *)block + sizeof(*block) + block->size;
This may lead to an unaligned access. Also how is the endianess guaranteed?
thanks,
Takashi
On 12/12/18 5:23 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:13 +0100, Pierre-Louis Bossart wrote:
+/* generic module parser for mmaped DSPs */ +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
struct snd_sof_mod_hdr *module)
+{
- struct snd_sof_blk_hdr *block;
- int count;
- u32 offset;
- dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n",
module->size, module->num_blocks, module->type);
- block = (void *)module + sizeof(*module);
- for (count = 0; count < module->num_blocks; count++) {
Need a sanity check that it won't go beyond the actual firmware size. User may pass a malicious module data, e.g. with extra large num_blocks.
Good point, will check.
if (block->size == 0) {
dev_warn(sdev->dev,
"warning: block %d size zero\n", count);
dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
block->type, block->offset);
continue;
}
switch (block->type) {
case SOF_BLK_IMAGE:
case SOF_BLK_CACHE:
case SOF_BLK_REGS:
case SOF_BLK_SIG:
case SOF_BLK_ROM:
continue; /* not handled atm */
case SOF_BLK_TEXT:
case SOF_BLK_DATA:
offset = block->offset;
break;
default:
dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n",
block->type, count);
return -EINVAL;
}
dev_dbg(sdev->dev,
"block %d type 0x%x size 0x%x ==> offset 0x%x\n",
count, block->type, block->size, offset);
snd_sof_dsp_block_write(sdev, offset,
(void *)block + sizeof(*block),
block->size);
/* next block */
block = (void *)block + sizeof(*block) + block->size;
This may lead to an unaligned access. Also how is the endianess guaranteed?
Will check, valid points.
On Tue, Dec 11, 2018 at 03:23:13PM -0600, Pierre-Louis Bossart wrote:
+int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) +{
- struct snd_sof_pdata *plat_data = dev_get_platdata(sdev->dev);
- const char *fw_filename;
- int ret;
This never actually calls the load_firmware() operation for the DSP AFAICT?
- ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev);
- if (ret < 0) {
dev_err(sdev->dev, "error: request firmware failed err: %d\n",
ret);
return ret;
- }
I'd suggest logging the name of the firmware we tried to load, users will thank you.
- /* create fw_version debugfs to store boot version info */
- if (sdev->first_boot) {
ret = snd_sof_debugfs_buf_create_item(sdev, &sdev->fw_version,
sizeof(sdev->fw_version),
"fw_version");
if (ret < 0) {
dev_err(sdev->dev, "error: cannot create debugfs for fw_version\n");
return ret;
}
- }
As Andy said elsewhere debugfs stuff like that probably shouldn't be fatal.
Thanks for the reviews Mark, much appreciated.
+int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
+{
- struct snd_sof_pdata *plat_data = dev_get_platdata(sdev->dev);
- const char *fw_filename;
- int ret;
This never actually calls the load_firmware() operation for the DSP AFAICT?
it's actually the implementation of the load_firmware callback, see e.g. for Baytrail/Broadwell
/*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy,
- ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev);
- if (ret < 0) {
dev_err(sdev->dev, "error: request firmware failed err: %d\n",
ret);
return ret;
- }
I'd suggest logging the name of the firmware we tried to load, users will thank you.
yes indeed, thanks for the suggestion, will add this right away.
- /* create fw_version debugfs to store boot version info */
- if (sdev->first_boot) {
ret = snd_sof_debugfs_buf_create_item(sdev, &sdev->fw_version,
sizeof(sdev->fw_version),
"fw_version");
if (ret < 0) {
dev_err(sdev->dev, "error: cannot create debugfs for fw_version\n");
return ret;
}
- }
As Andy said elsewhere debugfs stuff like that probably shouldn't be fatal.
yes, we've fixed this already.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add userspace ABI for audio userspace application IO outside of regular ALSA PCM and kcontrols. This is intended to be used to format coefficients and data for custom processing components.
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/uapi/sound/sof/abi.h | 63 ++++++++++ include/uapi/sound/sof/eq.h | 164 ++++++++++++++++++++++++++ include/uapi/sound/sof/fw.h | 67 +++++++++++ include/uapi/sound/sof/header.h | 27 +++++ include/uapi/sound/sof/manifest.h | 188 ++++++++++++++++++++++++++++++ include/uapi/sound/sof/tokens.h | 95 +++++++++++++++ include/uapi/sound/sof/tone.h | 21 ++++ include/uapi/sound/sof/trace.h | 93 +++++++++++++++ 8 files changed, 718 insertions(+) create mode 100644 include/uapi/sound/sof/abi.h create mode 100644 include/uapi/sound/sof/eq.h create mode 100644 include/uapi/sound/sof/fw.h create mode 100644 include/uapi/sound/sof/header.h create mode 100644 include/uapi/sound/sof/manifest.h create mode 100644 include/uapi/sound/sof/tokens.h create mode 100644 include/uapi/sound/sof/tone.h create mode 100644 include/uapi/sound/sof/trace.h
diff --git a/include/uapi/sound/sof/abi.h b/include/uapi/sound/sof/abi.h new file mode 100644 index 000000000000..cc0d3d2a47a7 --- /dev/null +++ b/include/uapi/sound/sof/abi.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +/** + * SOF ABI versioning is based on Semantic Versioning where we have a given + * MAJOR.MINOR.PATCH version number. See https://semver.org/ + * + * Rules for incrementing or changing version :- + * + * 1) Increment MAJOR version if you make incompatible API changes. MINOR and + * PATCH should be reset to 0. + * + * 2) Increment MINOR version if you add backwards compatible features or + * changes. PATCH should be reset to 0. + * + * 3) Increment PATCH version if you add backwards compatible bug fixes. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_ABI_H__ +#define __INCLUDE_UAPI_SOUND_SOF_ABI_H__ + +/* SOF ABI version major, minor and patch numbers */ +#define SOF_ABI_MAJOR 3 +#define SOF_ABI_MINOR 0 +#define SOF_ABI_PATCH 0 + +/* SOF ABI version number. Format within 32bit word is MMmmmppp */ +#define SOF_ABI_MAJOR_SHIFT 24 +#define SOF_ABI_MAJOR_MASK 0xff +#define SOF_ABI_MINOR_SHIFT 12 +#define SOF_ABI_MINOR_MASK 0xfff +#define SOF_ABI_PATCH_SHIFT 0 +#define SOF_ABI_PATCH_MASK 0xfff + +#define SOF_ABI_VER(major, minor, patch) \ + (((major) << SOF_ABI_MAJOR_SHIFT) | \ + ((minor) << SOF_ABI_MINOR_SHIFT) | \ + ((patch) << SOF_ABI_PATCH_SHIFT)) + +#define SOF_ABI_VERSION_MAJOR(version) \ + (((version) >> SOF_ABI_MAJOR_SHIFT) & SOF_ABI_MAJOR_MASK) +#define SOF_ABI_VERSION_MINOR(version) \ + (((version) >> SOF_ABI_MINOR_SHIFT) & SOF_ABI_MINOR_MASK) +#define SOF_ABI_VERSION_PATCH(version) \ + (((version) >> SOF_ABI_PATCH_SHIFT) & SOF_ABI_PATCH_MASK) + +#define SOF_ABI_VERSION_INCOMPATIBLE(sof_ver, client_ver) \ + (SOF_ABI_VERSION_MAJOR((sof_ver)) != \ + SOF_ABI_VERSION_MAJOR((client_ver)) \ + ) + +#define SOF_ABI_VERSION SOF_ABI_VER(SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH) + +/* SOF ABI magic number "SOF\0". */ +#define SOF_ABI_MAGIC 0x00464F53 + +#endif + diff --git a/include/uapi/sound/sof/eq.h b/include/uapi/sound/sof/eq.h new file mode 100644 index 000000000000..71a159502d06 --- /dev/null +++ b/include/uapi/sound/sof/eq.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_EQ_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_EQ_H__ + +/* FIR EQ type */ + +#define SOF_EQ_FIR_IDX_SWITCH 0 + +#define SOF_EQ_FIR_MAX_SIZE 4096 /* Max size allowed for coef data in bytes */ + +#define SOF_EQ_FIR_MAX_LENGTH 192 /* Max length for individual filter */ + +#define SOF_EQ_FIR_MAX_RESPONSES 8 /* A blob can define max 8 FIR EQs */ + +/* + * eq_fir_configuration data structure contains this information + * uint32_t size + * This is the number of bytes need to store the received EQ + * configuration. + * uint16_t channels_in_config + * This describes the number of channels in this EQ config data. It + * can be different from PLATFORM_MAX_CHANNELS. + * uint16_t number_of_responses + * 0=no responses, 1=one response defined, 2=two responses defined, etc. + * int16_t data[] + * assign_response[channels_in_config] + * 0 = use first response, 1 = use 2nd response, etc. + * E.g. {0, 0, 0, 0, 1, 1, 1, 1} would apply to channels 0-3 the + * same first defined response and for to channels 4-7 the second. + * coef_data[] + * Repeated data + * { filter_length, output_shift, h[] } + * for every EQ response defined where vector h has filter_length + * number of coefficients. Coefficients in h[] are in Q1.15 format. + * E.g. 16384 (Q1.15) = 0.5. The shifts are number of right shifts. + * + * NOTE: The channels_in_config must be even to have coef_data aligned to + * 32 bit word in RAM. Therefore a mono EQ assign must be duplicated to 2ch + * even if it would never used. Similarly a 5ch EQ assign must be increased + * to 6ch. EQ init will return an error if this is not met. + * + * NOTE: The filter_length must be multiple of four. Therefore the filter must + * be padded from the end with zeros have this condition met. + */ + +struct sof_eq_fir_config { + uint32_t size; + uint16_t channels_in_config; + uint16_t number_of_responses; + + /* reserved */ + uint32_t reserved[4]; + + int16_t data[]; +} __packed; + +struct sof_eq_fir_coef_data { + int16_t length; /* Number of FIR taps */ + int16_t out_shift; /* Amount of right shifts at output */ + + /* reserved */ + uint32_t reserved[4]; + + int16_t coef[]; /* FIR coefficients */ +} __packed; + +/* In the struct above there's two words (length, shift) before the actual + * FIR coefficients. This information is used in parsing of the config blob. + */ +#define SOF_EQ_FIR_COEF_NHEADER 2 + +/* IIR EQ type */ + +#define SOF_EQ_IIR_IDX_SWITCH 0 + +#define SOF_EQ_IIR_MAX_SIZE 1024 /* Max size allowed for coef data in bytes */ + +#define SOF_EQ_IIR_MAX_RESPONSES 8 /* A blob can define max 8 IIR EQs */ + +/* eq_iir_configuration + * uint32_t channels_in_config + * This describes the number of channels in this EQ config data. It + * can be different from PLATFORM_MAX_CHANNELS. + * uint32_t number_of_responses_defined + * 0=no responses, 1=one response defined, 2=two responses defined, etc. + * int32_t data[] + * Data consist of two parts. First is the response assign vector that + * has length of channels_in_config. The latter part is coefficient + * data. + * uint32_t assign_response[channels_in_config] + * -1 = not defined, 0 = use first response, 1 = use 2nd, etc. + * E.g. {0, 0, 0, 0, -1, -1, -1, -1} would apply to channels 0-3 the + * same first defined response and leave channels 4-7 unequalized. + * coefficient_data[] + * <1st EQ> + * uint32_t num_biquads + * uint32_t num_biquads_in_series + * <1st biquad> + * int32_t coef_a2 Q2.30 format + * int32_t coef_a1 Q2.30 format + * int32_t coef_b2 Q2.30 format + * int32_t coef_b1 Q2.30 format + * int32_t coef_b0 Q2.30 format + * int32_t output_shift number of shifts right, shift left is negative + * int32_t output_gain Q2.14 format + * <2nd biquad> + * ... + * <2nd EQ> + * + * Note: A flat response biquad can be made with a section set to + * b0 = 1.0, gain = 1.0, and other parameters set to 0 + * {0, 0, 0, 0, 1073741824, 0, 16484} + */ + +struct sof_eq_iir_config { + uint32_t size; + uint32_t channels_in_config; + uint32_t number_of_responses; + + /* reserved */ + uint32_t reserved[4]; + + int32_t data[]; /* eq_assign[channels], eq 0, eq 1, ... */ +} __packed; + +struct sof_eq_iir_header_df2t { + uint32_t num_sections; + uint32_t num_sections_in_series; + + /* reserved */ + uint32_t reserved[4]; + + int32_t biquads[]; /* Repeated biquad coefficients */ +} __packed; + +struct sof_eq_iir_biquad_df2t { + int32_t a2; /* Q2.30 */ + int32_t a1; /* Q2.30 */ + int32_t b2; /* Q2.30 */ + int32_t b1; /* Q2.30 */ + int32_t b0; /* Q2.30 */ + int32_t output_shift; /* Number of right shifts */ + int32_t output_gain; /* Q2.14 */ +} __packed; + +/* A full 22th order equalizer with 11 biquads cover octave bands 1-11 in + * in the 0 - 20 kHz bandwidth. + */ +#define SOF_EQ_IIR_DF2T_BIQUADS_MAX 11 + +/* The number of int32_t words in sof_eq_iir_header_df2t */ +#define SOF_EQ_IIR_NHEADER_DF2T 2 + +/* The number of int32_t words in sof_eq_iir_biquad_df2t */ +#define SOF_EQ_IIR_NBIQUAD_DF2T 7 + +#endif diff --git a/include/uapi/sound/sof/fw.h b/include/uapi/sound/sof/fw.h new file mode 100644 index 000000000000..37dd159c955b --- /dev/null +++ b/include/uapi/sound/sof/fw.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +/* + * Firmware file format . + */ + +#ifndef __INCLUDE_UAPI_SOF_FW_H__ +#define __INCLUDE_UAPI_SOF_FW_H__ + +#define SND_SOF_FW_SIG_SIZE 4 +#define SND_SOF_FW_ABI 1 +#define SND_SOF_FW_SIG "Reef" + +/* + * Firmware module is made up of 1 . N blocks of different types. The + * Block header is used to determine where and how block is to be copied in the + * DSP/host memory space. + */ +enum snd_sof_fw_blk_type { + SOF_BLK_IMAGE = 0, /* whole image - parsed by ROMs */ + SOF_BLK_TEXT = 1, + SOF_BLK_DATA = 2, + SOF_BLK_CACHE = 3, + SOF_BLK_REGS = 4, + SOF_BLK_SIG = 5, + SOF_BLK_ROM = 6, + /* add new block types here */ +}; + +struct snd_sof_blk_hdr { + enum snd_sof_fw_blk_type type; + uint32_t size; /* bytes minus this header */ + uint32_t offset; /* offset from base */ +} __packed; + +/* + * Firmware file is made up of 1 .. N different modules types. The module + * type is used to determine how to load and parse the module. + */ +enum snd_sof_fw_mod_type { + SOF_FW_BASE = 0, /* base firmware image */ + SOF_FW_MODULE = 1, /* firmware module */ +}; + +struct snd_sof_mod_hdr { + enum snd_sof_fw_mod_type type; + uint32_t size; /* bytes minus this header */ + uint32_t num_blocks; /* number of blocks */ +} __packed; + +/* + * Firmware file header. + */ +struct snd_sof_fw_header { + unsigned char sig[SND_SOF_FW_SIG_SIZE]; /* "Reef" */ + uint32_t file_size; /* size of file minus this header */ + uint32_t num_modules; /* number of modules */ + uint32_t abi; /* version of header format */ +} __packed; + +#endif diff --git a/include/uapi/sound/sof/header.h b/include/uapi/sound/sof/header.h new file mode 100644 index 000000000000..7868990b0d6f --- /dev/null +++ b/include/uapi/sound/sof/header.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_HEADER_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_HEADER_H__ + +/* + * Header for all non IPC ABI data. + * + * Identifies data type, size and ABI. + * Used by any bespoke component data structures or binary blobs. + */ +struct sof_abi_hdr { + uint32_t magic; /**< 'S', 'O', 'F', '\0' */ + uint32_t type; /**< component specific type */ + uint32_t size; /**< size in bytes of data excl. this struct */ + uint32_t abi; /**< SOF ABI version */ + uint32_t reserved[4]; /**< reserved for future use */ + uint32_t data[0]; /**< Component data - opaque to core */ +} __packed; + +#endif diff --git a/include/uapi/sound/sof/manifest.h b/include/uapi/sound/sof/manifest.h new file mode 100644 index 000000000000..2009ee30fad0 --- /dev/null +++ b/include/uapi/sound/sof/manifest.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_MANIFEST_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_MANIFEST_H__ + +/* start offset for base FW module */ +#define SOF_MAN_ELF_TEXT_OFFSET 0x2000 + +/* FW Extended Manifest Header id = $AE1 */ +#define SOF_MAN_EXT_HEADER_MAGIC 0x31454124 + +/* module type load type */ +#define SOF_MAN_MOD_TYPE_BUILTIN 0 +#define SOF_MAN_MOD_TYPE_MODULE 1 + +struct sof_man_module_type { + uint32_t load_type:4; /* SOF_MAN_MOD_TYPE_ */ + uint32_t auto_start:1; + uint32_t domain_ll:1; + uint32_t domain_dp:1; + uint32_t rsvd_:25; +}; + +/* segment flags.type */ +#define SOF_MAN_SEGMENT_TEXT 0 +#define SOF_MAN_SEGMENT_RODATA 1 +#define SOF_MAN_SEGMENT_DATA 1 +#define SOF_MAN_SEGMENT_BSS 2 +#define SOF_MAN_SEGMENT_EMPTY 15 + +union sof_man_segment_flags { + uint32_t ul; + struct { + uint32_t contents:1; + uint32_t alloc:1; + uint32_t load:1; + uint32_t readonly:1; + uint32_t code:1; + uint32_t data:1; + uint32_t _rsvd0:2; + uint32_t type:4; /* MAN_SEGMENT_ */ + uint32_t _rsvd1:4; + uint32_t length:16; /* of segment in pages */ + } r; +} __packed; + +/* + * Module segment descriptor. Used by ROM - Immutable. + */ +struct sof_man_segment_desc { + union sof_man_segment_flags flags; + uint32_t v_base_addr; + uint32_t file_offset; +} __packed; + +/* + * The firmware binary can be split into several modules. + */ + +#define SOF_MAN_MOD_ID_LEN 4 +#define SOF_MAN_MOD_NAME_LEN 8 +#define SOF_MAN_MOD_SHA256_LEN 32 +#define SOF_MAN_MOD_ID {'$', 'A', 'M', 'E'} + +/* + * Each module has an entry in the FW header. Used by ROM - Immutable. + */ +struct sof_man_module { + uint8_t struct_id[SOF_MAN_MOD_ID_LEN]; /* SOF_MAN_MOD_ID */ + uint8_t name[SOF_MAN_MOD_NAME_LEN]; + uint8_t uuid[16]; + struct sof_man_module_type type; + uint8_t hash[SOF_MAN_MOD_SHA256_LEN]; + uint32_t entry_point; + uint16_t cfg_offset; + uint16_t cfg_count; + uint32_t affinity_mask; + uint16_t instance_max_count; /* max number of instances */ + uint16_t instance_bss_size; /* instance (pages) */ + struct sof_man_segment_desc segment[3]; +} __packed; + +/* + * Each module has a configuration in the FW header. Used by ROM - Immutable. + */ +struct sof_man_mod_config { + uint32_t par[4]; /* module parameters */ + uint32_t is_pages; /* actual size of instance .bss (pages) */ + uint32_t cps; /* cycles per second */ + uint32_t ibs; /* input buffer size (bytes) */ + uint32_t obs; /* output buffer size (bytes) */ + uint32_t module_flags; /* flags, reserved for future use */ + uint32_t cpc; /* cycles per single run */ + uint32_t obls; /* output block size, reserved for future use */ +} __packed; + +/* + * FW Manifest Header + */ + +#define SOF_MAN_FW_HDR_FW_NAME_LEN 8 +#define SOF_MAN_FW_HDR_ID {'$', 'A', 'M', '1'} +#define SOF_MAN_FW_HDR_NAME "ADSPFW" +#define SOF_MAN_FW_HDR_FLAGS 0x0 +#define SOF_MAN_FW_HDR_FEATURES 0xff + +/* + * The firmware has a standard header that is checked by the ROM on firmware + * loading. preload_page_count is used by DMA code loader and is entire + * image size on CNL. i.e. CNL: total size of the binary’s .text and .rodata + * Used by ROM - Immutable. + */ +struct sof_man_fw_header { + uint8_t header_id[4]; + uint32_t header_len; + uint8_t name[SOF_MAN_FW_HDR_FW_NAME_LEN]; + /* number of pages of preloaded image loaded by driver */ + uint32_t preload_page_count; + uint32_t fw_image_flags; + uint32_t feature_mask; + uint16_t major_version; + uint16_t minor_version; + uint16_t hotfix_version; + uint16_t build_version; + uint32_t num_module_entries; + uint32_t hw_buf_base_addr; + uint32_t hw_buf_length; + /* target address for binary loading as offset in IMR - must be == base offset */ + uint32_t load_offset; +} __packed; + +/* + * Firmware manifest descriptor. This can contain N modules and N module + * configs. Used by ROM - Immutable. + */ +struct sof_man_fw_desc { + struct sof_man_fw_header header; + + /* Warning - hack for module arrays. For some unknown reason the we + * have a variable size array of struct man_module followed by a + * variable size array of struct mod_config. These should have been + * merged into a variable array of a parent structure. We have to hack + * around this in many places.... + * + * struct sof_man_module man_module[]; + * struct sof_man_mod_config mod_config[]; + */ + +} __packed; + +/* + * Component Descriptor. Used by ROM - Immutable. + */ +struct sof_man_component_desc { + uint32_t reserved[2]; /* all 0 */ + uint32_t version; + uint8_t hash[SOF_MAN_MOD_SHA256_LEN]; + uint32_t base_offset; + uint32_t limit_offset; + uint32_t attributes[4]; +} __packed; + +/* + * Audio DSP extended metadata. Used by ROM - Immutable. + */ +struct sof_man_adsp_meta_file_ext { + uint32_t ext_type; /* always 17 for ADSP extension */ + uint32_t ext_len; + uint32_t imr_type; + uint8_t reserved[16]; /* all 0 */ + struct sof_man_component_desc comp_desc[1]; +} __packed; + +/* + * Module Manifest for rimage module metadata. Not used by ROM. + */ +struct sof_man_module_manifest { + struct sof_man_module module; + uint32_t text_size; +} __packed; + +#endif diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h new file mode 100644 index 000000000000..98c2257e8e82 --- /dev/null +++ b/include/uapi/sound/sof/tokens.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + * Author: Liam Girdwood liam.r.girdwood@linux.intel.com + * Keyon Jie yang.jie@linux.intel.com + */ + +/* + * Topology IDs and tokens. + * + * ** MUST BE ALIGNED WITH TOPOLOGY CONFIGURATION TOKEN VALUES ** + */ + +#ifndef __INCLUDE_UAPI_SOF_TOPOLOGY_H__ +#define __INCLUDE_UAPI_SOF_TOPOLOGY_H__ + +/* + * Kcontrol IDs + */ +#define SOF_TPLG_KCTL_VOL_ID 256 +#define SOF_TPLG_KCTL_ENUM_ID 257 +#define SOF_TPLG_KCTL_BYTES_ID 258 + +/* + * Tokens - must match values in topology configurations + */ + +/* buffers */ +#define SOF_TKN_BUF_SIZE 100 +#define SOF_TKN_BUF_CAPS 101 + +/* DAI */ +#define SOF_TKN_DAI_DMAC_CONFIG 153 +#define SOF_TKN_DAI_TYPE 154 +#define SOF_TKN_DAI_INDEX 155 +#define SOF_TKN_DAI_DIRECTION 156 + +/* scheduling */ +#define SOF_TKN_SCHED_DEADLINE 200 +#define SOF_TKN_SCHED_PRIORITY 201 +#define SOF_TKN_SCHED_MIPS 202 +#define SOF_TKN_SCHED_CORE 203 +#define SOF_TKN_SCHED_FRAMES 204 +#define SOF_TKN_SCHED_TIMER 205 + +/* volume */ +#define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 +#define SOF_TKN_VOLUME_RAMP_STEP_MS 251 + +/* SRC */ +#define SOF_TKN_SRC_RATE_IN 300 +#define SOF_TKN_SRC_RATE_OUT 301 + +/* PCM */ +#define SOF_TKN_PCM_DMAC_CONFIG 353 + +/* Generic components */ +#define SOF_TKN_COMP_PERIOD_SINK_COUNT 400 +#define SOF_TKN_COMP_PERIOD_SOURCE_COUNT 401 +#define SOF_TKN_COMP_FORMAT 402 +#define SOF_TKN_COMP_PRELOAD_COUNT 403 + +/* SSP */ +#define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +#define SOF_TKN_INTEL_SSP_MCLK_ID 501 +#define SOF_TKN_INTEL_SSP_SAMPLE_BITS 502 +#define SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH 503 +#define SOF_TKN_INTEL_SSP_QUIRKS 504 +#define SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT 505 + +/* DMIC */ +#define SOF_TKN_INTEL_DMIC_DRIVER_VERSION 600 +#define SOF_TKN_INTEL_DMIC_CLK_MIN 601 +#define SOF_TKN_INTEL_DMIC_CLK_MAX 602 +#define SOF_TKN_INTEL_DMIC_DUTY_MIN 603 +#define SOF_TKN_INTEL_DMIC_DUTY_MAX 604 +#define SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE 605 +#define SOF_TKN_INTEL_DMIC_SAMPLE_RATE 608 +#define SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH 609 + +/* DMIC PDM */ +#define SOF_TKN_INTEL_DMIC_PDM_CTRL_ID 700 +#define SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable 701 +#define SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable 702 +#define SOF_TKN_INTEL_DMIC_PDM_POLARITY_A 703 +#define SOF_TKN_INTEL_DMIC_PDM_POLARITY_B 704 +#define SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE 705 +#define SOF_TKN_INTEL_DMIC_PDM_SKEW 706 + +#define SOF_TKN_EFFECT_TYPE 900 + +#endif diff --git a/include/uapi/sound/sof/tone.h b/include/uapi/sound/sof/tone.h new file mode 100644 index 000000000000..d7c6e5d8317e --- /dev/null +++ b/include/uapi/sound/sof/tone.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. +*/ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_TONE_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_TONE_H__ + +#define SOF_TONE_IDX_FREQUENCY 0 +#define SOF_TONE_IDX_AMPLITUDE 1 +#define SOF_TONE_IDX_FREQ_MULT 2 +#define SOF_TONE_IDX_AMPL_MULT 3 +#define SOF_TONE_IDX_LENGTH 4 +#define SOF_TONE_IDX_PERIOD 5 +#define SOF_TONE_IDX_REPEATS 6 +#define SOF_TONE_IDX_LIN_RAMP_STEP 7 + +#endif diff --git a/include/uapi/sound/sof/trace.h b/include/uapi/sound/sof/trace.h new file mode 100644 index 000000000000..dadedd199a68 --- /dev/null +++ b/include/uapi/sound/sof/trace.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_UAPI_SOUND_SOF_USER_TRACE_H__ +#define __INCLUDE_UAPI_SOUND_SOF_USER_TRACE_H__ + +/* + * Host system time. + * + * This property is used by the driver to pass down information about + * current system time. It is expressed in us. + * FW translates timestamps (in log entries, probe pockets) to this time + * domain. + * + * (cavs: SystemTime). + */ +struct system_time { + uint32_t val_l; /* Lower dword of current host time value */ + uint32_t val_u; /* Upper dword of current host time value */ +} __packed; + +/* trace event classes - high 8 bits*/ +#define TRACE_CLASS_IRQ (1 << 24) +#define TRACE_CLASS_IPC (2 << 24) +#define TRACE_CLASS_PIPE (3 << 24) +#define TRACE_CLASS_HOST (4 << 24) +#define TRACE_CLASS_DAI (5 << 24) +#define TRACE_CLASS_DMA (6 << 24) +#define TRACE_CLASS_SSP (7 << 24) +#define TRACE_CLASS_COMP (8 << 24) +#define TRACE_CLASS_WAIT (9 << 24) +#define TRACE_CLASS_LOCK (10 << 24) +#define TRACE_CLASS_MEM (11 << 24) +#define TRACE_CLASS_MIXER (12 << 24) +#define TRACE_CLASS_BUFFER (13 << 24) +#define TRACE_CLASS_VOLUME (14 << 24) +#define TRACE_CLASS_SWITCH (15 << 24) +#define TRACE_CLASS_MUX (16 << 24) +#define TRACE_CLASS_SRC (17 << 24) +#define TRACE_CLASS_TONE (18 << 24) +#define TRACE_CLASS_EQ_FIR (19 << 24) +#define TRACE_CLASS_EQ_IIR (20 << 24) +#define TRACE_CLASS_SA (21 << 24) +#define TRACE_CLASS_DMIC (22 << 24) +#define TRACE_CLASS_POWER (23 << 24) +#define TRACE_CLASS_IDC (24 << 24) +#define TRACE_CLASS_CPU (25 << 24) + +#define LOG_ENABLE 1 /* Enable logging */ +#define LOG_DISABLE 0 /* Disable logging */ + +#define LOG_LEVEL_CRITICAL 1 /* (FDK fatal) */ +#define LOG_LEVEL_VERBOSE 2 + +/* + * Layout of a log fifo. + */ +struct log_buffer_layout { + uint32_t read_ptr; /*read pointer */ + uint32_t write_ptr; /* write pointer */ + uint32_t buffer[0]; /* buffer */ +} __packed; + +/* + * Log buffer status reported by FW. + */ +struct log_buffer_status { + uint32_t core_id; /* ID of core that logged to other half */ +} __packed; + +#define TRACE_ID_LENGTH 12 + +/* + * Log entry header. + * + * The header is followed by an array of arguments (uint32_t[]). + * Number of arguments is specified by the params_num field of log_entry + */ +struct log_entry_header { + uint32_t id_0 : TRACE_ID_LENGTH; /* e.g. Pipeline ID */ + uint32_t id_1 : TRACE_ID_LENGTH; /* e.g. Component ID */ + uint32_t core_id : 8; /* Reporting core's id */ + + uint64_t timestamp; /* Timestamp (in dsp ticks) */ + uint32_t log_entry_address; /* Address of log entry in ELF */ +} __packed; + +#endif
<snip> I have one question here, it is more related to the design of address space you are using.
--- /dev/null +++ b/include/uapi/sound/sof/fw.h
+/*
- Firmware module is made up of 1 . N blocks of different types. The
- Block header is used to determine where and how block is to be copied in the
- DSP/host memory space.
- */
+enum snd_sof_fw_blk_type {
SOF_BLK_IMAGE = 0, /* whole image - parsed by ROMs */
SOF_BLK_TEXT = 1,
SOF_BLK_DATA = 2,
SOF_BLK_CACHE = 3,
SOF_BLK_REGS = 4,
SOF_BLK_SIG = 5,
SOF_BLK_ROM = 6,
/* add new block types here */
+};
+struct snd_sof_blk_hdr {
enum snd_sof_fw_blk_type type;
uint32_t size; /* bytes minus this header */
uint32_t offset; /* offset from base */
+} __packed;
This assumes that all sections of the firmware are placed in the same "type" of memory area, right?
We can go with that for the moment.
On our side, because OCRAM is pretty small we found it useful to place some parts of the binary in SDRAM.
So, I'm wondering if you considered that? I think with the current design is fairly easy to support that. Just add inside snd_sof_blk_hdr also the "base", make the loader.c correctly parse that. Also, make rimage aware of this.
thanks, Daniel.
On 12/21/18 5:10 AM, Daniel Baluta wrote:
<snip> I have one question here, it is more related to the design of address space you are using.
--- /dev/null +++ b/include/uapi/sound/sof/fw.h +/*
- Firmware module is made up of 1 . N blocks of different types. The
- Block header is used to determine where and how block is to be copied in the
- DSP/host memory space.
- */
+enum snd_sof_fw_blk_type {
SOF_BLK_IMAGE = 0, /* whole image - parsed by ROMs */
SOF_BLK_TEXT = 1,
SOF_BLK_DATA = 2,
SOF_BLK_CACHE = 3,
SOF_BLK_REGS = 4,
SOF_BLK_SIG = 5,
SOF_BLK_ROM = 6,
/* add new block types here */
+};
+struct snd_sof_blk_hdr {
enum snd_sof_fw_blk_type type;
uint32_t size; /* bytes minus this header */
uint32_t offset; /* offset from base */
+} __packed;
This assumes that all sections of the firmware are placed in the same "type" of memory area, right?
We can go with that for the moment.
On our side, because OCRAM is pretty small we found it useful to place some parts of the binary in SDRAM.
So, I'm wondering if you considered that? I think with the current design is fairly easy to support that. Just add inside snd_sof_blk_hdr also the "base", make the loader.c correctly parse that. Also, make rimage aware of this.
It's a generic requirement indeed. Even on Baytrail we could run from internal SRAM or external SDRAM, and a combination of the two. There are also firmware modules that need to be placed in specific memories with lower power, etc.
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add support for saving and restoring DSP context in D3 to host DDR.
The suspend callback includes: suspend all pcm's stream that are running, send CTX_SAVE ipc, drop all ipc's, release trace dma and then power off the DSP.
And the resume callback performs the following steps: load FW, run FW, re-initialize trace, restore pipeline, restore the kcontrol values and finally send the ctx restore ipc to the dsp.
The streams that are suspended are resumed by the ALSA resume trigger. If the streams are paused during system suspend, they are marked explicitly so they can be restored during PAUSE_RELEASE.
Signed-off-by: Ranjani Sridharan ranjani.sridharan@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/pm.c | 403 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 sound/soc/sof/pm.c
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c new file mode 100644 index 000000000000..4e056bb0d7a4 --- /dev/null +++ b/sound/soc/sof/pm.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include "ops.h" +#include "sof-priv.h" + +#define RUNTIME_PM 1 + +static int sof_restore_kcontrols(struct snd_sof_dev *sdev) +{ + struct snd_sof_control *scontrol = NULL; + int ipc_cmd, ctrl_type; + int ret = 0; + + /* restore kcontrol values */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + /* reset readback offset for scontrol after resuming */ + scontrol->readback_offset = 0; + + /* notify DSP of kcontrol values */ + switch (scontrol->cmd) { + case SOF_CTRL_CMD_VOLUME: + ipc_cmd = SOF_IPC_COMP_SET_VALUE; + ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + ret = snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd); + break; + case SOF_CTRL_CMD_BINARY: + ipc_cmd = SOF_IPC_COMP_SET_DATA; + ctrl_type = SOF_CTRL_TYPE_DATA_SET; + ret = snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd); + break; + + default: + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "error: failed kcontrol value set for widget: %d\n", + scontrol->comp_id); + + return ret; + } + } + + return 0; +} + +static int sof_restore_pipelines(struct snd_sof_dev *sdev) +{ + struct snd_sof_widget *swidget = NULL; + struct snd_sof_route *sroute = NULL; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_dai *dai; + struct sof_ipc_comp_dai *comp_dai; + struct sof_ipc_cmd_hdr *hdr; + int ret = 0; + + /* restore pipeline components */ + list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { + struct sof_ipc_comp_reply r; + + /* skip if there is no private data */ + if (!swidget->private) + continue; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + /* fallthrough */ + case snd_soc_dapm_dai_out: + dai = (struct snd_sof_dai *)swidget->private; + comp_dai = &dai->comp_dai; + ret = sof_ipc_tx_message(sdev->ipc, + comp_dai->comp.hdr.cmd, + comp_dai, sizeof(*comp_dai), + &r, sizeof(r)); + break; + case snd_soc_dapm_scheduler: + + /* + * During suspend, all DSP cores are powered off. + * Therefore upon resume, create the pipeline comp + * and power up the core that the pipeline is + * scheduled on. + */ + pipeline = (struct sof_ipc_pipe_new *)swidget->private; + ret = sof_load_pipeline_ipc(sdev, pipeline, &r); + break; + default: + hdr = (struct sof_ipc_cmd_hdr *)swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, + swidget->private, hdr->size, + &r, sizeof(r)); + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load widget type %d with ID: %d\n", + swidget->widget->id, swidget->comp_id); + + return ret; + } + } + + /* restore pipeline connections */ + list_for_each_entry_reverse(sroute, &sdev->route_list, list) { + struct sof_ipc_pipe_comp_connect *connect; + struct sof_ipc_reply reply; + + /* skip if there's no private data */ + if (!sroute->private) + continue; + + connect = sroute->private; + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load route sink %s control %s source %s\n", + sroute->route.sink, + sroute->route.control ? sroute->route.control + : "none", + sroute->route.source); + + return ret; + } + } + + /* restore dai links */ + list_for_each_entry_reverse(dai, &sdev->dai_list, list) { + struct sof_ipc_reply reply; + struct sof_ipc_dai_config *config = dai->dai_config; + + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, + config->hdr.size, + &reply, sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set dai config for %s\n", + dai->name); + + return ret; + } + } + + /* complete pipeline */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(sdev, swidget); + break; + default: + break; + } + } + + /* restore pipeline kcontrols */ + if (sdev->restore_kcontrols) + return sof_restore_kcontrols(sdev); + + return 0; +} + +static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd) +{ + struct sof_ipc_pm_ctx pm_ctx; + struct sof_ipc_reply reply; + + memset(&pm_ctx, 0, sizeof(pm_ctx)); + + /* configure ctx save ipc message */ + pm_ctx.hdr.size = sizeof(pm_ctx); + pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; + + /* send ctx save ipc to dsp */ + return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, + sizeof(pm_ctx), &reply, sizeof(reply)); +} + +static void sof_suspend_streams(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm; + struct snd_pcm_substream *substream; + int dir; + + /* suspend all running streams */ + list_for_each_entry(spcm, &sdev->pcm_list, list) { + + mutex_lock(&spcm->mutex); + + /* suspend running playback stream */ + dir = SNDRV_PCM_STREAM_PLAYBACK; + substream = spcm->stream[dir].substream; + + if (substream && substream->runtime) { + + snd_pcm_suspend(substream); + + /* + * set restore_stream so that hw_params can be + * restored during resume + */ + spcm->restore_stream[dir] = 1; + } + + /* suspend running capture stream */ + dir = SNDRV_PCM_STREAM_CAPTURE; + substream = spcm->stream[dir].substream; + + if (substream && substream->runtime) { + + snd_pcm_suspend(substream); + + /* + * set restore_stream so that hw_params can be + * restored during resume + */ + spcm->restore_stream[dir] = 1; + } + + mutex_unlock(&spcm->mutex); + } +} + +static int sof_resume(struct device *dev, int runtime_resume) +{ + struct sof_platform_priv *priv = dev_get_drvdata(dev); + struct snd_sof_dev *sdev = dev_get_drvdata(&priv->pdev_pcm->dev); + int ret = 0; + + /* do nothing if dsp resume callbacks are not set */ + if (!sdev->ops->resume || !sdev->ops->runtime_resume) + return 0; + + /* + * if the runtime_resume flag is set, call the runtime_resume routine + * or else call the system resume routine + */ + if (runtime_resume) + ret = snd_sof_dsp_runtime_resume(sdev); + else + ret = snd_sof_dsp_resume(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to power up DSP after resume\n"); + return ret; + } + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load DSP firmware after resume %d\n", + ret); + return ret; + } + + /* boot the firmware */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to boot DSP firmware after resume %d\n", + ret); + return ret; + } + + /* resume DMA trace, only need send ipc */ + ret = snd_sof_init_trace_ipc(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to init trace after resume %d\n", + ret); + } + + /* restore pipelines */ + ret = sof_restore_pipelines(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to restore pipeline after resume %d\n", + ret); + return ret; + } + + /* notify DSP of system resume */ + ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); + if (ret < 0) + dev_err(sdev->dev, + "error: ctx_restore ipc error during resume %d\n", + ret); + + return ret; +} + +static int sof_suspend(struct device *dev, int runtime_suspend) +{ + struct sof_platform_priv *priv = dev_get_drvdata(dev); + struct snd_sof_dev *sdev = dev_get_drvdata(&priv->pdev_pcm->dev); + int ret = 0; + + /* do nothing if dsp suspend callback is not set */ + if (!sdev->ops->suspend) + return 0; + + /* release trace */ + snd_sof_release_trace(sdev); + + /* + * Suspend running pcm streams. + * They will be restarted by ALSA resume trigger call. + */ + sof_suspend_streams(sdev); + + /* notify DSP of upcoming power down */ + ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_SAVE); + if (ret < 0) { + dev_err(sdev->dev, + "error: ctx_save ipc error during suspend %d\n", + ret); + return ret; + } + + /* drop all ipc */ + sof_ipc_drop_all(sdev->ipc); + + /* power down all DSP cores */ + if (runtime_suspend) + ret = snd_sof_dsp_runtime_suspend(sdev, 0); + else + ret = snd_sof_dsp_suspend(sdev, 0); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to power down DSP during suspend %d\n", + ret); + + /* set flag for restoring kcontrols upon resuming */ + sdev->restore_kcontrols = true; + + return ret; +} + +int snd_sof_runtime_suspend(struct device *dev) +{ + return sof_suspend(dev, RUNTIME_PM); +} +EXPORT_SYMBOL(snd_sof_runtime_suspend); + +int snd_sof_runtime_resume(struct device *dev) +{ + return sof_resume(dev, RUNTIME_PM); +} +EXPORT_SYMBOL(snd_sof_runtime_resume); + +int snd_sof_resume(struct device *dev) +{ + return sof_resume(dev, !RUNTIME_PM); +} +EXPORT_SYMBOL(snd_sof_resume); + +int snd_sof_suspend(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL(snd_sof_suspend); + +int snd_sof_suspend_late(struct device *dev) +{ + return sof_suspend(dev, !RUNTIME_PM); +} +EXPORT_SYMBOL(snd_sof_suspend_late); + +int snd_sof_prepare(struct device *dev) +{ + struct sof_platform_priv *priv = dev_get_drvdata(dev); + struct snd_sof_dev *sdev = dev_get_drvdata(&priv->pdev_pcm->dev); + + /* + * PCI devices are brought back to full power before system suspend. + * Setting this flag will prevent restoring kcontrols + * when resuming before system suspend + */ + sdev->restore_kcontrols = false; + + return 0; +} +EXPORT_SYMBOL(snd_sof_prepare);
On Tue, 11 Dec 2018 22:23:15 +0100, Pierre-Louis Bossart wrote:
+#define RUNTIME_PM 1
What's this? This seems used in snd_soc_runtime_suspend() and _resume(). But it's a just normal boolean flag, no?
+static void sof_suspend_streams(struct snd_sof_dev *sdev) +{
- struct snd_sof_pcm *spcm;
- struct snd_pcm_substream *substream;
- int dir;
- /* suspend all running streams */
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
mutex_lock(&spcm->mutex);
/* suspend running playback stream */
dir = SNDRV_PCM_STREAM_PLAYBACK;
substream = spcm->stream[dir].substream;
if (substream && substream->runtime) {
snd_pcm_suspend(substream);
/*
* set restore_stream so that hw_params can be
* restored during resume
*/
spcm->restore_stream[dir] = 1;
}
/* suspend running capture stream */
dir = SNDRV_PCM_STREAM_CAPTURE;
substream = spcm->stream[dir].substream;
if (substream && substream->runtime) {
snd_pcm_suspend(substream);
/*
* set restore_stream so that hw_params can be
* restored during resume
*/
spcm->restore_stream[dir] = 1;
}
Both playback and capture do the same thing, so this can be a loop of dir.
thanks,
Takashi
On 12/12/18 5:32 AM, Takashi Iwai wrote:
On Tue, 11 Dec 2018 22:23:15 +0100, Pierre-Louis Bossart wrote:
+#define RUNTIME_PM 1
What's this? This seems used in snd_soc_runtime_suspend() and _resume(). But it's a just normal boolean flag, no?
yes, it's useless. I saw it in one of my checks and forgot about it, will fix.
+static void sof_suspend_streams(struct snd_sof_dev *sdev) +{
- struct snd_sof_pcm *spcm;
- struct snd_pcm_substream *substream;
- int dir;
- /* suspend all running streams */
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
mutex_lock(&spcm->mutex);
/* suspend running playback stream */
dir = SNDRV_PCM_STREAM_PLAYBACK;
substream = spcm->stream[dir].substream;
if (substream && substream->runtime) {
snd_pcm_suspend(substream);
/*
* set restore_stream so that hw_params can be
* restored during resume
*/
spcm->restore_stream[dir] = 1;
}
/* suspend running capture stream */
dir = SNDRV_PCM_STREAM_CAPTURE;
substream = spcm->stream[dir].substream;
if (substream && substream->runtime) {
snd_pcm_suspend(substream);
/*
* set restore_stream so that hw_params can be
* restored during resume
*/
spcm->restore_stream[dir] = 1;
}
Both playback and capture do the same thing, so this can be a loop of dir.
yes, valid point, will fix.
thanks,
Takashi _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
From: Liam Girdwood liam.r.girdwood@linux.intel.com
Add a simple "fallback" machine driver that can be used to enable SOF on boards with no codec device. This machine driver can also be forced for debug/development.
Signed-off-by: Keyon Jie yang.jie@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof.h | 10 +++++ sound/soc/sof/nocodec.c | 81 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 sound/soc/sof/nocodec.c
diff --git a/include/sound/sof.h b/include/sound/sof.h index 371a14cb4a56..4ed6e2379141 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -12,6 +12,7 @@ #define __INCLUDE_SOUND_SOF_H
#include <linux/pci.h> +#include <sound/soc.h> #include <sound/soc-acpi.h>
struct snd_sof_dsp_ops; @@ -78,4 +79,13 @@ struct sof_dev_desc { const char *nocodec_tplg_filename; };
+int sof_nocodec_setup(struct device *dev, + struct snd_sof_pdata *sof_pdata, + struct snd_soc_acpi_mach *mach, + const struct sof_dev_desc *desc, + struct snd_sof_dsp_ops *ops); + +int sof_bes_setup(struct device *dev, struct snd_sof_dsp_ops *ops, + struct snd_soc_dai_link *links, int link_num, + struct snd_soc_card *card); #endif diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c new file mode 100644 index 000000000000..686324b30a57 --- /dev/null +++ b/sound/soc/sof/nocodec.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood liam.r.girdwood@linux.intel.com +// + +#include <linux/module.h> +#include <sound/sof.h> +#include "sof-priv.h" + +static struct snd_soc_card sof_nocodec_card = { + .name = "nocodec", /* the sof- prefix is added by the core */ +}; + +int sof_nocodec_setup(struct device *dev, + struct snd_sof_pdata *sof_pdata, + struct snd_soc_acpi_mach *mach, + const struct sof_dev_desc *desc, + struct snd_sof_dsp_ops *ops) +{ + struct snd_soc_dai_link *links; + int ret; + + if (!mach) + return -EINVAL; + + sof_pdata->drv_name = "sof-nocodec"; + + mach->drv_name = "sof-nocodec"; + mach->sof_fw_filename = desc->nocodec_fw_filename; + mach->sof_tplg_filename = desc->nocodec_tplg_filename; + + /* create dummy BE dai_links */ + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * + ops->num_drv, GFP_KERNEL); + if (!links) + return -ENOMEM; + + ret = sof_bes_setup(dev, ops, links, ops->num_drv, + &sof_nocodec_card); + if (ret) { + kfree(links); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sof_nocodec_setup); + +static int sof_nocodec_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &sof_nocodec_card; + + card->dev = &pdev->dev; + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static int sof_nocodec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver sof_nocodec_audio = { + .probe = sof_nocodec_probe, + .remove = sof_nocodec_remove, + .driver = { + .name = "sof-nocodec", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(sof_nocodec_audio) + +MODULE_DESCRIPTION("ASoC sof nocodec"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:sof-nocodec");
Add common directory for xtensa architecture
Signed-off-by: Pan Xiuli xiuli.pan@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sof/xtensa.h | 44 ++++++++++ sound/soc/sof/xtensa/Kconfig | 3 + sound/soc/sof/xtensa/Makefile | 5 ++ sound/soc/sof/xtensa/core.c | 152 ++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 include/sound/sof/xtensa.h create mode 100644 sound/soc/sof/xtensa/Kconfig create mode 100644 sound/soc/sof/xtensa/Makefile create mode 100644 sound/soc/sof/xtensa/core.c
diff --git a/include/sound/sof/xtensa.h b/include/sound/sof/xtensa.h new file mode 100644 index 000000000000..a7189984000d --- /dev/null +++ b/include/sound/sof/xtensa.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) 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 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_XTENSA_H__ +#define __INCLUDE_SOUND_SOF_XTENSA_H__ + +#include <sound/sof/header.h> + +/* + * Architecture specific debug + */ + +/* Xtensa Firmware Oops data */ +struct sof_ipc_dsp_oops_xtensa { + struct sof_ipc_hdr hdr; + uint32_t exccause; + uint32_t excvaddr; + uint32_t ps; + uint32_t epc1; + uint32_t epc2; + uint32_t epc3; + uint32_t epc4; + uint32_t epc5; + uint32_t epc6; + uint32_t epc7; + uint32_t eps2; + uint32_t eps3; + uint32_t eps4; + uint32_t eps5; + uint32_t eps6; + uint32_t eps7; + uint32_t depc; + uint32_t intenable; + uint32_t interrupt; + uint32_t sar; + uint32_t stack; +} __packed; + +#endif diff --git a/sound/soc/sof/xtensa/Kconfig b/sound/soc/sof/xtensa/Kconfig new file mode 100644 index 000000000000..f66f17bb0335 --- /dev/null +++ b/sound/soc/sof/xtensa/Kconfig @@ -0,0 +1,3 @@ +config SND_SOC_SOF_XTENSA + tristate + diff --git a/sound/soc/sof/xtensa/Makefile b/sound/soc/sof/xtensa/Makefile new file mode 100644 index 000000000000..cc89c7472a38 --- /dev/null +++ b/sound/soc/sof/xtensa/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) + +snd-sof-xtensa-dsp-objs := core.o + +obj-$(CONFIG_SND_SOC_SOF_XTENSA) += snd-sof-xtensa-dsp.o diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c new file mode 100644 index 000000000000..8bc3b782c16e --- /dev/null +++ b/sound/soc/sof/xtensa/core.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Pan Xiuli xiuli.pan@linux.intel.com +// + +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../sof-priv.h" + +struct xtensa_exception_cause { + u32 id; + const char *msg; + const char *description; +}; + +/* From 4.4.1.5 table 4-64 Exception Causes of + * Xtensa Instruction Set Architecture (ISA) Reference Manual + */ +static const struct xtensa_exception_cause xtensa_exception_causes[] = { + {0, "IllegalInstructionCause", "Illegal instruction"}, + {1, "SyscallCause", "SYSCALL instruction"}, + {2, "InstructionFetchErrorCause", + "Processor internal physical address or data error during instruction fetch"}, + {3, "LoadStoreErrorCause", + "Processor internal physical address or data error during load or store"}, + {4, "Level1InterruptCause", + "Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register"}, + {5, "AllocaCause", + "MOVSP instruction, if caller’s registers are not in the register file"}, + {6, "IntegerDivideByZeroCause", + "QUOS, QUOU, REMS, or REMU divisor operand is zero"}, + {8, "PrivilegedCause", + "Attempt to execute a privileged operation when CRING ? 0"}, + {9, "LoadStoreAlignmentCause", "Load or store to an unaligned address"}, + {12, "InstrPIFDataErrorCause", + "PIF data error during instruction fetch"}, + {13, "LoadStorePIFDataErrorCause", + "Synchronous PIF data error during LoadStore access"}, + {14, "InstrPIFAddrErrorCause", + "PIF address error during instruction fetch"}, + {15, "LoadStorePIFAddrErrorCause", + "Synchronous PIF address error during LoadStore access"}, + {16, "InstTLBMissCause", "Error during Instruction TLB refill"}, + {17, "InstTLBMultiHitCause", + "Multiple instruction TLB entries matched"}, + {18, "InstFetchPrivilegeCause", + "An instruction fetch referenced a virtual address at a ring level less than CRING"}, + {20, "InstFetchProhibitedCause", + "An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch"}, + {24, "LoadStoreTLBMissCause", + "Error during TLB refill for a load or store"}, + {25, "LoadStoreTLBMultiHitCause", + "Multiple TLB entries matched for a load or store"}, + {26, "LoadStorePrivilegeCause", + "A load or store referenced a virtual address at a ring level less than CRING"}, + {28, "LoadProhibitedCause", + "A load referenced a page mapped with an attribute that does not permit loads"}, + {32, "Coprocessor0Disabled", + "Coprocessor 0 instruction when cp0 disabled"}, + {33, "Coprocessor1Disabled", + "Coprocessor 1 instruction when cp1 disabled"}, + {34, "Coprocessor2Disabled", + "Coprocessor 2 instruction when cp2 disabled"}, + {35, "Coprocessor3Disabled", + "Coprocessor 3 instruction when cp3 disabled"}, + {36, "Coprocessor4Disabled", + "Coprocessor 4 instruction when cp4 disabled"}, + {37, "Coprocessor5Disabled", + "Coprocessor 5 instruction when cp5 disabled"}, + {38, "Coprocessor6Disabled", + "Coprocessor 6 instruction when cp6 disabled"}, + {39, "Coprocessor7Disabled", + "Coprocessor 7 instruction when cp7 disabled"}, +}; + +/* only need xtensa atm */ +static void xtensa_dsp_oops(struct snd_sof_dev *sdev, void *oops) +{ + struct sof_ipc_dsp_oops_xtensa *xoops = oops; + int i; + + dev_err(sdev->dev, "error: DSP Firmware Oops\n"); + for (i = 0; i < ARRAY_SIZE(xtensa_exception_causes); i++) { + if (xtensa_exception_causes[i].id == xoops->exccause) { + dev_err(sdev->dev, "error: Exception Cause: %s, %s\n", + xtensa_exception_causes[i].msg, + xtensa_exception_causes[i].description); + } + } + dev_err(sdev->dev, "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n", + xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar); + dev_err(sdev->dev, "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x", + xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4); + dev_err(sdev->dev, "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x", + xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc); + dev_err(sdev->dev, "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x", + xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5); + dev_err(sdev->dev, "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x", + xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt); +} + +static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + struct sof_ipc_dsp_oops_xtensa *xoops = oops; + u32 stack_ptr = xoops->stack; + int i; + + dev_err(sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr); + + for (i = 0; i <= stack_words - 4; i += 4) { + dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n", + stack_ptr + i, stack[i], stack[i + 1], stack[i + 2], + stack[i + 3]); + } + + /* deal with any remaining words */ + switch (stack_words - i) { + case 0: + break; + case 1: + dev_err(sdev->dev, "0x%8.8x: 0x%8.8x\n", + stack_ptr + stack_words - 1, stack[stack_words - 1]); + break; + case 2: + dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x\n", + stack_ptr + stack_words - 2, stack[stack_words - 2], + stack[stack_words - 1]); + break; + case 3: + dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x 0x%8.8x\n", + stack_ptr + stack_words - 3, stack[stack_words - 3], + stack[stack_words - 2], stack[stack_words - 1]); + break; + default: + break; + } +} + +const struct sof_arch_ops sof_xtensa_arch_ops = { + .dsp_oops = xtensa_dsp_oops, + .dsp_stack = xtensa_stack, +}; +EXPORT_SYMBOL(sof_xtensa_arch_ops); + +MODULE_DESCRIPTION("SOF Xtensa DSP support"); +MODULE_LICENSE("Dual BSD/GPL");
On Tue, Dec 11, 2018 at 03:23:17PM -0600, Pierre-Louis Bossart wrote:
Add common directory for xtensa architecture
- for (i = 0; i <= stack_words - 4; i += 4) {
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n",
stack_ptr + i, stack[i], stack[i + 1], stack[i + 2],
stack[i + 3]);
- }
- /* deal with any remaining words */
- switch (stack_words - i) {
- case 0:
break;
- case 1:
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x\n",
stack_ptr + stack_words - 1, stack[stack_words - 1]);
break;
- case 2:
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x\n",
stack_ptr + stack_words - 2, stack[stack_words - 2],
stack[stack_words - 1]);
break;
- case 3:
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x 0x%8.8x\n",
stack_ptr + stack_words - 3, stack[stack_words - 3],
stack[stack_words - 2], stack[stack_words - 1]);
break;
- default:
break;
- }
hex_dump_to_buffer().
On 12/11/18 5:08 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:17PM -0600, Pierre-Louis Bossart wrote:
Add common directory for xtensa architecture
- for (i = 0; i <= stack_words - 4; i += 4) {
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n",
stack_ptr + i, stack[i], stack[i + 1], stack[i + 2],
stack[i + 3]);
- }
- /* deal with any remaining words */
- switch (stack_words - i) {
- case 0:
break;
- case 1:
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x\n",
stack_ptr + stack_words - 1, stack[stack_words - 1]);
break;
- case 2:
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x\n",
stack_ptr + stack_words - 2, stack[stack_words - 2],
stack[stack_words - 1]);
break;
- case 3:
dev_err(sdev->dev, "0x%8.8x: 0x%8.8x 0x%8.8x 0x%8.8x\n",
stack_ptr + stack_words - 3, stack[stack_words - 3],
stack[stack_words - 2], stack[stack_words - 1]);
break;
- default:
break;
- }
hex_dump_to_buffer().
I vaguely recall there was a reason not to do it, we'll relook at this.
Helpers to set-up back-ends, create platform devices and common IO/block read/write operations
Signed-off-by: Keyon Jie yang.jie@linux.intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/soc/sof/utils.c | 173 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 sound/soc/sof/utils.c
diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c new file mode 100644 index 000000000000..bf66e14bc258 --- /dev/null +++ b/sound/soc/sof/utils.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: (GPL-2.0 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 Intel Corporation. All rights reserved. +// +// Author: Keyon Jie yang.jie@linux.intel.com +// + +#include <linux/io.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include "sof-priv.h" + +int sof_bes_setup(struct device *dev, struct snd_sof_dsp_ops *ops, + struct snd_soc_dai_link *links, int link_num, + struct snd_soc_card *card) +{ + char name[32]; + int i; + + if (!ops || !links || !card) + return -EINVAL; + + /* set up BE dai_links */ + for (i = 0; i < link_num; i++) { + snprintf(name, 32, "NoCodec-%d", i); + links[i].name = devm_kstrdup(dev, name, GFP_KERNEL); + if (!links[i].name) + return -ENOMEM; + + links[i].id = i; + links[i].no_pcm = 1; + links[i].cpu_dai_name = ops->drv[i].name; + links[i].platform_name = "sof-audio"; + links[i].codec_dai_name = "snd-soc-dummy-dai"; + links[i].codec_name = "snd-soc-dummy"; + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + } + + card->dai_link = links; + card->num_links = link_num; + + return 0; +} +EXPORT_SYMBOL(sof_bes_setup); + +/* register sof platform device */ +int sof_create_platform_device(struct sof_platform_priv *priv) +{ + struct snd_sof_pdata *sof_pdata = priv->sof_pdata; + struct device *dev = sof_pdata->dev; + + priv->pdev_pcm = + platform_device_register_data(dev, "sof-audio", -1, + sof_pdata, sizeof(*sof_pdata)); + if (IS_ERR(priv->pdev_pcm)) { + dev_err(dev, "error: cannot register device sof-audio. Error %d\n", + (int)PTR_ERR(priv->pdev_pcm)); + return PTR_ERR(priv->pdev_pcm); + } + + return 0; +} +EXPORT_SYMBOL(sof_create_platform_device); + +/* + * Register IO + */ + +void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value) +{ + writel(value, addr); +} +EXPORT_SYMBOL(sof_io_write); + +u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr) +{ + return readl(addr); +} +EXPORT_SYMBOL(sof_io_read); + +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value) +{ +#ifdef CONFIG_64BIT + writeq(value, addr); +#else + memcpy_toio(addr, &value, sizeof(value)); +#endif +} +EXPORT_SYMBOL(sof_io_write64); + +u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr) +{ +#ifdef CONFIG_64BIT + return readq(addr); +#else + u64 val; + + memcpy_fromio(&val, addr, sizeof(val)); + return val; +#endif +} +EXPORT_SYMBOL(sof_io_read64); + +/* + * IPC Mailbox IO + */ + +void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes) +{ + void __iomem *dest = sdev->bar[sdev->mailbox_bar] + offset; + + memcpy_toio(dest, message, bytes); +} +EXPORT_SYMBOL(sof_mailbox_write); + +void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes) +{ + void __iomem *src = sdev->bar[sdev->mailbox_bar] + offset; + + memcpy_fromio(message, src, bytes); +} +EXPORT_SYMBOL(sof_mailbox_read); + +/* + * Memory copy. + */ + +void sof_block_write(struct snd_sof_dev *sdev, u32 offset, void *src, + size_t size) +{ + void __iomem *dest = sdev->bar[sdev->mmio_bar] + offset; + const u8 *src_byte = src; + u32 affected_mask; + u32 tmp = 0; + int m, n; + + m = size / 4; + n = size % 4; + + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy(dest, src, m); + + if (n) { + affected_mask = (1 << (8 * n)) - 1; + + /* first read the 32bit data of dest, then change affected + * bytes, and write back to dest. For unaffected bytes, it + * should not be changed + */ + __ioread32_copy(&tmp, dest + m * 4, 1); + tmp &= ~affected_mask; + + tmp |= *(u32 *)(src_byte + m * 4) & affected_mask; + __iowrite32_copy(dest + m * 4, &tmp, 1); + } +} +EXPORT_SYMBOL(sof_block_write); + +void sof_block_read(struct snd_sof_dev *sdev, u32 offset, void *dest, + size_t size) +{ + void __iomem *src = sdev->bar[sdev->mmio_bar] + offset; + + memcpy_fromio(dest, src, size); +} +EXPORT_SYMBOL(sof_block_read);
On Tue, Dec 11, 2018 at 03:23:18PM -0600, Pierre-Louis Bossart wrote:
Helpers to set-up back-ends, create platform devices and common IO/block read/write operations
+int sof_bes_setup(struct device *dev, struct snd_sof_dsp_ops *ops,
struct snd_soc_dai_link *links, int link_num,
struct snd_soc_card *card)
+{
- char name[32];
- int i;
- if (!ops || !links || !card)
return -EINVAL;
- /* set up BE dai_links */
- for (i = 0; i < link_num; i++) {
snprintf(name, 32, "NoCodec-%d", i);
sizeof(name) ?
links[i].name = devm_kstrdup(dev, name, GFP_KERNEL);
if (!links[i].name)
return -ENOMEM;
...or better devm_kasprintf().
links[i].id = i;
links[i].no_pcm = 1;
links[i].cpu_dai_name = ops->drv[i].name;
links[i].platform_name = "sof-audio";
links[i].codec_dai_name = "snd-soc-dummy-dai";
links[i].codec_name = "snd-soc-dummy";
links[i].dpcm_playback = 1;
links[i].dpcm_capture = 1;
- }
- card->dai_link = links;
- card->num_links = link_num;
- return 0;
+} +EXPORT_SYMBOL(sof_bes_setup);
+/* register sof platform device */ +int sof_create_platform_device(struct sof_platform_priv *priv) +{
- struct snd_sof_pdata *sof_pdata = priv->sof_pdata;
- struct device *dev = sof_pdata->dev;
- priv->pdev_pcm =
platform_device_register_data(dev, "sof-audio", -1,
sof_pdata, sizeof(*sof_pdata));
- if (IS_ERR(priv->pdev_pcm)) {
dev_err(dev, "error: cannot register device sof-audio. Error %d\n",
(int)PTR_ERR(priv->pdev_pcm));
Instead of casting perhaps use %ld?
return PTR_ERR(priv->pdev_pcm);
- }
- return 0;
+} +EXPORT_SYMBOL(sof_create_platform_device);
+void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value) +{ +#ifdef CONFIG_64BIT
- writeq(value, addr);
+#else
- memcpy_toio(addr, &value, sizeof(value));
+#endif
This ifdef sounds strange. Just include io64-nonatomic-lo-hi.h and use readq() w/o limitations.
+} +EXPORT_SYMBOL(sof_io_write64);
+u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr) +{ +#ifdef CONFIG_64BIT
- return readq(addr);
+#else
- u64 val;
- memcpy_fromio(&val, addr, sizeof(val));
- return val;
+#endif
Ditto.
+} +EXPORT_SYMBOL(sof_io_read64);
affected_mask = (1 << (8 * n)) - 1;
/* first read the 32bit data of dest, then change affected
* bytes, and write back to dest. For unaffected bytes, it
* should not be changed
*/
__ioread32_copy(&tmp, dest + m * 4, 1);
tmp &= ~affected_mask;
tmp |= *(u32 *)(src_byte + m * 4) & affected_mask;
__iowrite32_copy(dest + m * 4, &tmp, 1);
Ain't these equivalents to simple ioread32() / iowrite32() ?
On 12/11/18 5:06 PM, Andy Shevchenko wrote:
On Tue, Dec 11, 2018 at 03:23:18PM -0600, Pierre-Louis Bossart wrote:
Helpers to set-up back-ends, create platform devices and common IO/block read/write operations +int sof_bes_setup(struct device *dev, struct snd_sof_dsp_ops *ops,
struct snd_soc_dai_link *links, int link_num,
struct snd_soc_card *card)
+{
- char name[32];
- int i;
- if (!ops || !links || !card)
return -EINVAL;
- /* set up BE dai_links */
- for (i = 0; i < link_num; i++) {
snprintf(name, 32, "NoCodec-%d", i);
sizeof(name) ?
yes
links[i].name = devm_kstrdup(dev, name, GFP_KERNEL);
if (!links[i].name)
return -ENOMEM;
...or better devm_kasprintf().
and yes
links[i].id = i;
links[i].no_pcm = 1;
links[i].cpu_dai_name = ops->drv[i].name;
links[i].platform_name = "sof-audio";
links[i].codec_dai_name = "snd-soc-dummy-dai";
links[i].codec_name = "snd-soc-dummy";
links[i].dpcm_playback = 1;
links[i].dpcm_capture = 1;
- }
- card->dai_link = links;
- card->num_links = link_num;
- return 0;
+} +EXPORT_SYMBOL(sof_bes_setup);
+/* register sof platform device */ +int sof_create_platform_device(struct sof_platform_priv *priv) +{
- struct snd_sof_pdata *sof_pdata = priv->sof_pdata;
- struct device *dev = sof_pdata->dev;
- priv->pdev_pcm =
platform_device_register_data(dev, "sof-audio", -1,
sof_pdata, sizeof(*sof_pdata));
- if (IS_ERR(priv->pdev_pcm)) {
dev_err(dev, "error: cannot register device sof-audio. Error %d\n",
(int)PTR_ERR(priv->pdev_pcm));
Instead of casting perhaps use %ld?
yes
return PTR_ERR(priv->pdev_pcm);
- }
- return 0;
+} +EXPORT_SYMBOL(sof_create_platform_device); +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value) +{ +#ifdef CONFIG_64BIT
- writeq(value, addr);
+#else
- memcpy_toio(addr, &value, sizeof(value));
+#endif
This ifdef sounds strange. Just include io64-nonatomic-lo-hi.h and use readq() w/o limitations.
ah yes. We added readq/writeq following your initial feedback, which broke ARCH=i386 and that was added as a workaround.
+} +EXPORT_SYMBOL(sof_io_write64);
+u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr) +{ +#ifdef CONFIG_64BIT
- return readq(addr);
+#else
- u64 val;
- memcpy_fromio(&val, addr, sizeof(val));
- return val;
+#endif
Ditto.
+} +EXPORT_SYMBOL(sof_io_read64);
affected_mask = (1 << (8 * n)) - 1;
/* first read the 32bit data of dest, then change affected
* bytes, and write back to dest. For unaffected bytes, it
* should not be changed
*/
__ioread32_copy(&tmp, dest + m * 4, 1);
tmp &= ~affected_mask;
tmp |= *(u32 *)(src_byte + m * 4) & affected_mask;
__iowrite32_copy(dest + m * 4, &tmp, 1);
Ain't these equivalents to simple ioread32() / iowrite32() ?
They are. We removed a loop but the this can be further simplified indeed. Thanks for finding this.
participants (7)
-
Andy Shevchenko
-
Daniel Baluta
-
Keyon Jie
-
Mark Brown
-
Pierre-Louis Bossart
-
rander.wang
-
Takashi Iwai