dma: SG elem lists replaced with arrays

Multiple alloctions of SG elements linked as a list
replaced with single allocations of arrays.

- The code flows (esp. cleanup-on-error) are simplified,

- number of 64bytes chunk allocations reduced from 18 to 12 for
  example very basic topology, no increase in other areas
  (2-period sgls still fit),

- more efficient allocation for host page tables on legacy platforms;
  instead of going from a desc array to the list that is copied to
  another list, there is just a single allocation of array moved
  to the destination component.

Signed-off-by: Marcin Maka <marcin.maka@linux.intel.com>
diff --git a/src/audio/dai.c b/src/audio/dai.c
index 2262d8c..2a44abf 100644
--- a/src/audio/dai.c
+++ b/src/audio/dai.c
@@ -217,7 +217,7 @@
 		goto error;
 	}
 
-	list_init(&dd->config.elem_list);
+	dma_sg_init(&dd->config.elem_array);
 	dd->dai_pos = NULL;
 	dd->dai_pos_blks = 0;
 	dd->xrun = 0;
@@ -248,11 +248,7 @@
 	struct dai_data *dd = comp_get_drvdata(dev);
 	struct dma_sg_config *config = &dd->config;
 	struct sof_ipc_comp_config *source_config;
-	struct dma_sg_elem *elem;
 	struct comp_buffer *dma_buffer;
-	struct list_item *elist;
-	struct list_item *tlist;
-	int i;
 	int err;
 	uint32_t buffer_size;
 
@@ -281,35 +277,20 @@
 		return err;
 	}
 
-	if (list_is_empty(&config->elem_list)) {
-		/* set up cyclic list of DMA elems */
-		for (i = 0; i < source_config->periods_sink; i++) {
-
-			elem = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
-				sizeof(*elem));
-			if (elem == NULL)
-				goto err_unwind;
-
-			elem->size = dd->period_bytes;
-			elem->src = (uintptr_t)(dma_buffer->r_ptr) +
-				i * dd->period_bytes;
-
-			elem->dest = dai_fifo(dd->dai, SOF_IPC_STREAM_PLAYBACK);
-
-			list_item_append(&elem->list, &config->elem_list);
+	if (!config->elem_array.elems) {
+		err = dma_sg_alloc(&config->elem_array, RZONE_RUNTIME,
+				   config->direction,
+				   source_config->periods_sink,
+				   dd->period_bytes,
+				   (uintptr_t)(dma_buffer->r_ptr),
+				   dai_fifo(dd->dai, SOF_IPC_STREAM_PLAYBACK));
+		if (err < 0) {
+			trace_dai_error("ep3");
+			return err;
 		}
 	}
 
 	return 0;
-
-err_unwind:
-	trace_dai_error("ep3");
-	list_for_item_safe(elist, tlist, &config->elem_list) {
-		elem = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(&elem->list);
-		rfree(elem);
-	}
-	return -ENOMEM;
 }
 
 static int dai_capture_params(struct comp_dev *dev)
@@ -317,11 +298,7 @@
 	struct dai_data *dd = comp_get_drvdata(dev);
 	struct dma_sg_config *config = &dd->config;
 	struct sof_ipc_comp_config *sink_config;
-	struct dma_sg_elem *elem;
 	struct comp_buffer *dma_buffer;
-	struct list_item *elist;
-	struct list_item *tlist;
-	int i;
 	int err;
 	uint32_t buffer_size;
 
@@ -350,33 +327,20 @@
 		return err;
 	}
 
-	if (list_is_empty(&config->elem_list)) {
-		/* set up cyclic list of DMA elems */
-		for (i = 0; i < sink_config->periods_source; i++) {
-
-			elem = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
-				sizeof(*elem));
-			if (elem == NULL)
-				goto err_unwind;
-
-			elem->size = dd->period_bytes;
-			elem->dest = (uintptr_t)(dma_buffer->w_ptr) +
-				i * dd->period_bytes;
-			elem->src = dai_fifo(dd->dai, SOF_IPC_STREAM_CAPTURE);
-			list_item_append(&elem->list, &config->elem_list);
+	if (!config->elem_array.elems) {
+		err = dma_sg_alloc(&config->elem_array, RZONE_RUNTIME,
+				   config->direction,
+				   sink_config->periods_source,
+				   dd->period_bytes,
+				   (uintptr_t)(dma_buffer->w_ptr),
+				   dai_fifo(dd->dai, SOF_IPC_STREAM_CAPTURE));
+		if (err < 0) {
+			trace_dai_error("ec3");
+			return err;
 		}
 	}
 
 	return 0;
-
-err_unwind:
-	trace_dai_error("ec3");
-	list_for_item_safe(elist, tlist, &config->elem_list) {
-		elem = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(&elem->list);
-		rfree(elem);
-	}
-	return -ENOMEM;
 }
 
 static int dai_params(struct comp_dev *dev)
@@ -450,7 +414,7 @@
 
 	dev->position = 0;
 
