Skip to content

Commit b54c365

Browse files
committed
shell: add dai_list and dma_status commands
Add two read-only introspection commands to the SOF shell: - 'sof dai_list' iterates dai_get_device_list() and prints, per DAI, the Zephyr device name, decoded type (ssp/dmic/hda/alh/uaol/sai/ esai/...), index, current channel count, sample rate, format and word size from dai_config_get(), plus per-direction fifo address, fifo depth, DMA handshake id and stream id from dai_get_properties(). - 'sof dma_status [dma_idx [chan]]' walks dma_info_get()->dma_array to list every SOF-registered DMAC (id, channel count, busy count, caps/devs bitmasks, base, Zephyr device name) and uses sof_dma_get_status() for per-channel state (busy, direction, pending/free bytes, read/write positions, total_copied). Two new Kconfigs (default y, depend on SHELL && ZEPHYR_NATIVE_DRIVERS): CONFIG_SOF_SHELL_DAI_LIST CONFIG_SOF_SHELL_DMA_STATUS Pairs with 'sof pipeline_state' for diagnosing P2M/M2P paths without host-side debug. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
1 parent 229bfdd commit b54c365

3 files changed

Lines changed: 287 additions & 3 deletions

File tree

zephyr/Kconfig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,42 @@ config SOF_SHELL_PERF_STATUS
561561
reset_performance_counters(); 'stop'/'pause' just update the
562562
state) - useful for taking before/after snapshots without IPC.
563563

564+
config SOF_SHELL_DAI_LIST
565+
bool "DAI introspection command"
566+
default y
567+
depends on SHELL && ZEPHYR_NATIVE_DRIVERS
568+
help
569+
Enables 'sof dai_list' which iterates dai_get_device_list()
570+
and prints, per DAI, the Zephyr device name, decoded type
571+
(ssp/dmic/hda/alh/uaol/sai/esai/...), index, current channel
572+
count, sample rate, format and word size from
573+
dai_config_get(), plus per-direction fifo address, fifo depth,
574+
DMA handshake id and stream id from dai_get_properties().
575+
576+
Useful for verifying which DAIs were registered for a topology
577+
without going through host-side debug.
578+
579+
config SOF_SHELL_DMA_STATUS
580+
bool "DMA controller / channel status command"
581+
default y
582+
depends on SHELL && ZEPHYR_NATIVE_DRIVERS
583+
help
584+
Enables 'sof dma_status [dma_idx [chan]]':
585+
586+
'sof dma_status' - list every DMA controller
587+
registered with SOF (id,
588+
channel count, busy count,
589+
caps/devs bitmasks, base,
590+
Zephyr device name).
591+
'sof dma_status <dma_idx>' - per-channel status for one
592+
controller.
593+
'sof dma_status <dma_idx> <chan>' - status for one channel.
594+
595+
Per-channel status comes from sof_dma_get_status() (Zephyr
596+
dma_get_status()) and shows busy/idle, direction, pending and
597+
free bytes, read/write positions and total_copied. Pairs well
598+
with 'sof dai_list' for diagnosing P2M/M2P paths.
599+
564600
endmenu # SOF shell commands
565601

566602
config SOF_VREGIONS

zephyr/shell.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ debug or testing. Items get ticked off as commands land on `topic/shell`.
7676
| Power management | `pm_state`, `pm_force <state>`, `pg_status`, `idle_stats` | TODO |
7777
| Cache | `dcache_flush <addr> <size>`, `dcache_inv`, `icache_inv` | TODO |
7878
| Watchdog | `wdt_status`, `wdt_kick`, `wdt_disable` | TODO |
79-
| DAI / link control | `dai_list`, `dai_status <type> <idx>`, `dai_trigger`, `dai_loopback` | TODO |
80-
| DMA | `dma_list`, `dma_chan_status <dma> <chan>`, `dma_stop` | TODO |
79+
| **DAI / link control** | `dai_list`, `dai_status <type> <idx>`, `dai_trigger`, `dai_loopback` | **DONE (task 7)** &mdash; `dai_list` covers introspection; trigger/loopback deferred (writeable, needs careful tplg coordination). |
80+
| **DMA** | `dma_list`, `dma_chan_status <dma> <chan>`, `dma_stop` | **DONE (task 7)** &mdash; `dma_status` covers list+per-channel. `dma_stop` deferred (would corrupt active stream). |
8181

8282
### Debug
8383

