Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/sound/sof/ipc4/header.h
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ struct sof_ipc4_notify_resource_data {
#define SOF_IPC4_DEBUG_SLOT_GDB_STUB 0x42444700
#define SOF_IPC4_DEBUG_SLOT_TELEMETRY 0x4c455400
#define SOF_IPC4_DEBUG_SLOT_BROKEN 0x44414544
#define SOF_IPC4_DEBUG_SLOT_LLEXT_LOAD 0x4C454C44 /* shell llext_load rendezvous */

/**
* struct sof_ipc4_notify_module_data - payload for module notification
Expand Down
37 changes: 28 additions & 9 deletions sound/soc/sof/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,35 @@ config SND_SOC_SOF_DEBUG_FW_GDB
socket, to which GDB can then connect.
If unsure, select "N".

config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT
bool "SOF retain DSP context on any FW exceptions"
config SND_SOC_SOF_SERIAL
tristate "SOF serial port client driver"
depends on SND_SOC_SOF
select SND_SOC_SOF_CLIENT
help
This option keeps the DSP in D0 state so that firmware debug
information can be retained and dumped to userspace.
Say Y if you want to retain DSP context for FW exceptions.
If unsure, select "N".

endif ## SND_SOC_SOF_DEBUG

This enables a virtual serial port (tty) client for the SOF DSP.
The driver registers a ttysof0 device that exchanges shell data
with IPC4 firmware through the ADSP shell debug window using the
Zephyr sys_winstream format. A debugfs file is also created to
expose the shared DSP memory window for inspection and testing.
Only IPC4 is supported. If unsure, select "N".

config SND_SOC_SOF_CLIENT_LLEXT_LOAD
tristate "SOF shell llext_load client"
depends on SND_SOC_SOF
select SND_SOC_SOF_CLIENT
help
Enables the SOF shell llext_load client driver which creates
/sys/kernel/debug/sof/llext_load. Writing a compiled rimage
library binary to this file completes the host side of the
2-step DSP-shell-driven module load handshake:

Step 1 (DSP shell): sof llext_load <name> [lib_id]
Step 2 (Linux host): cat module.ri > /sys/kernel/debug/sof/llext_load

The driver uses the IPC4 LOAD_LIBRARY_PREPARE + LOAD_LIBRARY path
(HDA code-loader DMA) matching the normal library-load flow.
Requires CONFIG_SOF_SHELL_LLEXT_LOAD=y in the DSP firmware build.
If unsure, select "N".
endif ## SND_SOC_SOF_DEVELOPER_SUPPORT

config SND_SOC_SOF
Expand Down
4 changes: 4 additions & 0 deletions sound/soc/sof/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ ifneq ($(CONFIG_SND_SOC_SOF_IPC4),)
snd-sof-probes-y += sof-client-probes-ipc4.o
endif
snd-sof-fw-gdb-objs := sof-client-fw-gdb.o
snd-sof-serial-y := sof-client-serial.o
snd-sof-llext-load-y := sof-client-llext-load.o

snd-sof-nocodec-y := nocodec.o

Expand All @@ -55,6 +57,8 @@ obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o
obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) += snd-sof-ipc-kernel-injector.o
obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o
obj-$(CONFIG_SND_SOC_SOF_DEBUG_FW_GDB) += snd-sof-fw-gdb.o
obj-$(CONFIG_SND_SOC_SOF_SERIAL) += snd-sof-serial.o
obj-$(CONFIG_SND_SOC_SOF_CLIENT_LLEXT_LOAD) += snd-sof-llext-load.o

obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/
obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/
Expand Down
2 changes: 1 addition & 1 deletion sound/soc/sof/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ MODULE_PARM_DESC(boot_timeout,
#endif

/* SOF defaults if not provided by the platform in ms */
#define TIMEOUT_DEFAULT_IPC_MS 500
#define TIMEOUT_DEFAULT_IPC_MS 1500
#define TIMEOUT_DEFAULT_BOOT_MS 2000

/**
Expand Down
60 changes: 58 additions & 2 deletions sound/soc/sof/intel/hda-loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/

#include <linux/firmware.h>
#include <linux/kthread.h>
#include <linux/pci.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/sof.h>
Expand All @@ -31,6 +33,35 @@ module_param(persistent_cl_buffer, bool, 0444);
MODULE_PARM_DESC(persistent_cl_buffer, "Persistent Code Loader DMA buffer "
"(default = Y, use N to force buffer re-allocation)");

/*
* On ACE (MTL/ARL-S) the Intel DMI link between CPU and PCH can enter
* L1 power state when the CPU has no pending DMI transactions. While
* the kernel blocks in wait_event waiting for the firmware to complete
* LOAD_LIBRARY, the CPU may enter deep C-states, DMI enters L1, and
* the code-loader HDA stream stalls permanently (SPIB-limited linear
* streams do not auto-resume after DMI L1 exit).
*
* A small busy-wait polling thread reads the HDA global capabilities
* register every ~50 µs using udelay() so the CPU stays in C0/C1 and
* DMI remains in L0 for the duration of the library DMA transfer.
*/
struct lib_dmi_keepalive {
void __iomem *hda_bar;
struct task_struct *task;
};

static int lib_dmi_keepalive_fn(void *data)
{
struct lib_dmi_keepalive *ka = data;

while (!kthread_should_stop()) {
/* Busy-wait read: keeps CPU in C0 so DMI stays in L0 */
readl(ka->hda_bar);
udelay(50);
}
return 0;
}

static void hda_ssp_set_cbp_cfp(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
Expand Down Expand Up @@ -573,8 +604,33 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev,
*/
msg.primary &= ~SOF_IPC4_MSG_TYPE_MASK;
msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_LOAD_LIBRARY);
msg.primary |= SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(fw_lib->id);
ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
msg.primary |= SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(fw_lib->id);

/*
* Start a short-lived keepalive thread that reads the HDA BAR
* every ~50 µs to keep the DMI link in L0 while sof_ipc_tx_message
* sleeps waiting for the firmware reply. Without this, DMI enters
* L1 and the code-loader host-to-DSP DMA stream stalls permanently.
*/
{
struct lib_dmi_keepalive ka = {
.hda_bar = sdev->bar[HDA_DSP_HDA_BAR],
};

ka.task = kthread_run(lib_dmi_keepalive_fn, &ka,
"sof-lib-dmi-keepalive");
if (IS_ERR(ka.task)) {
dev_warn(sdev->dev,
"%s: failed to start DMI keepalive (%ld)\n",
__func__, PTR_ERR(ka.task));
ka.task = NULL;
}

ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);

if (ka.task)
kthread_stop(ka.task);
}

