Skip to content

Commit 37ccb6b

Browse files
committed
ASoC: SOF: add shell llext_load client driver for interactive module loading
Implements the Linux host side of the 2-step DSP-shell-driven llext module load handshake introduced by the companion SOF firmware patch. New files: sound/soc/sof/shell-llext-shm.h — shared mailbox struct and state enum (binary layout must match zephyr/include/sof/shell_llext_load.h). sound/soc/sof/sof-client-llext-load.c — SOF auxiliary client driver that creates /sys/kernel/debug/sof/llext_load. Writing a raw rimage binary to this file drives the complete host-side flow: 1. Locate the ADSP_DW_SLOT_LLEXT_LOAD debug window slot populated by the DSP 'sof llext_load' shell command. 2. Set state DMA_ACTIVE, copy the user buffer via vmalloc. 3. Call snd_sof_ipc4_load_library_from_buf() → IPC4 LOAD_LIBRARY DMA. 4. Write DMA_DONE (or ERROR + errno) back to the slot. Other changes: ipc4-loader.c / ipc4-priv.h — snd_sof_ipc4_load_library_from_buf(): in- memory variant of sof_ipc4_load_library(), exported as SND_SOC_SOF so the client module can call it. sof-client.h — declare sof_client_ipc4_load_library_buf() helper. sof-client.c — register/unregister the llext-load auxiliary client device (IPC4 only). include/sound/sof/ipc4/header.h — add SOF_IPC4_DEBUG_SLOT_LLEXT_LOAD. Kconfig — add SND_SOC_SOF_CLIENT_LLEXT_LOAD tristate; reorganise debug menu so the new option sits alongside the serial client. Makefile — wire up snd-sof-llext-load module. core.c — increase default IPC timeout 500 → 1500 ms to accommodate the longer HDA DMA transfer time for library binaries. intel/hda-loader.c — add lib_dmi_keepalive kthread: keeps the CPU in C0 while sof_ipc_tx_message() sleeps, preventing DMI L1 entry which stalls the code-loader HDA stream on ACE (MTL/ARL-S). intel/hda-stream.c — add dev_info BDL trace for DMA buffer diagnostics. Usage: Step 1 (DSP shell): sof llext_load mymodule 1 Step 2 (Linux host): cat mymodule.ri > /sys/kernel/debug/sof/llext_load Requires CONFIG_SOF_SHELL_LLEXT_LOAD=y in the DSP firmware build. Requires matching Zephyr patch: intel/adsp: add ADSP_DW_SLOT_LLEXT_LOAD. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
1 parent de30f7a commit 37ccb6b

12 files changed

Lines changed: 583 additions & 26 deletions

File tree

