Skip to content
Open
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
35 changes: 35 additions & 0 deletions app/maxim_dsm_overlay.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ACP7x smart_amp overlay -- enables Maxim DSM in three steps.
#
# Usage:
# ./scripts/xtensa-build-zephyr.py -p acp_7_x -o app/maxim_dsm_overlay.conf
#
# The three configurations (each step only flips switches in this file,
# opening them up progressively):
#
# Step 1, PASSTHRU_AMP : CONFIG_COMP_SMART_AMP=y only
# -> the Kconfig choice falls back to PASSTHRU_AMP
# -> builds smart_amp.c + smart_amp_generic.c +
# smart_amp_passthru.c
# -> does not link libdsm.a. Use this to validate the
# smart_amp framework, the topology and the duplex
# clocking.
# -> SMART_UUID in the topology must be
# passthru_smart_amp
# (64a794f0-55d3-4bca-9d5b-7b588badd037)
#
# Step 2, MAXIM_DSM_STUB : adds CONFIG_MAXIM_DSM=y + CONFIG_MAXIM_DSM_STUB=y
# -> swaps in smart_amp_maxim_dsm.c + maxim_dsm_stub.c
# -> still does not link libdsm.a (NOT STUB is false
# in CMakeLists)
# -> SMART_UUID in the topology becomes maxim_dsm
# (0cd84e80-ebd3-11ea-adc1-0242ac120002)
#
# Step 3, real libdsm.a : drops CONFIG_MAXIM_DSM_STUB
# -> zephyr_library_import takes effect and links
# src/audio/smart_amp/lib/release/dsm_lib/libdsm.a


CONFIG_COMP_SMART_AMP=y
CONFIG_MAXIM_DSM=y
# CONFIG_MAXIM_DSM_STUB=y

135 changes: 121 additions & 14 deletions src/audio/smart_amp/maxim_dsm_stub.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,155 @@
// Author: Curtis Malainey <cujomalainey@chromium.org>
//

/* Stub implementation of the Maxim DSM closed source API.
*
* The upstream stub returns DSM_API_OK from every function without writing
* any output fields. That satisfies the compiler (the Kconfig help text
* also says "only be used for CI and testing"), but always fails at runtime
* during comp_new:
*
* dsm_api_get_mem() never writes omemsizerequestedbytes
* -> maxim_dsm_get_handle_size() returns 0
* -> smart_amp_alloc_mod_memblk(): the `if (ret == 0) return 0` branch
* skips both the allocation and the set_memblk() call
* -> hspk->dsmhandle remains NULL
* -> maxim_dsm_init(): `if (!hspk->dsmhandle) return -EINVAL` fires
* -> host sees `ipc tx error for 0x30010000 ... -22`
* + `Failed to setup widget SMART_AMP2.0`, card registration fails.
*
* This stub fills all output fields read back by the caller so that the
* smart_amp Maxim glue layer can fully initialise and run the data path.
* Feed-forward processing is a straight pass-through (input -> output), so
* audio is still audible under the stub, allowing the glue layer + topology
* + blob layout to be validated independently from the closed-source
* libdsm.a itself.
*/

#include "dsm_api_public.h"

/* Must match DSM_FRM_SZ in smart_amp_maxim_dsm.c: that file hard-codes
* DSM_FRM_SZ samples per frame in its ff/fb interleave/de-interleave loops,
* and a mismatch here causes sample misalignment.
*/
#define DSM_STUB_FRAME_SZ_SAMPLES 48

/* The real libdsm.a handle size is unknown. The stub itself does not use
* this memory; any non-zero value works. 4 KB is chosen to also exercise
* the PRIVATE memblk allocation path.
*/
#define DSM_STUB_HANDLE_SZ_BYTES 4096

/* maxim_dsm_get_volatile_param() iterates up to DSM_API_ADAPTIVE_PARAM_END
* (0x14); the parameter count must exceed that value or caldata overflows.
* Use the maximum value the API allows.
*/
#define DSM_STUB_MAX_NUM_PARAM DSM_DEFAULT_MAX_NUM_PARAM

enum DSM_API_MESSAGE dsm_api_get_mem(struct dsm_api_memory_size_ext_t *iopmmemparam,
int iparamsize)
{
if (!iopmmemparam)
return DSM_API_MSG_NULL_PARAM_POINTER;
if (iparamsize != (int)sizeof(*iopmmemparam))
return DSM_API_MSG_INVALID_PARAM;

iopmmemparam->omemsizerequestedbytes = DSM_STUB_HANDLE_SZ_BYTES;

return DSM_API_OK;
}

