[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