/* Stop the DMA channel */
ret1 = hda_cl_trigger(sdev->dev, hext_stream, SNDRV_PCM_TRIGGER_STOP);
Expand Down
3 changes: 3 additions & 0 deletions sound/soc/sof/intel/hda-stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,

chunk_size = snd_sgbuf_get_chunk_size(dmab, 0, hstream->bufsize);

dev_info(sdev->dev, "BDL setup: bufsize=%u chunk_size=%u dmab_bytes=%zu\n",
hstream->bufsize, chunk_size, dmab->bytes);

period_bytes = hstream->bufsize;

/*
Expand Down
89 changes: 89 additions & 0 deletions sound/soc/sof/ipc4-loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,95 @@ static int sof_ipc4_load_library(struct snd_sof_dev *sdev, unsigned long lib_id,
return ret;
}

/**
* snd_sof_ipc4_load_library_from_buf - load an IPC4 library from an in-memory buffer
* @sdev: SOF device
* @lib_id: target library slot [1 .. max_libs_count - 1]
* @buf: raw library binary (rimage format with ext-manifest header)
* @size: size of the binary in bytes
*
* Mirrors sof_ipc4_load_library() but accepts an already-loaded buffer instead
* of a filename. Used by the shell llext_load debugfs client driver.
* The HDA DMA path is reused via ipc4_data->load_library().
*
* Return: 0 on success, negative errno on error.
*/
int snd_sof_ipc4_load_library_from_buf(struct snd_sof_dev *sdev, u32 lib_id,
const void *buf, size_t size)
{
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
/*
* Use a devm-allocated firmware wrapper so fw_lib->sof_fw.fw remains
* valid for the lifetime of the device. A stack-allocated struct would
* create a use-after-return when the xa_insert'd fw_lib is later
* accessed (e.g. during library reload after S3). The caller's @buf
* must also remain valid for the device lifetime; callers that pass a
* vmalloc'd buffer should not free it — devm cleanup handles it.
*/
struct firmware *fake_fw;
struct sof_ipc4_fw_library *fw_lib;
ssize_t payload_offset;
int i, ret;

if (!ipc4_data->load_library) {
dev_err(sdev->dev, "Library loading not supported on this platform\n");
return -EOPNOTSUPP;
}

if (!lib_id || lib_id >= ipc4_data->max_libs_count) {
dev_err(sdev->dev, "Invalid lib_id %u (max %u)\n",
lib_id, ipc4_data->max_libs_count - 1);
return -EINVAL;
}

fake_fw = devm_kzalloc(sdev->dev, sizeof(*fake_fw), GFP_KERNEL);
if (!fake_fw)
return -ENOMEM;
fake_fw->data = buf;
fake_fw->size = size;

fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL);
if (!fw_lib)
return -ENOMEM;