@@ -115,7 +115,7 @@ debug or testing. Items get ticked off as commands land on `topic/shell`.
115115
4. **`log_status` / `mtrace_dump`** &mdash; DONE.
116116
5. **`mailbox_hex` / `dbgwin_dump`** &mdash; DONE (was originally `crash_log`/`bt`; pivoted because SOF panic.c isn't built on Zephyr and `bt` of a running CPU from itself isn't meaningful).
117117
6. **`perf_status`** &mdash; DONE.
118-
7. `dai_list` / `dma_chan_status` &mdash; link/DMA blind spot.
118+
7. **`dai_list` / `dma_status`** &mdash; DONE.
119119
8. `kctl_get/set` &mdash; today only doable via tplg/IPC.
120120

121121
---
@@ -349,3 +349,46 @@ debug or testing. Items get ticked off as commands land on `topic/shell`.
349349
one row per occupied slot.
350350
- Zephyr already provides `kernel cpu_load` and `kernel threads`;
351351
`cpu_load` was therefore not duplicated here.
352+
353+
## Task 7 &mdash; `dai_list` / `dma_status`
354+
355+
### Commands
356+
357+
| Command | Description |
358+
|---|---|
359+
| `sof dai_list` | Iterate `dai_get_device_list()` and print, per DAI, the Zephyr device name, decoded type (ssp/dmic/hda/alh/uaol/sai/esai/...), index, current channel count, sample rate, format and word size, plus per-direction fifo address, fifo depth, DMA handshake id and stream id. |
360+
| `sof dma_status` | List every SOF DMA controller (`dma_info_get()`), with id, channel count, busy count, caps/devs bitmasks, base address and Zephyr device name. |
361+
| `sof dma_status <dma>` | Walk all channels of one controller, calling `sof_dma_get_status()` on each. |
362+
| `sof dma_status <dma> <chan>` | Status of a single channel: busy/idle, direction, pending/free bytes, read/write positions, total_copied. |
363+
364+
### Implementation
365+
366+
- `dai_list` uses `dai_get_device_list()` from
367+
[src/include/sof/lib/dai-zephyr.h](src/include/sof/lib/dai-zephyr.h)
368+
and the Zephyr DAI API
369+
(`dai_config_get()`, `dai_get_properties()`); it falls back to
370+
TX-only or RX-only `config_get()` when `DAI_DIR_BOTH` is not
371+
supported by a driver.
372+
- `dma_status` walks `sof_get()->dma_info->dma_array[]` (via
373+
`dma_info_get()` from
374+
[zephyr/include/sof/lib/dma.h](zephyr/include/sof/lib/dma.h))
375+
and calls `sof_dma_get_status()` per channel; this re-uses the
376+
same Zephyr `dma_get_status()` path the DSP itself uses, so the
377+
numbers exactly match runtime audio state.
378+
- Two new Kconfigs (default `y`, both depend on
379+
`ZEPHYR_NATIVE_DRIVERS`):
380+
- `CONFIG_SOF_SHELL_DAI_LIST`
381+
- `CONFIG_SOF_SHELL_DMA_STATUS`
382+
- Shell commands in [zephyr/sof_shell.c](zephyr/sof_shell.c).
383+
384+
### Notes / follow-ups
385+
386+
- Read-only on purpose. `dai_trigger`, `dai_loopback`, `dma_stop`
387+
were intentionally not added in this pass &mdash; they would corrupt
388+
in-flight streams and require coordination with topology / IPC
389+
state machines. Pair with the existing `pipeline_state` (gated by
390+
`CONFIG_SOF_SHELL_PIPELINE_OPS`) for stream control.
391+
- `dma_status` only iterates SOF-registered DMACs. Zephyr also
392+
ships its own `dma` shell when `CONFIG_DMA_SHELL=y`, but that one
393+
walks Zephyr DMA devices and exposes raw register pokes, so the
394+
two are complementary.

