Component & Module Interfaces

Introduction of the Module Adapter, an intermediate layer which provides common code for different module API adapters, created multi-level sequences of calls to functions and this mechanism is very expensive during run-time processing with regards to additional cycles consumed for parameter translation and copying as well as the additional memory for extra buffers, contexts, and the call stack. The module_adapter translates the comp_ops interface required by the existing infrastructure (pipelines etc.) into the module_interface. Then appropriate adapter translates the module_interface into the final module interface like Cadence Codec API or IADK ProcessingModuleInterface. These dependencies are illustrated in the next figure.

scale 1024 width

component "pipelines" {
	class pipeline
	hide pipeline methods
	hide pipeline attributes
}
component "component" {

	class comp_driver <<struct>> {
	}
	hide comp_driver methods
	hide comp_driver attributes

	class comp_dev <<struct>> {
		state
		position
		frames
		pipeline
		min_sink_bytes
		min_source_bytes
		task
		size
		period
		...
	}
	hide comp_dev methods

	interface buffer
	hide buffer methods
	hide buffer attributes

	interface comp_ops {
		create() : comp_dev*
		free(comp_dev*)
		params(params)
		dai_get_hw_params(params, dir)
		dai_config(dai_config, dai_spec_config)
		cmd(int cmd, void *data)
		trigger(int cmd)
		prepare()
		reset()
		copy()
		position()
		get_attribute()
		set_attribute()
		dai_ts_config()
		dai_ts_start()
		dai_ts_stop()
		unbind()
		get_large_config()
		set_large_config()
	}
	hide comp_ops attributes


	comp_driver -> comp_dev : creates
	comp_dev *-right- comp_ops
}
pipeline -> comp_ops : calls

component "module_adapter" {

	class module_adapter <<struct>> {
		ops : comp_ops =
			.create = adapter_shim_new
			.prepare = module_adapter_prepare
			.params = module_adapter_params
			.copy = module_adapter_copy

		adapter_shim_new()
		module_adapter_prepare()
		module_adapter_params()
		module_adapter_copy()
	}

	interface module_interface {
		init(processing_module*)
		prepare(processing_module*)
		process(processing_module*)
		set_configuration()
		get_configuration()
		set_processing_mode()
		get_processing_mode()
		reset()
		free()
	}
	hide module_interface attributes

	class processing_module <<struct>> {
		stream_params
		sink_buffer_list
		period_bytes
		deep_buff_bytes
		output_buffer_size
		input_buffers[]
		output_buffers[]
	}
	hide processing_module methods

	module_adapter -left-> processing_module : creates
	module_adapter -> module_interface : calls

}
module_adapter -up-|> comp_ops

component "cadence adapter" {
	class cadence_codec {
		cadence_codec_init()
		cadence_codec_prepare()
		cadence_codec_process()
		cadence_codec_set_configuration()
		cadence_codec_reset()
		cadence_codec_free()
	}
	hide cadence_codec attributes

	interface "Cadence Codec API" as cadence_codec_api
	hide cadence_codec_api methods
	hide cadence_codec_api attributes

	cadence_codec -> cadence_codec_api : calls
}
cadence_codec -up-|> module_interface

component "custom extensions" {
	class "mp3 codec" as mp3_codec
	hide mp3_codec methods
	hide mp3_codec attributes

	class "aac codec" as aac_codec
	hide aac_codec methods
	hide aac_codec attributes
}
mp3_codec -up-|> cadence_codec_api
aac_codec -up-|> cadence_codec_api

component "IADK adapter" {
	class adp_interface {
		intel_modules_init()
		intel_modules_prepare()
		intel_modules_process()
	}
	hide adp_interface attributes

	interface ProcessingModuleInterface <<C++>> {
		Init()
		Delete()
		Process()
		Reset()
		SetProcessingMode()
		GetProcessingMode()
		SetConfiguration(config_id, fragment_pos, data_in, data_out)
		GetConfiguration(config_id, fragment_pos, data_out)
	}
	hide ProcessingModuleInterface attributes

	adp_interface -> ProcessingModuleInterface : calls
}
adp_interface -up-|> module_interface

Figure 6 Component & Module API

Maintenance of two base component (alias module) interfaces is expensive and also confusing for the developers who wants to create a module that provides SOF native module API. It is unclear whether this should be the comp_ops or the module_interface. The latter is much more convenient since it is tailored for the audio processing modules while the comp_ops is a multipurpose interface cluttered with many optional operations required for dai-comp modules only.

Therefore the module_interface should become the only SOF native module interface that the rest of underlying infrastructure would interact with directly. The comp_ops would become obsolete and eventually would be removed from the SOF.

The cost of extra memory required at the moment for intermediate audio data buffers allocated inside the module_adapter layer (see the Preparation Flow figure below) as well as cost of extra cycles required to copy the data to/from the intermediate buffers (see the Processing Flow figure below) could be avoided by removing the comp_ops as well.

actor pipeline
box "Module Adapter\no-- comp_ops"
	participant "module_adapter" as module_adapter
end box
box "IADK Module Adapter\no-- module_interface"
	participant "iadk_adapter" as iadk_adapter
end box
box "IADK Module\no-- ProcessingModuleInterface"
	participant iadk_module
end box

pipeline -> module_adapter : <b>(1) ops->module_adapter_prepare()</b>
	activate module_adapter
	module_adapter -> module_adapter : module_prepare()
		activate module_adapter
		module_adapter -> iadk_adapter : <b>(2) ops->prepare()</b>
			activate iadk_adapter
			iadk_adapter -> iadk_module : <b>(3) preparation</b>
		module_adapter <-- iadk_adapter
		deactivate iadk_adapter
		module_adapter -> module_adapter : alloc buf descriptors
		module_adapter -> module_adapter : alloc buffers

Figure 7 Preparation Flow

actor pipeline
box "Module Adapter\no-- comp_ops"
	participant "module_adapter" as module_adapter
end box
box "IADK Module Adapter\no-- module_interface"
	participant "iadk_adapter" as iadk_adapter
end box
box "IADK Module\no-- ProcessingModuleInterface"
	participant iadk_module
end box

pipeline -> module_adapter : <b>(1) ops->module_adapter_copy()</b>
	activate module_adapter

	module_adapter -> module_adapter : find min bytes\nto process

	note left of module_adapter
	This logic is WRONG for some modules!!
	end note

	module_adapter -> module_adapter : copy input from sources\nto internal buffers

	module_adapter -> module_adapter : module_process()
		activate module_adapter
note left of module_adapter
Why all those extra internal calls
used only once??
end note

		module_adapter -> iadk_adapter : <b>(2) ops->process()</b>
			activate iadk_adapter
			iadk_adapter -> iadk_module : <b>(3) processing</b>
		module_adapter <-- iadk_adapter
		deactivate iadk_adapter

	deactivate module_adapter

	module_adapter -> module_adapter : module_adapter_process_output()
		activate module_adapter
		module_adapter -> module_adapter : copy output from internal buffers\ntosinks
	deactivate module_adapter
pipeline <-- module_adapter

Figure 8 Processing Flow