enum DSM_API_MESSAGE dsm_api_init(void *ipmodulehandler,
struct dsm_api_init_ext_t *iopparamstruct,
int iparamsize)
{
if (!ipmodulehandler)
return DSM_API_MSG_NULL_MODULE_HANDLER;
if (!iopparamstruct)
return DSM_API_MSG_NULL_PARAM_POINTER;
if (iparamsize != (int)sizeof(*iopparamstruct))
return DSM_API_MSG_INVALID_PARAM;

/* Caller uses these two fields to compute ifsamples / ibsamples. */
iopparamstruct->off_framesizesamples = DSM_STUB_FRAME_SZ_SAMPLES;
iopparamstruct->ofb_framesizesamples = DSM_STUB_FRAME_SZ_SAMPLES;

return DSM_API_OK;
}

enum DSM_API_MESSAGE dsm_api_ff_process(void *ipmodulehandler,
int channelmask,
short *ibufferorg,
int *ipnrsamples,
short *obufferorg,
int *opnrsamples)
enum DSM_API_MESSAGE dsm_api_ff_process(void *ipmodulehandler, int channelmask,
short *ibufferorg, int *ipnrsamples,
short *obufferorg, int *opnrsamples)
{
int nsamples;
int idx;

if (!ipmodulehandler)
return DSM_API_MSG_NULL_MODULE_HANDLER;
if (!ibufferorg || !ipnrsamples)
return DSM_API_MSG_NULL_INPUT_BUFFER_POINTER;
if (!obufferorg || !opnrsamples)
return DSM_API_MSG_NULL_OUTPUT_BUFFER_POINTER;

/* Pass-through: copy input to output unchanged so audio is audible under the stub. */
nsamples = *ipnrsamples;
for (idx = 0; idx < nsamples; idx++)
obufferorg[idx] = ibufferorg[idx];

*opnrsamples = nsamples;

return DSM_API_OK;
}

enum DSM_API_MESSAGE dsm_api_fb_process(void *ipmodulehandler,
int ichannelmask,
short *icurrbuffer,
short *ivoltbuffer,
enum DSM_API_MESSAGE dsm_api_fb_process(void *ipmodulehandler, int ichannelmask,
short *icurrbuffer, short *ivoltbuffer,
int *iopnrsamples)
{
if (!ipmodulehandler)
return DSM_API_MSG_NULL_MODULE_HANDLER;
if (!icurrbuffer || !ivoltbuffer || !iopnrsamples)
return DSM_API_MSG_NULL_INPUT_BUFFER_POINTER;

/* Stub does no adaptation; IV feedback data is silently discarded. */

return DSM_API_OK;
}

enum DSM_API_MESSAGE dsm_api_set_params(void *ipmodulehandler,
int icommandnumber, void *ipparamsbuffer)
enum DSM_API_MESSAGE dsm_api_set_params(void *ipmodulehandler, int icommandnumber,
void *ipparamsbuffer)
{
if (!ipmodulehandler)
return DSM_API_MSG_NULL_MODULE_HANDLER;
if (!ipparamsbuffer)
return DSM_API_MSG_NULL_PARAM_POINTER;

/* Stub has no internal state; written tuning parameters are discarded. */

return DSM_API_OK;
}

enum DSM_API_MESSAGE dsm_api_get_params(void *ipmodulehandler,
int icommandnumber, void *opparams)
enum DSM_API_MESSAGE dsm_api_get_params(void *ipmodulehandler, int icommandnumber,
void *opparams)
{
int *cmdblock = (int *)opparams;

if (!ipmodulehandler)
return DSM_API_MSG_NULL_MODULE_HANDLER;
if (!cmdblock)
return DSM_API_MSG_NULL_PARAM_POINTER;

/* Caller places the command id in cmdblock[DSM_GET_ID_IDX]; return
* values are read from CH1/CH2. The upstream stub leaves these fields
* unwritten, so the caller reads uninitialised stack data.
* maxim_dsm_get_num_param() is especially critical: it uses this result
* to size the caldata allocation.
*/
if (DSM_CH_MASK(cmdblock[DSM_GET_ID_IDX]) == DSM_API_GET_MAXIMUM_CMD_ID) {
cmdblock[DSM_GET_CH1_IDX] = DSM_STUB_MAX_NUM_PARAM;
cmdblock[DSM_GET_CH2_IDX] = DSM_STUB_MAX_NUM_PARAM;
} else {
cmdblock[DSM_GET_CH1_IDX] = 0;
cmdblock[DSM_GET_CH2_IDX] = 0;
}

return DSM_API_OK;
}
62 changes: 54 additions & 8 deletions src/audio/smart_amp/smart_amp.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,23 @@ static inline void smart_amp_free_mod_memories(struct smart_amp_data *sad)
sad->mod_data = NULL;
}