-	if (list_is_empty(&dd->config.elem_list)) {
+	if (!dd->config.elem_array.elems) {
 		trace_dai_error("wdm");
 		comp_set_state(dev, COMP_TRIGGER_RESET);
 		return -EINVAL;
@@ -489,19 +453,12 @@
 {
 	struct dai_data *dd = comp_get_drvdata(dev);
 	struct dma_sg_config *config = &dd->config;
-	struct list_item *elist;
-	struct list_item *tlist;
-	struct dma_sg_elem *elem;
 
 	trace_dai("res");
 
 	dma_channel_put(dd->dma, dd->chan);
 
-	list_for_item_safe(elist, tlist, &config->elem_list) {
-		elem = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(&elem->list);
-		rfree(elem);
-	}
+	dma_sg_free(&config->elem_array);
 
 	dd->dai_pos_blks = 0;
 	if (dd->dai_pos)
@@ -750,8 +707,6 @@
 static void dai_cache(struct comp_dev *dev, int cmd)
 {
 	struct dai_data *dd;
-	struct list_item *item;
-	struct dma_sg_elem *e;
 
 	switch (cmd) {
 	case COMP_CACHE_WRITEBACK_INV:
@@ -759,12 +714,7 @@
 
 		dd = comp_get_drvdata(dev);
 
-		list_for_item(item, &dd->config.elem_list) {
-			e = container_of(item, struct dma_sg_elem, list);
-			dcache_writeback_invalidate_region(e, sizeof(*e));
-			dcache_writeback_invalidate_region(item,
-							   sizeof(*item));
-		}
+		dma_sg_cache_wb_inv(&dd->config.elem_array);
 
 		dcache_writeback_invalidate_region(dd->dai, sizeof(*dd->dai));
 		dcache_writeback_invalidate_region(dd->dma, sizeof(*dd->dma));
@@ -782,11 +732,7 @@
 		dcache_invalidate_region(dd->dma, sizeof(*dd->dma));
 		dcache_invalidate_region(dd->dai, sizeof(*dd->dai));
 
-		list_for_item(item, &dd->config.elem_list) {
-			dcache_invalidate_region(item, sizeof(*item));
-			e = container_of(item, struct dma_sg_elem, list);
-			dcache_invalidate_region(e, sizeof(*e));
-		}
+		dma_sg_cache_inv(&dd->config.elem_array);
 		break;
 	}
 }
diff --git a/src/audio/host.c b/src/audio/host.c
index 25f2e8b..754da0e 100644
--- a/src/audio/host.c
+++ b/src/audio/host.c
@@ -52,10 +52,12 @@
 #define tracev_host(__e)	tracev_event(TRACE_CLASS_HOST, __e)
 #define trace_host_error(__e)	trace_error(TRACE_CLASS_HOST, __e)
 
+/**
+ * \brief Host buffer info.
+ */
 struct hc_buf {
-	/* host buffer info */
-	struct list_item elem_list;
-	struct list_item *current;
+	struct dma_sg_elem_array elem_array; /**< array of SG elements */
+	uint32_t current;		/**< index of current element */
 	uint32_t current_end;
 };
 
@@ -107,15 +109,11 @@
 
 static inline struct dma_sg_elem *next_buffer(struct hc_buf *hc)
 {
-	struct dma_sg_elem *elem;
-
-	if (list_item_is_last(hc->current, &hc->elem_list))
-		elem = list_first_item(&hc->elem_list, struct dma_sg_elem, list);
-	else
-		elem = list_first_item(hc->current, struct dma_sg_elem, list);
-
-	hc->current = &elem->list;
-	return elem;
+	if (!hc->elem_array.elems || !hc->elem_array.count)
+		return NULL;
+	if (++hc->current == hc->elem_array.count)
+		hc->current = 0;
+	return hc->elem_array.elems + hc->current;
 }
 
 #endif
@@ -142,8 +140,7 @@
 	uint32_t period_bytes = hd->period_bytes;
 #endif
 
-	local_elem = list_first_item(&hd->config.elem_list,
-				     struct dma_sg_elem, list);
+	local_elem = hd->config.elem_array.elems;
 
 	tracev_host("irq");
 
@@ -240,48 +237,26 @@
 static int create_local_elems(struct comp_dev *dev)
 {
 	struct host_data *hd = comp_get_drvdata(dev);
-	struct dma_sg_elem *e;
-	struct list_item *elist;
-	struct list_item *tlist;
-	int i;
+	struct dma_sg_elem_array *elem_array;
+	int err;
 
-	/* TODO: simplify elem storage by using an array */
-	for (i = 0; i < hd->period_count; i++) {
-		/* allocate new host DMA elem and add it to our list */
-		e = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*e));
-		if (e == NULL)
-			goto unwind;
-
-		if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK)
-			e->dest = (uintptr_t)(hd->dma_buffer->addr) +
-				i * hd->period_bytes;
-		else
-			e->src = (uintptr_t)(hd->dma_buffer->addr) +
-				i * hd->period_bytes;
-
-		e->size = hd->period_bytes;
 #if defined CONFIG_DMA_GW
-		list_item_append(&e->list, &hd->config.elem_list);
+	elem_array = &hd->config.elem_array;
 #else
-		list_item_append(&e->list, &hd->local.elem_list);
+	elem_array = &hd->local.elem_array;
 #endif
-	}
+	err = dma_sg_alloc(elem_array, RZONE_RUNTIME,
+			   dev->params.direction == SOF_IPC_STREAM_PLAYBACK ?
+				DMA_DIR_HMEM_TO_LMEM : DMA_DIR_LMEM_TO_HMEM,
+			   hd->period_count,
+			   hd->period_bytes, (uintptr_t)(hd->dma_buffer->addr),
+			   0);
 
+	if (err < 0) {
+		trace_host_error("el0");
+		return err;
+	}
 	return 0;
-
-unwind:
-#if defined CONFIG_DMA_GW
-	list_for_item_safe(elist, tlist, &hd->config.elem_list) {
-#else
-	list_for_item_safe(elist, tlist, &hd->local.elem_list) {
-#endif
-		e = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(&e->list);
-		rfree(e);
-	}
-
-	trace_host_error("el0");
-	return -ENOMEM;
 }
 
 /**
@@ -353,7 +328,7 @@
 	struct sof_ipc_comp_host *ipc_host = (struct sof_ipc_comp_host *)comp;
 	uint32_t dir, caps, dma_dev;
 #if !defined CONFIG_DMA_GW
-	struct dma_sg_elem *elem = NULL;
+	int err;
 #endif
 
 	trace_host("new");
@@ -389,15 +364,16 @@
 	}
 
 	/* init buffer elems */
-	list_init(&hd->config.elem_list);
-#if !defined CONFIG_DMA_GW
-	list_init(&hd->host.elem_list);
-	list_init(&hd->local.elem_list);
+#if defined CONFIG_DMA_GW
+	dma_sg_init(&hd->config.elem_array);
+#else
+	dma_sg_init(&hd->host.elem_array);
+	dma_sg_init(&hd->local.elem_array);
 
-	elem = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*elem));
-	if (!elem)
+	err = dma_sg_alloc(&hd->config.elem_array, RZONE_RUNTIME,
+			   dir, 1, 0, 0, 0);
+	if (err < 0)
 		goto error;
-	list_item_prepend(&elem->list, &hd->config.elem_list);
 #endif
 
 	/* init posn data. TODO: other fields */
