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

119 changes: 105 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,139 @@
// Author: Curtis Malainey <cujomalainey@chromium.org>
//

/* Stub implementation of the Maxim DSM API.
*
* The upstream stub returns DSM_API_OK from every function without writing any
* output field, which compiles but cannot run: with omemsizerequestedbytes left
* unset the handle size comes out 0, smart_amp_alloc_mod_memblk() skips the
* allocation, dsmhandle stays NULL and maxim_dsm_init() returns -EINVAL, so
* widget setup and card registration fail.
*
* This stub writes every field the caller reads back, and passes feed-forward
* audio straight through, so the glue layer, topology and blob layout can be
* exercised without libdsm.a.
*/

#include "dsm_api_public.h"

/* must match DSM_FRM_SZ in smart_amp_maxim_dsm.c, which hard-codes it in the
* ff/fb interleave loops; a mismatch misaligns the samples
*/
#define DSM_STUB_FRAME_SZ_SAMPLES 48

/* any non-zero value works -- the stub never touches this memory; 4 KB also
* exercises the PRIVATE memblk allocation path
*/
#define DSM_STUB_HANDLE_SZ_BYTES 4096

/* must exceed DSM_API_ADAPTIVE_PARAM_END, which
* maxim_dsm_get_volatile_param() iterates up to, or caldata overflows
*/
#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;

