[Sound-open-firmware] [PATCH] host: Add support for non continuous host buffers

Liam Girdwood liam.r.girdwood at linux.intel.com
Thu Dec 22 14:05:12 CET 2016


Host buffers are not guaranteed to be continuous physical pages or a factor
of the DSP period size. Add support for non continuous host buffers of any
size.

Signed-off-by: Liam Girdwood <liam.r.girdwood at linux.intel.com>
---
 src/audio/host.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 169 insertions(+), 27 deletions(-)

diff --git a/src/audio/host.c b/src/audio/host.c
index d544924..f339bc2 100644
--- a/src/audio/host.c
+++ b/src/audio/host.c
@@ -76,6 +76,8 @@ struct host_data {
 	/* pointers set during params to host or local above */
 	struct hc_buf *source;
 	struct hc_buf *sink;
+	uint32_t split_remaining;
+	uint32_t next_inc;
 
 	/* stream info */
 	struct stream_params params;
@@ -95,10 +97,17 @@ static inline struct dma_sg_elem *next_buffer(struct hc_buf *hc)
 	return elem;
 }
 
-/* this is called by DMA driver every time descriptor has completed */
-static void host_dma_cb(void *data, uint32_t type)
+/*
+ * Host period copy to DSP DMA completion. This is called when DMA completes
+ * its current transfer from host to DSP. The host memory is not guaranteed
+ * to be continuous and also not guaranteed to have a period/buffer size that
+ * is a multiple of the DSP period size. This means we must check we do not
+ * overflow host period/buffer/page boundaries on each transfer and split the
+ * DMA transfer if we do overflow.
+ */
+static void host_dma_cb_playback(struct comp_dev *dev,
+	struct dma_sg_elem *next)
 {
-	struct comp_dev *dev = (struct comp_dev *)data;
 	struct host_data *hd = comp_get_drvdata(dev);
 	struct dma_sg_elem *local_elem, *source_elem, *sink_elem;
 	struct comp_buffer *dma_buffer;
@@ -106,6 +115,63 @@ static void host_dma_cb(void *data, uint32_t type)
 	local_elem = list_first_item(&hd->config.elem_list,
 		struct dma_sg_elem, list);
 
+	trace_host("Cpp");
+
+	/* are we dealing with a split transfer */
+	if (hd->split_remaining) {
+
+		/* update local elem */
+		local_elem->dest += local_elem->size;
+		source_elem = next_buffer(hd->source);
+		hd->source->current_end = source_elem->src + source_elem->size;
+		local_elem->src = source_elem->src;
+
+		/* set up next elem */
+		local_elem->size = hd->split_remaining;
+		hd->next_inc = hd->split_remaining;
+		hd->split_remaining = 0;
+
+	} else {
+		/* destination is always DSP period size */
+		sink_elem = next_buffer(hd->sink);
+
+		local_elem->dest = sink_elem->dest;
+		local_elem->size = hd->period->size;
+		local_elem->src += hd->next_inc;
+		hd->next_inc = hd->period->size;
+
+		/* are we at end of elem */
+		if (local_elem->src == hd->source->current_end) {
+
+			/* end of elem, so use next */
+			source_elem = next_buffer(hd->source);
+			hd->source->current_end = source_elem->src + source_elem->size;
+			local_elem->src = source_elem->src;
+
+		} else if (local_elem->src + hd->period->size > hd->source->current_end) {
+
+			/* split copy - split transaction into 2 copies */
+			local_elem->size = hd->source->current_end - local_elem->src;
+			hd->split_remaining = hd->period->size - local_elem->size;
+
+			next->src = local_elem->src;
+			next->dest = local_elem->dest;
+			next->size = local_elem->size;
+			return;
+		}
+	}
+
+	/* update buffer positions */
+	dma_buffer = hd->dma_buffer;
+
+	dma_buffer->w_ptr += hd->period->size;
+
+	if (dma_buffer->w_ptr >= dma_buffer->end_addr)
+		dma_buffer->w_ptr = dma_buffer->addr;
+#if 0
+	trace_value((uint32_t)(hd->dma_buffer->w_ptr - hd->dma_buffer->addr));
+#endif
+
 	/* new local period, update host buffer position blks */
 	hd->host_pos_blks += hd->period->size;
 
@@ -115,45 +181,103 @@ static void host_dma_cb(void *data, uint32_t type)
 	if (hd->host_pos)
 		*hd->host_pos = hd->host_pos_blks;
 
-	/* update source buffer elem and check for overflow */
-	local_elem->src += hd->period->size;
-	if (local_elem->src >= hd->source->current_end) {
+	/* recalc available buffer space */
+	comp_update_buffer(hd->dma_buffer);
 
-		/* move onto next host side elem */
-		source_elem = next_buffer(hd->source);
-		hd->source->current_end = source_elem->src + source_elem->size;
-		local_elem->src = source_elem->src;
+	/* send IPC message to driver if needed */
+	hd->host_period_pos += hd->period->size;
+	if (hd->host_period_pos >= hd->host_period_bytes) {
+		hd->host_period_pos = 0;
+		ipc_stream_send_notification(dev, &hd->cp);
 	}
 
-	/* update sink buffer elem and check for overflow */
-	local_elem->dest += hd->period->size;
-	if (local_elem->dest >= hd->sink->current_end) {
+	/* let any waiters know we have completed */
+	wait_completed(&hd->complete);
+}
+
+/*
+ * DSP period copy to host DMA completion. This is called when DMA completes
+ * its current transfer from DSP to host. The host memory is not guaranteed
+ * to be continuous and also not guaranteed to have a period/buffer size that
+ * is a multiple of the DSP period size. This means we must check we do not
+ * overflow host period/buffer/page boundaries on each transfer and split the
+ * DMA transfer if we do overflow.
+ */
+static void host_dma_cb_capture(struct comp_dev *dev,
+		struct dma_sg_elem *next)
+{
+	struct host_data *hd = comp_get_drvdata(dev);
+	struct dma_sg_elem *local_elem, *source_elem, *sink_elem;
+	struct comp_buffer *dma_buffer;
+
+	local_elem = list_first_item(&hd->config.elem_list,
+		struct dma_sg_elem, list);
+
+	trace_host("Cpc");
+
+	/* are we dealing with a split transfer */
+	if (hd->split_remaining) {
 
-		/* move onto next host side elem */
+		/* update local elem */
+		local_elem->src += local_elem->size;
 		sink_elem = next_buffer(hd->sink);
 		hd->sink->current_end = sink_elem->dest + sink_elem->size;
 		local_elem->dest = sink_elem->dest;
+
+		/* set up next elem */
+		local_elem->size = hd->split_remaining;
+		hd->next_inc = hd->split_remaining;
+		hd->split_remaining = 0;
+
+	} else {
+		/* source is always DSP period size */
+		source_elem = next_buffer(hd->source);
+
+		local_elem->src = source_elem->src;
+		local_elem->size = hd->period->size;
+		local_elem->dest += hd->next_inc;
+		hd->next_inc = hd->period->size;
+
+		/* are we at end of elem */
+		if (local_elem->dest == hd->sink->current_end) {
+
+			/* end of elem, so use next */
+			sink_elem = next_buffer(hd->sink);
+			hd->sink->current_end = sink_elem->dest + sink_elem->size;
+			local_elem->dest = sink_elem->dest;
+
+		} else if (local_elem->dest + hd->period->size > hd->sink->current_end) {
+
+			/* split copy - split transaction into 2 copies */
+			local_elem->size = hd->sink->current_end - local_elem->dest;
+			hd->split_remaining = hd->period->size - local_elem->size;
+
+			next->src = local_elem->src;
+			next->dest = local_elem->dest;
+			next->size = local_elem->size;
+			return;
+		}
 	}
 
 	/* update buffer positions */
 	dma_buffer = hd->dma_buffer;
-	if (hd->params.direction == STREAM_DIRECTION_PLAYBACK) {
-		dma_buffer->w_ptr += hd->period->size;
+	hd->dma_buffer->r_ptr += hd->period->size;
 
-		if (dma_buffer->w_ptr >= dma_buffer->end_addr)
-			dma_buffer->w_ptr = dma_buffer->addr;
+	if (dma_buffer->r_ptr >= dma_buffer->end_addr)
+		dma_buffer->r_ptr = dma_buffer->addr;
 #if 0
-		trace_value((uint32_t)(hd->dma_buffer->w_ptr - hd->dma_buffer->addr));
+	trace_value((uint32_t)(hd->dma_buffer->r_ptr - hd->dma_buffer->addr));
 #endif
-	} else {
-		hd->dma_buffer->r_ptr += hd->period->size;
 
-		if (dma_buffer->r_ptr >= dma_buffer->end_addr)
-			dma_buffer->r_ptr = dma_buffer->addr;
-#if 0
-		trace_value((uint32_t)(hd->dma_buffer->r_ptr - hd->dma_buffer->addr));
-#endif
-	}
+
+	/* new local period, update host buffer position blks */
+	hd->host_pos_blks += hd->period->size;
+
+	/* buffer overlap ? */
+	if (hd->host_pos_blks >= hd->host_size)
+		hd->host_pos_blks = 0;
+	if (hd->host_pos)
+		*hd->host_pos = hd->host_pos_blks;
 
 	/* recalc available buffer space */
 	comp_update_buffer(hd->dma_buffer);
@@ -169,6 +293,19 @@ static void host_dma_cb(void *data, uint32_t type)
 	wait_completed(&hd->complete);
 }
 
+/* this is called by DMA driver every time descriptor has completed */
+static void host_dma_cb(void *data, uint32_t type, struct dma_sg_elem *next)
+{
+
+	struct comp_dev *dev = (struct comp_dev *)data;
+	struct host_data *hd = comp_get_drvdata(dev);
+
+	if (hd->params.direction == STREAM_DIRECTION_PLAYBACK)
+		host_dma_cb_playback(dev, next);
+	else
+		host_dma_cb_capture(dev, next);
+}
+
 static struct comp_dev *host_new(uint32_t type, uint32_t index,
 	uint32_t direction)
 {
@@ -203,6 +340,7 @@ static struct comp_dev *host_new(uint32_t type, uint32_t index,
 	hd->host_pos = NULL;
 	hd->source = NULL;
 	hd->sink = NULL;
+	hd->split_remaining = 0;
 
 	/* init buffer elems */
 	list_init(&hd->config.elem_list);
@@ -347,6 +485,7 @@ static int host_params(struct comp_dev *dev, struct stream_params *params)
 	local_elem->dest = sink_elem->dest;
 	local_elem->size = hd->period->size;
 	local_elem->src = source_elem->src;
+	hd->next_inc = hd->period->size;
 	return 0;
 }
 
@@ -401,6 +540,8 @@ static int host_prepare(struct comp_dev *dev)
 	hd->host_period_pos = 0;
 	hd->host_period_bytes =
 		hd->params.period_frames * hd->params.frame_size;
+	hd->split_remaining = 0;
+
 
 	if (hd->params.direction == STREAM_DIRECTION_PLAYBACK)
 		host_preload(dev);
@@ -533,6 +674,7 @@ static int host_copy(struct comp_dev *dev)
 	if (dev->state != COMP_STATE_RUNNING)
 		return 0;
 
+	trace_host("CpS");
 	dma_set_config(hd->dma, hd->chan, &hd->config);
 	dma_start(hd->dma, hd->chan);
 
-- 
2.9.3



More information about the Sound-open-firmware mailing list