fw_lib->sof_fw.fw = fake_fw;
fw_lib->name = "shell-llext";

payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib);
if (payload_offset <= 0) {
ret = payload_offset ? (int)payload_offset : -EINVAL;
goto free_fw_lib;
}

fw_lib->sof_fw.payload_offset = payload_offset;
fw_lib->id = lib_id;

for (i = 0; i < fw_lib->num_modules; i++)
fw_lib->modules[i].man4_module_entry.id |=
(lib_id << SOF_IPC4_MOD_LIB_ID_SHIFT);

ret = ipc4_data->load_library(sdev, fw_lib, false);
if (ret)
goto free_modules;

ret = xa_insert(&ipc4_data->fw_lib_xa, lib_id, fw_lib, GFP_KERNEL);
if (unlikely(ret)) {
dev_err(sdev->dev, "Library ID %u already registered\n", lib_id);
goto free_modules;
}

dev_info(sdev->dev, "Shell llext loaded as lib_id=%u (%zu bytes)\n",
lib_id, size);
return 0;

free_modules:
devm_kfree(sdev->dev, fw_lib->modules);
free_fw_lib:
devm_kfree(sdev->dev, fw_lib);
return ret;
}
EXPORT_SYMBOL_NS_GPL(snd_sof_ipc4_load_library_from_buf, "SND_SOC_SOF");

/**
* sof_ipc4_complete_split_release - loads the library parts of a split firmware
* @sdev: SOF device
Expand Down
3 changes: 3 additions & 0 deletions sound/soc/sof/ipc4-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core);
int sof_ipc4_complete_split_release(struct snd_sof_dev *sdev);
int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev);
int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev);

int snd_sof_ipc4_load_library_from_buf(struct snd_sof_dev *sdev, u32 lib_id,
const void *buf, size_t size);
struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
const guid_t *uuid);

Expand Down
44 changes: 44 additions & 0 deletions sound/soc/sof/shell-llext-shm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2024 Intel Corporation
*
* Author: Kai Vehmanen <kai.vehmanen@linux.intel.com>
*/

/*
* Shared mailbox layout for the DSP shell "llext_load" command.
* Binary layout must match sof/zephyr/include/sof/shell_llext_load.h on
* the DSP side. Both files must be updated together.
*
* See shell_llext_load.h for the full protocol description.
*/

#ifndef __SOF_SHELL_LLEXT_SHM_H__
#define __SOF_SHELL_LLEXT_SHM_H__

#include <linux/types.h>

#define SOF_SHELL_LLEXT_MAGIC 0x4C454C44U /* 'LELD' */

enum sof_shell_llext_state {
SOF_SHELL_LLEXT_IDLE = 0,
SOF_SHELL_LLEXT_REQUESTING = 1, /* DSP ready, waiting for host DMA */
SOF_SHELL_LLEXT_DMA_ACTIVE = 2, /* host: DMA in progress */
SOF_SHELL_LLEXT_DMA_DONE = 3, /* host: DMA + IPC load complete */
SOF_SHELL_LLEXT_ERROR = 4, /* host: load failed */
};

struct sof_shell_llext_slot {
__u32 magic;
__u32 state;
__u32 lib_id;
__u32 xfer_bytes;
__s32 result;
__u32 reserved[3];
char name[64];
} __packed;

#endif /* __SOF_SHELL_LLEXT_SHM_H__ */
Loading
Loading