include/sound/sof/ipc4/header.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ struct sof_ipc4_notify_resource_data {
590590
#define SOF_IPC4_DEBUG_SLOT_GDB_STUB 0x42444700
591591
#define SOF_IPC4_DEBUG_SLOT_TELEMETRY 0x4c455400
592592
#define SOF_IPC4_DEBUG_SLOT_BROKEN 0x44414544
593+
#define SOF_IPC4_DEBUG_SLOT_LLEXT_LOAD 0x4C454C44 /* shell llext_load rendezvous */
593594

594595
/**
595596
* struct sof_ipc4_notify_module_data - payload for module notification

sound/soc/sof/Kconfig

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -283,16 +283,23 @@ config SND_SOC_SOF_SERIAL
283283
expose the shared DSP memory window for inspection and testing.
284284
Only IPC4 is supported. If unsure, select "N".
285285

286-
config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT
287-
bool "SOF retain DSP context on any FW exceptions"
288-
help
289-
This option keeps the DSP in D0 state so that firmware debug
290-
information can be retained and dumped to userspace.
291-
Say Y if you want to retain DSP context for FW exceptions.
292-
If unsure, select "N".
293-
294-
endif ## SND_SOC_SOF_DEBUG
295-
286+
config SND_SOC_SOF_CLIENT_LLEXT_LOAD
287+
tristate "SOF shell llext_load client"
288+
depends on SND_SOC_SOF
289+
select SND_SOC_SOF_CLIENT
290+
help
291+
Enables the SOF shell llext_load client driver which creates
292+
/sys/kernel/debug/sof/llext_load. Writing a compiled rimage
293+
library binary to this file completes the host side of the
294+
2-step DSP-shell-driven module load handshake:
295+
296+
Step 1 (DSP shell): sof llext_load <name> [lib_id]
297+
Step 2 (Linux host): cat module.ri > /sys/kernel/debug/sof/llext_load
298+
299+
The driver uses the IPC4 LOAD_LIBRARY_PREPARE + LOAD_LIBRARY path
300+
(HDA code-loader DMA) matching the normal library-load flow.
301+
Requires CONFIG_SOF_SHELL_LLEXT_LOAD=y in the DSP firmware build.
302+
If unsure, select "N".
296303
endif ## SND_SOC_SOF_DEVELOPER_SUPPORT
297304

298305
config SND_SOC_SOF

sound/soc/sof/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ snd-sof-probes-y += sof-client-probes-ipc4.o
3737
endif
3838
snd-sof-fw-gdb-objs := sof-client-fw-gdb.o
3939
snd-sof-serial-y := sof-client-serial.o
40+
snd-sof-llext-load-y := sof-client-llext-load.o
4041

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

@@ -57,6 +58,7 @@ obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) += snd-sof-ipc-kernel-inject
5758
obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o
5859
obj-$(CONFIG_SND_SOC_SOF_DEBUG_FW_GDB) += snd-sof-fw-gdb.o
5960
obj-$(CONFIG_SND_SOC_SOF_SERIAL) += snd-sof-serial.o
61+
obj-$(CONFIG_SND_SOC_SOF_CLIENT_LLEXT_LOAD) += snd-sof-llext-load.o
6062

6163
obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/
6264
obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/

sound/soc/sof/core.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ MODULE_PARM_DESC(boot_timeout,
6161
#endif
6262

6363
/* SOF defaults if not provided by the platform in ms */
64-
#define TIMEOUT_DEFAULT_IPC_MS 500
64+
#define TIMEOUT_DEFAULT_IPC_MS 1500
6565
#define TIMEOUT_DEFAULT_BOOT_MS 2000
6666

6767
/**

sound/soc/sof/intel/hda-loader.c

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717

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

36+
/*
37+
* On ACE (MTL/ARL-S) the Intel DMI link between CPU and PCH can enter
38+
* L1 power state when the CPU has no pending DMI transactions. While
39+
* the kernel blocks in wait_event waiting for the firmware to complete
40+
* LOAD_LIBRARY, the CPU may enter deep C-states, DMI enters L1, and
41+
* the code-loader HDA stream stalls permanently (SPIB-limited linear
42+
* streams do not auto-resume after DMI L1 exit).
43+
*
44+
* A small busy-wait polling thread reads the HDA global capabilities
45+
* register every ~50 µs using udelay() so the CPU stays in C0/C1 and
46+
* DMI remains in L0 for the duration of the library DMA transfer.
47+
*/
48+
struct lib_dmi_keepalive {
49+
void __iomem *hda_bar;
50+
struct task_struct *task;
51+
};
52+
53+
static int lib_dmi_keepalive_fn(void *data)
54+
{
55+
struct lib_dmi_keepalive *ka = data;
56+
57+
while (!kthread_should_stop()) {
58+
/* Busy-wait read: keeps CPU in C0 so DMI stays in L0 */
59+
readl(ka->hda_bar);
60+
udelay(50);
61+
}
62+
return 0;
63+
}
64+
3465
static void hda_ssp_set_cbp_cfp(struct snd_sof_dev *sdev)
3566
{
3667
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
@@ -573,8 +604,33 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev,
573604
*/
574605
msg.primary &= ~SOF_IPC4_MSG_TYPE_MASK;
575606
msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_LOAD_LIBRARY);
576-
msg.primary |= SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(fw_lib->id);
577-
ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
607+
msg.primary |= SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(fw_lib->id);
608+
609+
/*
610+
* Start a short-lived keepalive thread that reads the HDA BAR
611+
* every ~50 µs to keep the DMI link in L0 while sof_ipc_tx_message
612+
* sleeps waiting for the firmware reply. Without this, DMI enters
613+
* L1 and the code-loader host-to-DSP DMA stream stalls permanently.
614+
*/
615+
{
616+
struct lib_dmi_keepalive ka = {
617+
.hda_bar = sdev->bar[HDA_DSP_HDA_BAR],
618+
};
619+
620+
ka.task = kthread_run(lib_dmi_keepalive_fn, &ka,
621+
"sof-lib-dmi-keepalive");
622+
if (IS_ERR(ka.task)) {
623+
dev_warn(sdev->dev,
624+
"%s: failed to start DMI keepalive (%ld)\n",
625+
__func__, PTR_ERR(ka.task));
626+
ka.task = NULL;
627+
}
628+
629+
ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
630+
631+
if (ka.task)
632+
kthread_stop(ka.task);
633+
}
578634

579635
/* Stop the DMA channel */
580636
ret1 = hda_cl_trigger(sdev->dev, hext_stream, SNDRV_PCM_TRIGGER_STOP);

sound/soc/sof/intel/hda-stream.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,
127127

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

130+
dev_info(sdev->dev, "BDL setup: bufsize=%u chunk_size=%u dmab_bytes=%zu\n",
131+
hstream->bufsize, chunk_size, dmab->bytes);
132+
130133
period_bytes = hstream->bufsize;
131134

132135
/*

sound/soc/sof/ipc4-loader.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,95 @@ static int sof_ipc4_load_library(struct snd_sof_dev *sdev, unsigned long lib_id,
252252
return ret;
253253
}
254254

255+
/**
256+
* snd_sof_ipc4_load_library_from_buf - load an IPC4 library from an in-memory buffer
257+
* @sdev: SOF device
258+
* @lib_id: target library slot [1 .. max_libs_count - 1]
259+
* @buf: raw library binary (rimage format with ext-manifest header)
260+
* @size: size of the binary in bytes
261+
*
262+
* Mirrors sof_ipc4_load_library() but accepts an already-loaded buffer instead
263+
* of a filename. Used by the shell llext_load debugfs client driver.
264+
* The HDA DMA path is reused via ipc4_data->load_library().
265+
*
266+
* Return: 0 on success, negative errno on error.
267+
*/
268+
int snd_sof_ipc4_load_library_from_buf(struct snd_sof_dev *sdev, u32 lib_id,
269+
const void *buf, size_t size)
270+
{
271+
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
272+
/*
273+
* Use a devm-allocated firmware wrapper so fw_lib->sof_fw.fw remains
274+
* valid for the lifetime of the device. A stack-allocated struct would
275+
* create a use-after-return when the xa_insert'd fw_lib is later
276+
* accessed (e.g. during library reload after S3). The caller's @buf
277+
* must also remain valid for the device lifetime; callers that pass a
278+
* vmalloc'd buffer should not free it — devm cleanup handles it.
279+
*/
280+
struct firmware *fake_fw;
281+
struct sof_ipc4_fw_library *fw_lib;
282+
ssize_t payload_offset;
283+
int i, ret;
284+
285+
if (!ipc4_data->load_library) {
286+
dev_err(sdev->dev, "Library loading not supported on this platform\n");
287+
return -EOPNOTSUPP;
288+
}
289+
290+
if (!lib_id || lib_id >= ipc4_data->max_libs_count) {
291+
dev_err(sdev->dev, "Invalid lib_id %u (max %u)\n",
292+
lib_id, ipc4_data->max_libs_count - 1);
293+
return -EINVAL;
294+
}
295+
296+
fake_fw = devm_kzalloc(sdev->dev, sizeof(*fake_fw), GFP_KERNEL);
297+
if (!fake_fw)
298+
return -ENOMEM;
299+
fake_fw->data = buf;
300+
fake_fw->size = size;
301+
302+
fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL);
303+
if (!fw_lib)
304+
return -ENOMEM;
305+
306+
fw_lib->sof_fw.fw = fake_fw;
307+
fw_lib->name = "shell-llext";
308+
309+
payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib);
310+
if (payload_offset <= 0) {
311+
ret = payload_offset ? (int)payload_offset : -EINVAL;
312+
goto free_fw_lib;
313+
}
314+
315+
fw_lib->sof_fw.payload_offset = payload_offset;
316+
fw_lib->id = lib_id;
317+
318+
for (i = 0; i < fw_lib->num_modules; i++)
319+
fw_lib->modules[i].man4_module_entry.id |=
320+
(lib_id << SOF_IPC4_MOD_LIB_ID_SHIFT);
321+
322+
ret = ipc4_data->load_library(sdev, fw_lib, false);
323+
if (ret)
324+
goto free_modules;
325+
326+
ret = xa_insert(&ipc4_data->fw_lib_xa, lib_id, fw_lib, GFP_KERNEL);
327+
if (unlikely(ret)) {
328+
dev_err(sdev->dev, "Library ID %u already registered\n", lib_id);
329+
goto free_modules;
330+
}
331+
332+
dev_info(sdev->dev, "Shell llext loaded as lib_id=%u (%zu bytes)\n",
333+
lib_id, size);
334+
return 0;
335+
336+
free_modules:
337+
devm_kfree(sdev->dev, fw_lib->modules);
338+
free_fw_lib:
339+
devm_kfree(sdev->dev, fw_lib);
340+
return ret;
341+
}
342+
EXPORT_SYMBOL_NS_GPL(snd_sof_ipc4_load_library_from_buf, "SND_SOC_SOF");
343+
255344
/**
256345
* sof_ipc4_complete_split_release - loads the library parts of a split firmware
257346
* @sdev: SOF device

sound/soc/sof/ipc4-priv.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core);
110110
int sof_ipc4_complete_split_release(struct snd_sof_dev *sdev);
111111
int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev);
112112
int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev);
113+
114+
int snd_sof_ipc4_load_library_from_buf(struct snd_sof_dev *sdev, u32 lib_id,
115+
const void *buf, size_t size);
113116
struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
114117
const guid_t *uuid);
115118

sound/soc/sof/shell-llext-shm.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
2+
/*
3+
* This file is provided under a dual BSD/GPLv2 license. When using or
4+
* redistributing this file, you may do so under either license.
5+
*
6+
* Copyright(c) 2024 Intel Corporation
7+
*
8+
* Author: Kai Vehmanen <kai.vehmanen@linux.intel.com>
9+
*/
10+
11+
/*
12+
* Shared mailbox layout for the DSP shell "llext_load" command.
13+
* Binary layout must match sof/zephyr/include/sof/shell_llext_load.h on
14+
* the DSP side. Both files must be updated together.
15+
*
16+
* See shell_llext_load.h for the full protocol description.
17+
*/
18+
19+
#ifndef __SOF_SHELL_LLEXT_SHM_H__
20+
#define __SOF_SHELL_LLEXT_SHM_H__
21+
22+
#include <linux/types.h>
23+
24+
#define SOF_SHELL_LLEXT_MAGIC 0x4C454C44U /* 'LELD' */
25+
26+
enum sof_shell_llext_state {
27+
SOF_SHELL_LLEXT_IDLE = 0,
28+
SOF_SHELL_LLEXT_REQUESTING = 1, /* DSP ready, waiting for host DMA */
29+
SOF_SHELL_LLEXT_DMA_ACTIVE = 2, /* host: DMA in progress */
30+
SOF_SHELL_LLEXT_DMA_DONE = 3, /* host: DMA + IPC load complete */
31+
SOF_SHELL_LLEXT_ERROR = 4, /* host: load failed */
32+
};
33+
34+
struct sof_shell_llext_slot {
35+
__u32 magic;
36+
__u32 state;
37+
__u32 lib_id;
38+
__u32 xfer_bytes;
39+
__s32 result;
40+
__u32 reserved[3];
41+
char name[64];
42+
} __packed;
43+
44+
#endif /* __SOF_SHELL_LLEXT_SHM_H__ */

0 commit comments

Comments
 (0)