/* The upstream stub leaves CH1/CH2 unwritten and the caller reads
* uninitialised stack data -- maxim_dsm_get_num_param() sizes the
* caldata allocation from it.
*/
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;
}
109 changes: 88 additions & 21 deletions src/audio/smart_amp/smart_amp.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ struct smart_amp_data {
struct smart_amp_mod_stream ff_mod; /**< feed-forward buffer for mod */
struct smart_amp_mod_stream fb_mod; /**< feedback buffer for mod */
struct smart_amp_mod_stream out_mod; /**< output buffer for mod */
uint32_t ff_max_frames; /**< frames of ff_mod/out_mod capacity, per process() */
uint32_t fb_max_frames; /**< frames of fb_mod capacity, per process() */

struct smart_amp_buf mod_mems[MOD_MEMBLK_MAX]; /**< memory blocks for mod */

Expand Down Expand Up @@ -90,10 +92,17 @@ 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 none, and the 4-byte Zephyr sys_heap chunk header leaves pointers at
* 4 modulo 8, which faults inner models using Xtensa HiFi 8-byte loads and
* stores (LoadStoreAlignmentCause). 16 matches the inner models' own.
*/
#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 @@ -289,9 +298,8 @@ static int smart_amp_get_config(struct processing_module *mod,
comp_dbg(dev, "actual blob size = %zu, expected blob size = %zu",
bs, sizeof(struct sof_smart_amp_config));

/* bs is the host-set config.size and is used as the memcpy source
* length from the fixed-size sad->config, so bound it by the struct
* size. memcpy_s() below already bounds it against the destination.
/* bs is host-set and used as the memcpy source length from the
* fixed-size sad->config, so bound it by the struct size
*/
if (bs == 0 || bs > sizeof(struct sof_smart_amp_config))
return -EINVAL;
Expand Down Expand Up @@ -525,11 +533,8 @@ static int smart_amp_ff_process(struct processing_module *mod,
return 0;
}

/*
* The remap functions write frames * ff_mod.channels samples into
* ff_mod.buf, which holds SMART_AMP_FF_BUF_DB_SZ samples. Bound the
* total sample count, not just the frame count, so an unexpected
* channel count cannot overflow the buffer.
/* the remap writes frames * channels samples, so bound the total
* sample count rather than just the frame count
*/
if ((uint64_t)frames * sad->ff_mod.channels > SMART_AMP_FF_BUF_DB_SZ) {
comp_err(dev, "feed forward frame size overflow: %u frames, %u ch",
Expand Down Expand Up @@ -607,7 +612,18 @@ static int smart_amp_process(struct processing_module *mod,
avail_passthrough_frames = audio_stream_avail_frames(&source_buf->stream,
&sink_buf->stream);

avail_frames = avail_passthrough_frames;
/* The mod buffers hold one SMART_AMP_MAX_PERIOD_MS period, but the
* stream buffers are sized at 2 periods, so a missed cycle can leave
* more than that available. Process what fits and leave the rest for
* the next cycle: the overflow checks below would otherwise reject the
* whole call and emit a period of silence, and the LL pipeline drains
* one period per tick, so the backlog does not grow. A permanently
* oversized period is smart_amp_prepare()'s warning, not this cap.
*/
avail_frames = MIN(avail_passthrough_frames, sad->ff_max_frames);
if (avail_frames < avail_passthrough_frames)
comp_dbg(dev, "capped to %u of %u available frames",
avail_frames, avail_passthrough_frames);

if (sad->feedback_buf) {
struct comp_buffer *feedback_buf = sad->feedback_buf;
Expand All @@ -617,14 +633,17 @@ static int smart_amp_process(struct processing_module *mod,
avail_feedback_frames =
audio_stream_get_avail_frames(&feedback_buf->stream);

avail_feedback_frames = MIN(avail_passthrough_frames,
avail_feedback_frames);
/* one fb frame per ff frame, and never more than the
* fb buffer holds (its channel count may exceed ff's)
*/
avail_feedback_frames = MIN(avail_feedback_frames, avail_frames);
avail_feedback_frames = MIN(avail_feedback_frames, sad->fb_max_frames);

feedback_bytes = avail_feedback_frames *
audio_stream_frame_bytes(&feedback_buf->stream);

comp_dbg(dev, "processing %u feedback frames (avail_passthrough_frames: %u)",
avail_feedback_frames, avail_passthrough_frames);
comp_dbg(dev, "processing %u feedback frames (avail_frames: %u)",
avail_feedback_frames, avail_frames);

/* perform buffer writeback after source_buf process */
buffer_stream_invalidate(feedback_buf, feedback_bytes);
Expand Down Expand Up @@ -739,21 +758,49 @@ 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 */

/* smart_amp_reset() does not clear these, so a re-prepare after the
* feedback branch was disconnected would reuse the stale buffer
*/
sad->source_buf = NULL;
sad->feedback_buf = NULL;
sad->ff_max_frames = 0;
sad->fb_max_frames = 0;

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 by pipeline id:
* the feedback buffer is filled by the echo reference capture
* pipeline's demux, the playback source by our own pipeline's host.
*
* Not sources[i]->bound_module: only module_bind() assigns it and
* its sole caller is in module_adapter_ipc4.c, so it stays NULL on
* IPC3 and dereferencing it faults the DSP.
*/
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 All @@ -769,6 +816,21 @@ static int smart_amp_prepare(struct processing_module *mod,
audio_stream_get_channels(&sad->source_buf->stream));
sad->out_mod.channels = sad->ff_mod.channels;

if (!sad->ff_mod.channels) {
comp_err(dev, "no channels on playback source buffer");
return -EINVAL;
}
sad->ff_max_frames = SMART_AMP_FF_BUF_DB_SZ / sad->ff_mod.channels;

/* smart_amp_process() caps each call at ff_max_frames, which absorbs a
* transient backlog, but a scheduled period permanently above the cap
* would underrun the DAI forever. Name the knob rather than let that
* look like an amp fault.
*/
if (dev->frames > sad->ff_max_frames)
comp_warn(dev, "period of %u frames exceeds the %u frames buffered for %u ms, raise SMART_AMP_MAX_PERIOD_MS",
dev->frames, sad->ff_max_frames, SMART_AMP_MAX_PERIOD_MS);

/* the least bitdepth required for inner model, which should not be lower than the bitdepth
* for input samples of feed-forward, and feedback if exists.
*/
Expand All @@ -782,6 +844,11 @@ static int smart_amp_prepare(struct processing_module *mod,
fb_src_fmt = audio_stream_get_frm_fmt(&sad->feedback_buf->stream);
sad->fb_mod.channels = MIN(SMART_AMP_FB_MAX_CH_NUM,
audio_stream_get_channels(&sad->feedback_buf->stream));
if (!sad->fb_mod.channels) {
comp_err(dev, "no channels on feedback source buffer");
return -EINVAL;
}
sad->fb_max_frames = SMART_AMP_FB_BUF_DB_SZ / sad->fb_mod.channels;
least_req_depth = MAX(least_req_depth, get_sample_bitdepth(fb_src_fmt));
}

Expand Down
Loading
Loading