@@ -408,9 +384,6 @@
 	return dev;
 
 error:
-#if !defined CONFIG_DMA_GW
-	rfree(elem);
-#endif
 	rfree(hd);
 	rfree(dev);
 	return NULL;
@@ -419,7 +392,6 @@
 static void host_free(struct comp_dev *dev)
 {
 	struct host_data *hd = comp_get_drvdata(dev);
-	struct dma_sg_elem *elem;
 
 	trace_host("fre");
 
@@ -427,12 +399,7 @@
 	dma_channel_put(hd->dma, hd->chan);
 #endif
 
-	if (!list_is_empty(&hd->config.elem_list)) {
-		elem = list_first_item(&hd->config.elem_list,
-				       struct dma_sg_elem, list);
-		rfree(elem);
-	}
-
+	dma_sg_free(&hd->config.elem_array);
 	rfree(hd);
 	rfree(dev);
 }
@@ -446,20 +413,17 @@
 	struct dma_sg_elem *local_elem;
 
 	/* setup elem to point to first source elem */
-	source_elem = list_first_item(&hd->source->elem_list,
-		struct dma_sg_elem, list);
-	hd->source->current = &source_elem->list;
+	source_elem = hd->source->elem_array.elems;
+	hd->source->current = 0;
 	hd->source->current_end = source_elem->src + source_elem->size;
 
 	/* setup elem to point to first sink elem */
-	sink_elem = list_first_item(&hd->sink->elem_list,
-		struct dma_sg_elem, list);
-	hd->sink->current = &sink_elem->list;
+	sink_elem = hd->sink->elem_array.elems;
+	hd->sink->current = 0;
 	hd->sink->current_end = sink_elem->dest + sink_elem->size;
 
 	/* local element */
-	local_elem = list_first_item(&hd->config.elem_list,
-		struct dma_sg_elem, list);
+	local_elem = hd->config.elem_array.elems;
 	local_elem->dest = sink_elem->dest;
 	local_elem->size =  hd->period_bytes;
 	local_elem->src = source_elem->src;
@@ -478,7 +442,7 @@
 	uint32_t buffer_size;
 	int err;
 
-	trace_host("par");
+	trace_event(TRACE_CLASS_HOST, "host-params");
 
 	/* host params always installed by pipeline IPC */
 	hd->host_size = dev->params.buffer.size;
@@ -625,20 +589,14 @@
 }
 
 #if !defined CONFIG_DMA_GW
-static int host_buffer(struct comp_dev *dev, struct dma_sg_elem *elem,
-		uint32_t host_size)
+static int host_buffer(struct comp_dev *dev,
+		       struct dma_sg_elem_array *elem_array,
+		       uint32_t host_size)
 {
 	struct host_data *hd = comp_get_drvdata(dev);
-	struct dma_sg_elem *e;
 
-	/* allocate new host DMA elem and add it to our list */
-	e = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*e));
-	if (e == NULL)
-		return -ENOMEM;
+	hd->host.elem_array = *elem_array;
 
-	*e = *elem;
-
-	list_item_append(&e->list, &hd->host.elem_list);
 	return 0;
 }
 #endif