/* Alignment required for every buffer handed to the inner model.
*
* mod_alloc() requests no particular alignment, and on platforms built with
* CONFIG_SYS_HEAP_SMALL_ONLY=y and without CONFIG_SOF_ZEPHYR_HEAP_CACHED the
* Zephyr sys_heap chunk header is only 4 bytes, so every returned pointer is
* 4 modulo 8 (see chunk_mem() in zephyr/lib/heap/heap.c). Inner models using
* Xtensa HiFi 8-byte loads and stores (AE_L32X2 / AE_S32X2 / AE_L64 / AE_S64)
* then take an unhandled LoadStoreAlignmentCause exception. Ask for 16 bytes
* explicitly, matching the alignment the vendor libraries lay their internal
* memory blocks out on.
*/
#define SMART_AMP_BUF_ALIGN 16

static inline int smart_amp_buf_alloc(struct processing_module *mod,
struct smart_amp_buf *buf, size_t size)
{
buf->data = mod_alloc(mod, size);
buf->data = mod_alloc_align(mod, size, SMART_AMP_BUF_ALIGN);
if (!buf->data)
return -ENOMEM;
buf->size = size;
Expand Down Expand Up @@ -739,21 +752,54 @@ static int smart_amp_prepare(struct processing_module *mod,
int ret, i;

comp_dbg(dev, "%d sources %d sinks", num_of_sources, num_of_sinks);

#if CONFIG_IPC_MAJOR_4
smart_amp_ipc4_params(mod);
#endif

/* In module API, state is managed by the framework, so no comp_set_state needed */

/* Neither pointer is cleared by smart_amp_reset(), so a re-prepare after
* the feedback branch was disconnected would otherwise keep using the
* stale buffer from the previous run.
*/
sad->source_buf = NULL;
sad->feedback_buf = NULL;

for (i = 0; i < num_of_sources; i++) {
/* NOTE: This should not work in module based environment:
* sources[i]->bound_module->dev->ipc_config.type == SOF_COMP_DEMUX
* So let's check which one of the sources is from a capture stream.
* The code is not tested and may not work.
struct comp_buffer *buf = comp_buffer_get_from_source(sources[i]);
struct comp_dev *producer;

if (!buf) {
comp_err(dev, "source %d has no buffer", i);
return -ENOTCONN;
}

/*
* Tell the feedback source from the playback one.
*
* Do not use sources[i]->bound_module here: it is only ever assigned by
* module_bind(), whose sole caller lives in module_adapter_ipc4.c, so it
* stays NULL on IPC3 platforms (e.g. AMD ACP7x) and dereferencing it
* faults the DSP.
*
* The feedback buffer is filled by the demux of the echo reference
* capture pipeline, so its producer belongs to a different pipeline than
* this module, while the playback source is filled by the host component
* of our own pipeline. Comparing pipeline ids works under both IPC
* versions, and unlike comp_dev::direction it is already valid here even
* when the capture pipeline has not been parametrised yet.
*/
if (sources[i]->bound_module->dev->direction == SOF_IPC_STREAM_CAPTURE)
sad->feedback_buf = comp_buffer_get_from_source(sources[i]);
producer = comp_buffer_get_source_component(buf);
if (producer && dev_comp_pipe_id(producer) != dev_comp_pipe_id(dev))
sad->feedback_buf = buf;
else
sad->source_buf = comp_buffer_get_from_source(sources[i]);
sad->source_buf = buf;
}

if (!sad->source_buf) {
comp_err(dev, "no playback source buffer");
return -ENOTCONN;
}

/* sink buffer */
Expand Down
Loading
Loading