zephyr/sof_shell.c

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,197 @@ __cold static int cmd_sof_perf_status(const struct shell *sh,
22362236

22372237
#endif /* CONFIG_SOF_SHELL_PERF_STATUS */
22382238

2239+
#if CONFIG_SOF_SHELL_DAI_LIST || CONFIG_SOF_SHELL_DMA_STATUS
2240+
#include <sof/lib/dai.h>
2241+
#include <zephyr/drivers/dai.h>
2242+
#include <zephyr/drivers/dma.h>
2243+
#endif
2244+
2245+
#if CONFIG_SOF_SHELL_DAI_LIST
2246+
2247+
static const char *zephyr_dai_type_str(int t)
2248+
{
2249+
switch (t) {
2250+
case DAI_LEGACY_I2S: return "i2s";
2251+
case DAI_INTEL_SSP: return "ssp";
2252+
case DAI_INTEL_DMIC: return "dmic";
2253+
case DAI_INTEL_HDA: return "hda";
2254+
case DAI_INTEL_ALH: return "alh";
2255+
case DAI_IMX_SAI: return "sai";
2256+
case DAI_IMX_ESAI: return "esai";
2257+
case DAI_AMD_BT: return "amd_bt";
2258+
case DAI_AMD_SP: return "amd_sp";
2259+
case DAI_AMD_DMIC: return "amd_dmic";
2260+
case DAI_MEDIATEK_AFE: return "mtk_afe";
2261+
case DAI_INTEL_SSP_NHLT: return "ssp_nhlt";
2262+
case DAI_INTEL_DMIC_NHLT: return "dmic_nhlt";
2263+
case DAI_INTEL_HDA_NHLT: return "hda_nhlt";
2264+
case DAI_INTEL_ALH_NHLT: return "alh_nhlt";
2265+
case DAI_IMX_MICFIL: return "micfil";
2266+
case DAI_INTEL_UAOL: return "uaol";
2267+
case DAI_AMD_SDW: return "amd_sdw";
2268+
default: return "?";
2269+
}
2270+
}
2271+
2272+
__cold static int cmd_sof_dai_list(const struct shell *sh,
2273+
size_t argc, char *argv[])
2274+
{
2275+
const struct device **list;
2276+
size_t count = 0;
2277+
int i;
2278+
2279+
list = dai_get_device_list(&count);
2280+
if (!list || !count) {
2281+
shell_print(sh, "No DAIs registered");
2282+
return 0;
2283+
}
2284+
2285+
shell_print(sh, "%zu DAI(s) registered:", count);
2286+
shell_print(sh, " idx name type index channels rate fmt word");
2287+
for (i = 0; i < count; i++) {
2288+
const struct device *dev = list[i];
2289+
struct dai_config cfg = {0};
2290+
const struct dai_properties *props;
2291+
2292+
if (dai_config_get(dev, &cfg, DAI_DIR_BOTH)) {
2293+
/* try TX-only then RX-only */
2294+
if (dai_config_get(dev, &cfg, DAI_DIR_TX) &&
2295+
dai_config_get(dev, &cfg, DAI_DIR_RX)) {
2296+
shell_print(sh, " %3d %-26s (config_get failed)",
2297+
i, dev->name ? dev->name : "?");
2298+
continue;
2299+
}
2300+
}
2301+
2302+
shell_print(sh,
2303+
" %3d %-26s %-10s %5u %8u %7u 0x%04x %4u",
2304+
i, dev->name ? dev->name : "?",
2305+
zephyr_dai_type_str(cfg.type), cfg.dai_index,
2306+
cfg.channels, cfg.rate, cfg.format, cfg.word_size);
2307+
2308+
props = dai_get_properties(dev, DAI_DIR_TX, 0);
2309+
if (props)
2310+
shell_print(sh,
2311+
" TX: fifo 0x%08x depth %u hs %u stream %d",
2312+
props->fifo_address, props->fifo_depth,
2313+
props->dma_hs_id, props->stream_id);
2314+
props = dai_get_properties(dev, DAI_DIR_RX, 0);
2315+
if (props)
2316+
shell_print(sh,
2317+
" RX: fifo 0x%08x depth %u hs %u stream %d",
2318+
props->fifo_address, props->fifo_depth,
2319+
props->dma_hs_id, props->stream_id);
2320+
}
2321+
2322+
return 0;
2323+
}
2324+
2325+
#endif /* CONFIG_SOF_SHELL_DAI_LIST */
2326+
2327+
#if CONFIG_SOF_SHELL_DMA_STATUS
2328+
2329+
static const char *dma_dir_str(enum dma_channel_direction d)
2330+
{
2331+
switch (d) {
2332+
case MEMORY_TO_MEMORY: return "M2M";
2333+
case MEMORY_TO_PERIPHERAL: return "M2P";
2334+
case PERIPHERAL_TO_MEMORY: return "P2M";
2335+
case PERIPHERAL_TO_PERIPHERAL: return "P2P";
2336+
case HOST_TO_MEMORY: return "H2M";
2337+
case MEMORY_TO_HOST: return "M2H";
2338+
default: return "?";
2339+
}
2340+
}
2341+
2342+
static void dma_print_one(const struct shell *sh, struct sof_dma *dma,
2343+
int dma_idx, int chan)
2344+
{
2345+
struct dma_status st = {0};
2346+
int ret = sof_dma_get_status(dma, chan, &st);
2347+
2348+
if (ret) {
2349+
shell_print(sh, " dma %d ch %d: get_status -> %d",
2350+
dma_idx, chan, ret);
2351+
return;
2352+
}
2353+
shell_print(sh,
2354+
" dma %d ch %d: %s dir=%s pending=%u free=%u rd=%u wr=%u total=%llu",
2355+
dma_idx, chan, st.busy ? "BUSY" : "idle",
2356+
dma_dir_str(st.dir), st.pending_length, st.free,
2357+
st.read_position, st.write_position,
2358+
(unsigned long long)st.total_copied);
2359+
}
2360+
2361+
__cold static int cmd_sof_dma_status(const struct shell *sh,
2362+
size_t argc, char *argv[])
2363+
{
2364+
const struct dma_info *info = dma_info_get();
2365+
struct sof_dma *dma;
2366+
int i, ch;
2367+
2368+
if (!info || !info->num_dmas) {
2369+
shell_print(sh, "No DMA controllers registered");
2370+
return 0;
2371+
}
2372+
2373+
if (argc == 1) {
2374+
shell_print(sh, "%zu DMA controller(s):", info->num_dmas);
2375+
shell_print(sh, " idx id channels busy caps devs base");
2376+
for (i = 0; i < info->num_dmas; i++) {
2377+
dma = &info->dma_array[i];
2378+
shell_print(sh,
2379+
" %3d %2u %8u %4u 0x%04x 0x%04x 0x%08x (%s)",
2380+
i, dma->plat_data.id,
2381+
dma->plat_data.channels,
2382+
(unsigned int)atomic_get(&dma->num_channels_busy),
2383+
dma->plat_data.caps,
2384+
dma->plat_data.devs,
2385+
dma->plat_data.base,
2386+
dma->z_dev && dma->z_dev->name ?
2387+
dma->z_dev->name : "?");
2388+
}
2389+
shell_print(sh,
2390+
"Usage: sof dma_status <dma_idx> [chan] (omit chan to walk all)");
2391+
return 0;
2392+
}
2393+
2394+
{
2395+
char *end = NULL;
2396+
long idx = strtol(argv[1], &end, 0);
2397+
2398+
if (end == argv[1] || idx < 0 || idx >= (long)info->num_dmas) {
2399+
shell_print(sh, "Bad DMA index (0..%zu)",
2400+
info->num_dmas - 1);
2401+
return -EINVAL;
2402+
}
2403+
dma = &info->dma_array[idx];
2404+
2405+
if (argc > 2) {
2406+
ch = strtol(argv[2], &end, 0);
2407+
if (end == argv[2] || ch < 0 ||
2408+
ch >= (int)dma->plat_data.channels) {
2409+
shell_print(sh, "Bad channel (0..%u)",
2410+
dma->plat_data.channels - 1);
2411+
return -EINVAL;
2412+
}
2413+
dma_print_one(sh, dma, (int)idx, ch);
2414+
return 0;
2415+
}
2416+
2417+
shell_print(sh, "DMA %ld (%s): %u channels",
2418+
idx, dma->z_dev && dma->z_dev->name ?
2419+
dma->z_dev->name : "?",
2420+
dma->plat_data.channels);
2421+
for (ch = 0; ch < (int)dma->plat_data.channels; ch++)
2422+
dma_print_one(sh, dma, (int)idx, ch);
2423+
}
2424+
2425+
return 0;
2426+
}
2427+
2428+
#endif /* CONFIG_SOF_SHELL_DMA_STATUS */
2429+
22392430
SHELL_STATIC_SUBCMD_SET_CREATE(sof_commands,
22402431
SHELL_CMD(test_inject_sched_gap, NULL,
22412432
"Inject a gap to audio scheduling\n",
@@ -2443,6 +2634,20 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sof_commands,
24432634
cmd_sof_perf_status, 1, 1),
24442635
#endif
24452636

2637+
#if CONFIG_SOF_SHELL_DAI_LIST
2638+
SHELL_CMD(dai_list, NULL,
2639+
"List all registered DAIs (name, type, channels, rate, "
2640+
"fifo, hs)\n",
2641+
cmd_sof_dai_list),
2642+
#endif
2643+
2644+
#if CONFIG_SOF_SHELL_DMA_STATUS
2645+
SHELL_CMD_ARG(dma_status, NULL,
2646+
"List DMA controllers, or per-channel status: "
2647+
"[dma_idx] [chan]\n",
2648+
cmd_sof_dma_status, 1, 2),
2649+
#endif
2650+
24462651
SHELL_SUBCMD_SET_END
24472652
);
24482653

0 commit comments

Comments
 (0)