@@ -646,42 +604,23 @@
 static int host_reset(struct comp_dev *dev)
 {
 	struct host_data *hd = comp_get_drvdata(dev);
-	struct dma_sg_elem *e;
-	struct list_item *elist;
-	struct list_item *tlist;
 
 	trace_host("res");
 
 #if !defined CONFIG_DMA_GW
 	/* free all host DMA elements */
-	list_for_item_safe(elist, tlist, &hd->host.elem_list) {
-		e = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(&e->list);
-		rfree(e);
-	}
+	dma_sg_free(&hd->host.elem_array);
 
 	/* free all local DMA elements */
-	list_for_item_safe(elist, tlist, &hd->local.elem_list) {
-		e = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(&e->list);
-		rfree(e);
-	}
+	dma_sg_free(&hd->local.elem_array);
 #endif
 
 #if defined CONFIG_DMA_GW
 	dma_stop(hd->dma, hd->chan);
 	dma_channel_put(hd->dma, hd->chan);
 
-	/*
-	 * here free dma_sg_elem those allocated in create_local_elems(),
-	 * we should only keep header since hda-dma allocates the full
-	 * list again
-	 */
-	list_for_item_safe(elist, tlist, &hd->config.elem_list) {
-		e = container_of(elist, struct dma_sg_elem, list);
-		list_item_del(elist);
-		rfree(e);
-	}
+	/* free array for hda-dma only, do not free single one for dw-dma */
+	dma_sg_free(&hd->config.elem_array);
 #endif
 
 	host_pointer_reset(dev);
@@ -712,8 +651,7 @@
 	if (dev->state != COMP_STATE_ACTIVE)
 		return 0;
 
-	local_elem = list_first_item(&hd->config.elem_list,
-		struct dma_sg_elem, list);
+	local_elem = hd->config.elem_array.elems;
 
 	/* enough free or avail to copy ? */
 	if (dev->params.direction == SOF_IPC_STREAM_PLAYBACK) {
@@ -760,8 +698,8 @@
 static void host_cache(struct comp_dev *dev, int cmd)
 {
 	struct host_data *hd;
-	struct list_item *item;
-	struct dma_sg_elem *e;
+
+	trace_event(TRACE_CLASS_HOST, "host-cache cmd %d", cmd);
 
 	switch (cmd) {
 	case COMP_CACHE_WRITEBACK_INV:
@@ -769,20 +707,10 @@
 
 		hd = comp_get_drvdata(dev);
 
-		list_for_item(item, &hd->config.elem_list) {
-			e = container_of(item, struct dma_sg_elem, list);
-			dcache_writeback_invalidate_region(e, sizeof(*e));
-			dcache_writeback_invalidate_region(item,
-							   sizeof(*item));
-		}
+		dma_sg_cache_wb_inv(&hd->config.elem_array);
 
 #if !defined CONFIG_DMA_GW
-		list_for_item(item, &hd->local.elem_list) {
-			e = container_of(item, struct dma_sg_elem, list);
-			dcache_writeback_invalidate_region(e, sizeof(*e));
-			dcache_writeback_invalidate_region(item,
-							   sizeof(*item));
-		}
+		dma_sg_cache_wb_inv(&hd->local.elem_array);
 #endif
 
 		dcache_writeback_invalidate_region(hd->dma, sizeof(*hd->dma));
@@ -800,18 +728,10 @@
 		dcache_invalidate_region(hd->dma, sizeof(*hd->dma));
 
 #if !defined CONFIG_DMA_GW
-		list_for_item(item, &hd->local.elem_list) {
-			dcache_invalidate_region(item, sizeof(*item));
-			e = container_of(item, struct dma_sg_elem, list);
-			dcache_invalidate_region(e, sizeof(*e));
-		}
+		dma_sg_cache_inv(&hd->local.elem_array);
 #endif
 
-		list_for_item(item, &hd->config.elem_list) {
-			dcache_invalidate_region(item, sizeof(*item));
-			e = container_of(item, struct dma_sg_elem, list);
-			dcache_invalidate_region(e, sizeof(*e));
-		}
+		dma_sg_cache_inv(&hd->config.elem_array);
 		break;
 	}
 }
diff --git a/src/drivers/intel/cavs/hda-dma.c b/src/drivers/intel/cavs/hda-dma.c
index 311c70c..8373c50 100644
--- a/src/drivers/intel/cavs/hda-dma.c
+++ b/src/drivers/intel/cavs/hda-dma.c
@@ -470,26 +470,21 @@
 	struct dma_sg_config *config)
 {
 	struct dma_pdata *p = dma_get_drvdata(dma);
-	struct list_item *plist;
 	struct dma_sg_elem *sg_elem;
 	uint32_t buffer_addr = 0;
 	uint32_t period_bytes = 0;
 	uint32_t buffer_bytes = 0;
-	uint32_t desc_count = 0;
 	uint32_t flags;
 	uint32_t addr;
 	uint32_t dgcs;
+	int i;
 	int ret = 0;
 
 	spin_lock_irq(&dma->lock, flags);
 
 	trace_host("Dsc");
 
-	/* get number of SG elems */
-	list_for_item(plist, &config->elem_list)
-		desc_count++;
-
-	if (desc_count == 0) {
+	if (!config->elem_array.count) {
 		trace_host_error("eD1");
 		ret = -EINVAL;
 		goto out;
@@ -497,11 +492,11 @@
 
 	/* default channel config */
 	p->chan[channel].direction = config->direction;
-	p->chan[channel].desc_count = desc_count;
+	p->chan[channel].desc_count = config->elem_array.count;
 
 	/* validate - HDA only supports continuous elems of same size  */
-	list_for_item(plist, &config->elem_list) {
-		sg_elem = container_of(plist, struct dma_sg_elem, list);
+	for (i = 0; i < config->elem_array.count; i++) {
+		sg_elem = config->elem_array.elems + i;
 
 		if (config->direction == DMA_DIR_HMEM_TO_LMEM ||
 		    config->direction == DMA_DIR_DEV_TO_MEM)
diff --git a/src/drivers/intel/dw-dma.c b/src/drivers/intel/dw-dma.c
index 29008f5..760b904 100644
--- a/src/drivers/intel/dw-dma.c
+++ b/src/drivers/intel/dw-dma.c
@@ -602,12 +602,10 @@
 	struct dma_sg_config *config)
 {
 	struct dma_pdata *p = dma_get_drvdata(dma);
-	struct list_item *plist;
 	struct dma_sg_elem *sg_elem;
 	struct dw_lli2 *lli_desc;
 	struct dw_lli2 *lli_desc_head;
 	struct dw_lli2 *lli_desc_tail;
-	uint32_t desc_count = 0;
 	uint32_t flags;
 	uint32_t msize = 3;/* default msize */
 	int i, ret = 0;
@@ -622,20 +620,16 @@
 	p->chan[channel].cfg_lo = DW_CFG_LOW_DEF;
 	p->chan[channel].cfg_hi = DW_CFG_HIGH_DEF;
 
-	/* get number of SG elems */
-	list_for_item(plist, &config->elem_list)
-		desc_count++;
-
-	if (desc_count == 0) {
+	if (!config->elem_array.count) {
 		trace_dma_error("eD0");
 		ret = -EINVAL;
 		goto out;
 	}
 
 	/* do we need to realloc descriptors */
-	if (desc_count != p->chan[channel].desc_count) {
+	if (config->elem_array.count != p->chan[channel].desc_count) {
 
-		p->chan[channel].desc_count = desc_count;
+		p->chan[channel].desc_count = config->elem_array.count;
 
 		/* allocate descriptors for channel */
 		if (p->chan[channel].lli)
@@ -677,9 +671,9 @@
 	}
 
 	/* fill in lli for the elem in the list */
-	list_for_item(plist, &config->elem_list) {
+	for (i = 0; i < config->elem_array.count; i++) {
 
-		sg_elem = container_of(plist, struct dma_sg_elem, list);
+		sg_elem = config->elem_array.elems + i;
 
 		/* write CTL_LOn for each lli */
 		switch (config->src_width) {
diff --git a/src/host/common_test.c b/src/host/common_test.c
index ef5100b..845ddae 100644
--- a/src/host/common_test.c
+++ b/src/host/common_test.c
@@ -232,3 +232,17 @@
 {
 	return NULL;
 }
+
+int dma_sg_alloc(struct dma_sg_elem_array *elem_array,
+		 int zone,
+		 uint32_t direction,
+		 uint32_t buffer_count, uint32_t buffer_bytes,
+		 uintptr_t dma_buffer_addr, uintptr_t external_addr)
+{
+	return 0;
+}
+
+void dma_sg_free(struct dma_sg_elem_array *elem_array)
+{
+}
+
diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h
index 975bbeb..77e0f53 100644
--- a/src/include/sof/audio/component.h
+++ b/src/include/sof/audio/component.h
@@ -173,8 +173,9 @@
 	int (*copy)(struct comp_dev *dev);
 
 	/* host buffer config */
-	int (*host_buffer)(struct comp_dev *dev, struct dma_sg_elem *elem,
-			uint32_t host_size);
+	int (*host_buffer)(struct comp_dev *dev,
+			   struct dma_sg_elem_array *elem_array,
+			   uint32_t host_size);
 
 	/* position */
 	int (*position)(struct comp_dev *dev,
@@ -267,10 +268,10 @@
  * mandatory for host components, optional for the others.
  */
 static inline int comp_host_buffer(struct comp_dev *dev,
-	struct dma_sg_elem *elem, uint32_t host_size)
+		struct dma_sg_elem_array *elem_array, uint32_t host_size)
 {
 	if (dev->drv->ops.host_buffer)
-		return dev->drv->ops.host_buffer(dev, elem, host_size);
+		return dev->drv->ops.host_buffer(dev, elem_array, host_size);
 	return 0;
 }
 
diff --git a/src/include/sof/dma-trace.h b/src/include/sof/dma-trace.h
index 81ce0a2..e970e53 100644
--- a/src/include/sof/dma-trace.h
+++ b/src/include/sof/dma-trace.h
@@ -70,8 +70,9 @@
 
 int dma_trace_init_early(struct sof *sof);
 int dma_trace_init_complete(struct dma_trace_data *d);
-int dma_trace_host_buffer(struct dma_trace_data *d, struct dma_sg_elem *elem,
-	uint32_t host_size);
+int dma_trace_host_buffer(struct dma_trace_data *d,
+			  struct dma_sg_elem_array *elem_array,
+			  uint32_t host_size);
 int dma_trace_enable(struct dma_trace_data *d);
 void dma_trace_flush(void *t);
 
diff --git a/src/include/sof/dma.h b/src/include/sof/dma.h
index 491938f..24e6ccb 100644
--- a/src/include/sof/dma.h
+++ b/src/include/sof/dma.h
@@ -91,24 +91,34 @@
 
 struct dma;
 
+/**
+ *  \brief Element of SG list (as array item).
+ */
 struct dma_sg_elem {
-	uint32_t src;
-	uint32_t dest;
-	uint32_t size;
-	struct list_item list;
+	uint32_t src;	/**< source address */
+	uint32_t dest;	/**< destination address */
+	uint32_t size;	/**< size (in bytes) */
+};
+
+/**
+ * \brief SG elem array.
+ */
+struct dma_sg_elem_array {
+	uint32_t count;			/**< number of elements in elems */
+	struct dma_sg_elem *elems;	/**< elements */
 };
 
 /* DMA physical SG params */
 struct dma_sg_config {
-	uint32_t src_width;	/* in bytes */
-	uint32_t dest_width;	/* in bytes */
+	uint32_t src_width;			/* in bytes */
+	uint32_t dest_width;			/* in bytes */
 	uint32_t burst_elems;
 	uint32_t direction;
 	uint32_t src_dev;
 	uint32_t dest_dev;
-	uint32_t cyclic;	/* circular buffer */
+	uint32_t cyclic;			/* circular buffer */
 	uint32_t timer_delay;	/* non zero if timer scheduled */
-	struct list_item elem_list;	/* list of dma_sg elems */
+	struct dma_sg_elem_array elem_array;	/* array of dma_sg elems */
 };
 
 struct dma_chan_status {
@@ -281,18 +291,46 @@
 	return dma->ops->probe(dma);
 }
 
-/* get the size of SG buffer */
-static inline uint32_t dma_sg_get_size(struct dma_sg_config *sg)
+static inline void dma_sg_init(struct dma_sg_elem_array *ea)
 {
-	struct dma_sg_elem *sg_elem;
-	struct list_item *plist;
+	ea->count = 0;
+	ea->elems = NULL;
+}
+
+int dma_sg_alloc(struct dma_sg_elem_array *ea,
+		 int zone,
+		 uint32_t direction,
+		 uint32_t buffer_count, uint32_t buffer_bytes,
+		 uintptr_t dma_buffer_addr, uintptr_t external_addr);
+
+void dma_sg_free(struct dma_sg_elem_array *ea);
+
+static inline void dma_sg_cache_wb_inv(struct dma_sg_elem_array *ea)
+{
+	dcache_writeback_invalidate_region(ea->elems,
+					   ea->count *
+					   sizeof(struct dma_sg_elem));
+}
+
+static inline void dma_sg_cache_inv(struct dma_sg_elem_array *ea)
+{
+	dcache_invalidate_region(ea->elems,
+				 ea->count * sizeof(struct dma_sg_elem));
+}
+
+/**
+ * \brief Get the total size of SG buffer
+ *
+ * \param ea Array of SG elements.
+ * \return Size of the buffer.
+ */
+static inline uint32_t dma_sg_get_size(struct dma_sg_elem_array *ea)
+{
+	int i;
 	uint32_t size = 0;
 
-	list_for_item(plist, &sg->elem_list) {
-
-		sg_elem = container_of(plist, struct dma_sg_elem, list);
-		size += sg_elem->size;
-	}
+	for (i = 0 ; i < ea->count; i++)
+		size += ea->elems[i].size;
 
 	return size;
 }
diff --git a/src/include/sof/ipc.h b/src/include/sof/ipc.h
index 7564233..0844d3d 100644
--- a/src/include/sof/ipc.h
+++ b/src/include/sof/ipc.h
@@ -145,7 +145,7 @@
 /* create a SG page table eme list from a compressed page table */
 int ipc_parse_page_descriptors(uint8_t *page_table,
 			       struct sof_ipc_host_buffer *ring,
-			       struct list_item *elem_list,
+			       struct dma_sg_elem_array *elem_array,
 			       uint32_t direction);
 int ipc_get_page_descriptors(struct dma *dmac, uint8_t *page_table,
 			     struct sof_ipc_host_buffer *ring);
diff --git a/src/ipc/dma-copy.c b/src/ipc/dma-copy.c
index a8c3f5d..2e8da36 100644
--- a/src/ipc/dma-copy.c
+++ b/src/ipc/dma-copy.c
@@ -44,17 +44,18 @@
 #define trace_dma_error(__e)	trace_error(TRACE_CLASS_DMA, __e)
 #define tracev_dma(__e)	tracev_event(TRACE_CLASS_DMA, __e)
 
+#if !defined CONFIG_DMA_GW
 static struct dma_sg_elem *sg_get_elem_at(struct dma_sg_config *host_sg,
 	int32_t *offset)
 {
 	struct dma_sg_elem *host_sg_elem;
-	struct list_item *plist;
+	int i;
 	int32_t _offset = *offset;
  
 	/* find host element with host_offset */
-	list_for_item(plist, &host_sg->elem_list) {
+	for (i = 0; i < host_sg->elem_array.count; i++) {
 
-		host_sg_elem = container_of(plist, struct dma_sg_elem, list);
+		host_sg_elem = host_sg->elem_array.elems + i;
 
 		/* is offset in this elem ? */
 		if (_offset >= 0 && _offset < host_sg_elem->size) {
@@ -69,6 +70,7 @@
 	trace_dma_error("ex0");
 	return NULL;
 }
+#endif
 
 #if !defined CONFIG_DMA_GW
 
@@ -130,7 +132,7 @@
 	config.src_width = sizeof(uint32_t);
 	config.dest_width = sizeof(uint32_t);
 	config.cyclic = 0;
-	list_init(&config.elem_list);
+	dma_sg_init(&config.elem_array);
 
 	/* configure local DMA elem */
 	local_sg_elem.dest = host_sg_elem->dest + offset;
@@ -140,7 +142,8 @@
 	else
 		local_sg_elem.size = size;
 
-	list_item_prepend(&local_sg_elem.list, &config.elem_list);
+	config.elem_array.elems = &local_sg_elem;
+	config.elem_array.count = 1;
 
 	/* start the DMA */
 	err = dma_set_config(dc->dmac, dc->chan, &config);
@@ -157,138 +160,6 @@
 
 #endif
 
-/* Copy host memory to DSP memory.
- * Copies host memory to host in PAGE_SIZE or smaller blocks and waits/sleeps
- * between blocks. Cant be used in IRQ context.
- */
-int dma_copy_from_host(struct dma_copy *dc, struct dma_sg_config *host_sg,
-	int32_t host_offset, void *local_ptr, int32_t size)
-{
-	struct dma_sg_config config;
-	struct dma_sg_elem *host_sg_elem;
-	struct dma_sg_elem local_sg_elem;
-	int32_t err;
-	int32_t offset = host_offset;
-	int32_t bytes_copied = 0;
-
-	if (size <= 0)
-		return 0;
-
-	/* find host element with host_offset */
-	host_sg_elem = sg_get_elem_at(host_sg, &offset);
-	if (host_sg_elem == NULL)
-		return -EINVAL;
-
-	/* set up DMA configuration */
-	config.direction = DMA_DIR_HMEM_TO_LMEM;
-	config.src_width = sizeof(uint32_t);
-	config.dest_width = sizeof(uint32_t);
-	config.cyclic = 0;
-	list_init(&config.elem_list);
-
-	/* configure local DMA elem */
-	local_sg_elem.dest = (uint32_t)local_ptr;
-	local_sg_elem.src = host_sg_elem->src + offset;
-	if (size >= HOST_PAGE_SIZE - offset)
-		local_sg_elem.size = HOST_PAGE_SIZE - offset;
-	else
-		local_sg_elem.size = size;
-	list_item_prepend(&local_sg_elem.list, &config.elem_list);
-
-	/* transfer max PAGE size at a time to SG buffer */
-	while (size > 0) {
-
-		/* start the DMA */
-		wait_init(&dc->complete);
-		err = dma_set_config(dc->dmac, dc->chan, &config);
-		if (err < 0)
-			return err;
-
-		err = dma_start(dc->dmac, dc->chan);
-		if (err < 0)
-			return err;
-
-		/* wait for DMA to complete */
-		err = wait_for_completion_timeout(&dc->complete);
-		if (err < 0) {
-			trace_dma_error("ex2");
-			return -EIO;
-		}
-
-		/* update offset and bytes remaining */
-		size -= local_sg_elem.size;
-		host_offset += local_sg_elem.size;
-
-		/* next dest host address is in next host elem */
-		host_sg_elem = list_next_item(host_sg_elem, list);
-		local_sg_elem.src = host_sg_elem->src;
-
-		/* local address is continuous */
-		local_sg_elem.dest = (uint32_t)local_ptr + local_sg_elem.size;
-
-		bytes_copied += local_sg_elem.size;
-
-		/* do we have less than 1 PAGE to copy ? */
-		if (size >= HOST_PAGE_SIZE)
-			local_sg_elem.size = HOST_PAGE_SIZE;
-		else
-			local_sg_elem.size = size;
-	}
-
-	/* bytes copied */
-	return bytes_copied;
-}
-
-/* Copy host memory to DSP memory.
- * Copies host memory to DSP in a single PAGE_SIZE or smaller block. Does not
- * waits/sleeps and can be used in IRQ context.
- */
-int dma_copy_from_host_nowait(struct dma_copy *dc, struct dma_sg_config *host_sg,
-	int32_t host_offset, void *local_ptr, int32_t size)
-{
-	struct dma_sg_config config;
-	struct dma_sg_elem *host_sg_elem;
-	struct dma_sg_elem local_sg_elem;
-	int32_t err;
-	int32_t offset = host_offset;
-
-	if (size <= 0)
-		return 0;
-
-	/* find host element with host_offset */
-	host_sg_elem = sg_get_elem_at(host_sg, &offset);
-	if (host_sg_elem == NULL)
-		return -EINVAL;
-
-	/* set up DMA configuration */
-	config.direction = DMA_DIR_HMEM_TO_LMEM;
-	config.src_width = sizeof(uint32_t);
-	config.dest_width = sizeof(uint32_t);
-	config.cyclic = 0;
-	list_init(&config.elem_list);
-
-	/* configure local DMA elem */
-	local_sg_elem.dest = (uint32_t)local_ptr;
-	local_sg_elem.src = host_sg_elem->src + offset;
-	if (size >= HOST_PAGE_SIZE - offset)
-		local_sg_elem.size = HOST_PAGE_SIZE - offset;
-	else
-		local_sg_elem.size = size;
-	list_item_prepend(&local_sg_elem.list, &config.elem_list);
-
-	/* start the DMA */
-	err = dma_set_config(dc->dmac, dc->chan, &config);
-	if (err < 0)
-		return err;
-
-	err = dma_start(dc->dmac, dc->chan);
-	if (err < 0)
-		return err;
-
-	/* bytes copied */
-	return local_sg_elem.size;
-}
-
 int dma_copy_new(struct dma_copy *dc)
 {
 	uint32_t dir, cap, dev;
diff --git a/src/ipc/handler.c b/src/ipc/handler.c
index 0e15821..3711fac 100644
--- a/src/ipc/handler.c
+++ b/src/ipc/handler.c
@@ -170,10 +170,7 @@
 #ifdef CONFIG_HOST_PTABLE
 	struct intel_ipc_data *iipc = ipc_get_drvdata(_ipc);
 	struct sof_ipc_comp_host *host = NULL;
-	struct list_item elem_list;
-	struct dma_sg_elem *elem;
-	struct list_item *clist;
-	struct list_item *tlist;
+	struct dma_sg_elem_array elem_array;
 	uint32_t ring_size;
 #endif
 	struct sof_ipc_pcm_params *pcm_params = _ipc->comp_data;
@@ -210,7 +207,7 @@
 	cd->params = pcm_params->params;
 
 #ifdef CONFIG_HOST_PTABLE
-	list_init(&elem_list);
+	dma_sg_init(&elem_array);
 
 	/*
 	 * walk in both directions to check if the pipeline is hostless
@@ -233,23 +230,16 @@
 
 	err = ipc_parse_page_descriptors(iipc->page_table,
 					 &pcm_params->params.buffer,
-					 &elem_list, host->direction);
+					 &elem_array, host->direction);
 	if (err < 0) {
 		trace_ipc_error("eAP");
 		goto error;
 	}
 
-	list_for_item_safe(clist, tlist, &elem_list) {
-		elem = container_of(clist, struct dma_sg_elem, list);
-
-		err = comp_host_buffer(cd, elem, ring_size);
-		if (err < 0) {
-			trace_ipc_error("ePb");
-			goto error;
-		}
-
-		list_item_del(&elem->list);
-		rfree(elem);
+	err = comp_host_buffer(cd, &elem_array, ring_size);
+	if (err < 0) {
+		trace_ipc_error("ePb");
+		goto error;
 	}
 
 pipe_params:
@@ -285,11 +275,7 @@
 
 error:
 #ifdef CONFIG_HOST_PTABLE
-	list_for_item_safe(clist, tlist, &elem_list) {
-		elem = container_of(clist, struct dma_sg_elem, list);
-		list_item_del(&elem->list);
-		rfree(elem);
-	}
+	dma_sg_free(&elem_array);
 #endif
 
 	err = pipeline_reset(pcm_dev->cd->pipeline, pcm_dev->cd);
@@ -636,10 +622,7 @@
 {
 #ifdef CONFIG_HOST_PTABLE
 	struct intel_ipc_data *iipc = ipc_get_drvdata(_ipc);
-	struct list_item elem_list;
-	struct dma_sg_elem *elem;
-	struct list_item *clist;
-	struct list_item *tlist;
+	struct dma_sg_elem_array elem_array;
 	uint32_t ring_size;
 #endif
 	struct sof_ipc_dma_trace_params *params = _ipc->comp_data;
@@ -656,7 +639,7 @@
 
 #ifdef CONFIG_HOST_PTABLE
 
-	list_init(&elem_list);
+	dma_sg_init(&elem_array);
 
 	/* use DMA to read in compressed page table ringbuffer from host */
 	err = ipc_get_page_descriptors(iipc->dmac, iipc->page_table,
@@ -672,24 +655,18 @@
 	ring_size = params->buffer.size;
 
 	err = ipc_parse_page_descriptors(iipc->page_table, &params->buffer,
-					 &elem_list, SOF_IPC_STREAM_CAPTURE);
+					 &elem_array, SOF_IPC_STREAM_CAPTURE);
 	if (err < 0) {
 		trace_ipc_error("ePP");
 		goto error;
 	}
 
-	list_for_item_safe(clist, tlist, &elem_list) {
-		elem = container_of(clist, struct dma_sg_elem, list);
-
-		err = dma_trace_host_buffer(_ipc->dmat, elem, ring_size);
-		if (err < 0) {
-			trace_ipc_error("ePb");
-			goto error;
-		}
-
-		list_item_del(&elem->list);
-		rfree(elem);
+	err = dma_trace_host_buffer(_ipc->dmat, &elem_array, ring_size);
+	if (err < 0) {
+		trace_ipc_error("ePb");
+		goto error;
 	}
+
 #else
 	/* stream tag of capture stream for DMA trace */
 	_ipc->dmat->stream_tag = params->stream_tag;
@@ -712,11 +689,7 @@
 
 error:
 #ifdef CONFIG_HOST_PTABLE
-	list_for_item_safe(clist, tlist, &elem_list) {
-		elem = container_of(clist, struct dma_sg_elem, list);
-		list_item_del(&elem->list);
-		rfree(elem);
-	}
+	dma_sg_free(&elem_array);
 #endif
 
 	if (err < 0)
diff --git a/src/ipc/ipc.c b/src/ipc/ipc.c
index fb1db34..dd45687 100644
--- a/src/ipc/ipc.c
+++ b/src/ipc/ipc.c
@@ -387,7 +387,7 @@
  */
 int ipc_parse_page_descriptors(uint8_t *page_table,
 			       struct sof_ipc_host_buffer *ring,
-			       struct list_item *elem_list,
+			       struct dma_sg_elem_array *elem_array,
 			       uint32_t direction)
 {
 	int i;
@@ -406,6 +406,12 @@
 		return -EINVAL;
 	}
 
+	elem_array->elems = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
+				    sizeof(struct dma_sg_elem) * ring->pages);
+	if (!elem_array->elems)
+		return -ENOMEM;
+	elem_array->count = ring->pages;
+
 	for (i = 0; i < ring->pages; i++) {
 		idx = (((i << 2) + i)) >> 1;
 		phy_addr = page_table[idx] | (page_table[idx + 1] << 8)
@@ -417,10 +423,7 @@
 			phy_addr <<= 12;
 		phy_addr &= 0xfffff000;
 
-		/* allocate new host DMA elem and add it to our list */
-		e = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*e));
-		if (!e)
-			return -ENOMEM;
+		e = elem_array->elems + i;
 
 		if (direction == SOF_IPC_STREAM_PLAYBACK)
 			e->src = phy_addr;
@@ -432,8 +435,6 @@
 			e->size = ring->size - HOST_PAGE_SIZE * i;
 		else
 			e->size = HOST_PAGE_SIZE;
-
-		list_item_append(&e->list, elem_list);
 	}
 
 	return 0;
@@ -471,7 +472,7 @@
 	config.src_width = sizeof(uint32_t);
 	config.dest_width = sizeof(uint32_t);
 	config.cyclic = 0;
-	list_init(&config.elem_list);
+	dma_sg_init(&config.elem_array);
 
 	/* set up DMA descriptor */
 	elem.dest = (uint32_t)page_table;
@@ -480,7 +481,8 @@
 	/* source buffer size is always PAGE_SIZE bytes */
 	/* 20 bits for each page, round up to 32 */
 	elem.size = (ring->pages * 5 * 16 + 31) / 32;
-	list_item_prepend(&elem.list, &config.elem_list);
+	config.elem_array.elems = &elem;
+	config.elem_array.count = 1;
 
 	ret = dma_set_config(dmac, chan, &config);
 	if (ret < 0) {
diff --git a/src/lib/dma-trace.c b/src/lib/dma-trace.c
index f3690ed..065849f 100644
--- a/src/lib/dma-trace.c
+++ b/src/lib/dma-trace.c
@@ -122,7 +122,7 @@
 	trace_data = rzalloc(RZONE_SYS | RZONE_FLAG_UNCACHED, SOF_MEM_CAPS_RAM,
 			     sizeof(*trace_data));
 
-	list_init(&trace_data->config.elem_list);
+	dma_sg_init(&trace_data->config.elem_array);
 	spinlock_init(&trace_data->lock);
 	sof->dmat = trace_data;
 
@@ -147,25 +147,17 @@
 	return 0;
 }
 
-int dma_trace_host_buffer(struct dma_trace_data *d, struct dma_sg_elem *elem,
+#if defined(CONFIG_HOST_PTABLE)
+int dma_trace_host_buffer(struct dma_trace_data *d,
+			  struct dma_sg_elem_array *elem_array,
 			  uint32_t host_size)
 {
-	struct dma_sg_elem *e;
-
-	/* allocate new host DMA elem and add it to our list */
-	e = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*e));
-	if (e == NULL)
-		return -ENOMEM;
-
-	/* copy fields - excluding possibly non-initialized elem->src */
-	e->dest = elem->dest;
-	e->size = elem->size;
-
 	d->host_size = host_size;
+	d->config.elem_array = *elem_array;
 
-	list_item_append(&e->list, &d->config.elem_list);
 	return 0;
 }
+#endif
 
 static int dma_trace_buffer_init(struct dma_trace_data *d)
 {
@@ -198,10 +190,8 @@
 static int dma_trace_start(struct dma_trace_data *d)
 {
 	struct dma_sg_config config;
-	struct dma_sg_elem *e;
 	uint32_t elem_size, elem_addr, elem_num;
 	int err = 0;
-	int i;
 
 	err = dma_copy_set_stream_tag(&d->dc, d->stream_tag);
 	if (err < 0)
@@ -220,31 +210,17 @@
 	config.src_width = sizeof(uint32_t);
 	config.dest_width = sizeof(uint32_t);
 	config.cyclic = 0;
-	list_init(&config.elem_list);
 
-	/* generate local elem list for local trace buffer */
-	e = rzalloc(RZONE_SYS, SOF_MEM_CAPS_RAM, sizeof(*e) * elem_num);
-	if (!e)
-		return -ENOMEM;
-
-	for (i = 0; i < elem_num; i++) {
-		e[i].dest = 0;
-		e[i].src = elem_addr;
-		e[i].size = elem_size; /* the minimum size of DMA copy */
-
-		list_item_append(&e[i].list, &config.elem_list);
-		elem_addr += elem_size;
-	}
+	err = dma_sg_alloc(&config.elem_array, RZONE_SYS,
+			   config.direction,
+			   elem_num, elem_size, elem_addr, 0);
 
 	err = dma_set_config(d->dc.dmac, d->dc.chan, &config);
-	if (err < 0) {
-		rfree(e);
+	if (err < 0)
 		return err;
-	}
 
 	err = dma_start(d->dc.dmac, d->dc.chan);
 
-	rfree(e);
 	return err;
 }
 
diff --git a/src/lib/dma.c b/src/lib/dma.c
index 53fae96..5634fec 100644
--- a/src/lib/dma.c
+++ b/src/lib/dma.c
@@ -30,6 +30,7 @@
 
 #include <sof/dma.h>
 #include <sof/atomic.h>
+#include <sof/alloc.h>
 #include <platform/dma.h>
 
 struct dma_info {
@@ -99,3 +100,43 @@
 
 	return NULL;
 }
+
+int dma_sg_alloc(struct dma_sg_elem_array *elem_array,
+		 int zone,
+		 uint32_t direction,
+		 uint32_t buffer_count, uint32_t buffer_bytes,
+		 uintptr_t dma_buffer_addr, uintptr_t external_addr)
+{
+	int i;
+
+	elem_array->elems = rzalloc(zone, SOF_MEM_CAPS_RAM,
+				    sizeof(struct dma_sg_elem) * buffer_count);
+	if (!elem_array->elems)
+		return -ENOMEM;
+
+	for (i = 0; i < buffer_count; i++) {
+		elem_array->elems[i].size = buffer_bytes;
+		// TODO: may count offsets once
+		switch (direction) {
+		case DMA_DIR_MEM_TO_DEV:
+		case DMA_DIR_LMEM_TO_HMEM:
+			elem_array->elems[i].src = dma_buffer_addr;
+			elem_array->elems[i].dest = external_addr;
+			break;
+		default:
+			elem_array->elems[i].src = external_addr;
+			elem_array->elems[i].dest = dma_buffer_addr;
+			break;
+		}
+
+		dma_buffer_addr += buffer_bytes;
+	}
+	elem_array->count = buffer_count;
+	return 0;
+}
+
+void dma_sg_free(struct dma_sg_elem_array *elem_array)
+{
+	rfree(elem_array->elems);
+	dma_sg_init(elem_array);
+}