1 // SPDX-License-Identifier: BSD-3-Clause
2 //
3 // Copyright(c) 2016 Intel Corporation. All rights reserved.
4 //
5 // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
6 //         Keyon Jie <yang.jie@linux.intel.com>
7 
8 #include <sof/audio/buffer.h>
9 #include <sof/audio/component_ext.h>
10 #include <sof/audio/pipeline.h>
11 #include <sof/ipc/msg.h>
12 #include <sof/list.h>
13 #include <sof/spinlock.h>
14 #include <sof/string.h>
15 #include <ipc/header.h>
16 #include <ipc/stream.h>
17 #include <ipc/topology.h>
18 #include <errno.h>
19 #include <stdbool.h>
20 #include <stddef.h>
21 #include <stdint.h>
22 
23 /*
24  * This flag disables firmware-side xrun recovery.
25  * It should remain enabled in the situation when the
26  * recovery is delegated to the outside of firmware.
27  */
28 #define NO_XRUN_RECOVERY 1
29 
pipeline_comp_xrun(struct comp_dev * current,struct comp_buffer * calling_buf,struct pipeline_walk_context * ctx,int dir)30 static int pipeline_comp_xrun(struct comp_dev *current,
31 			      struct comp_buffer *calling_buf,
32 			      struct pipeline_walk_context *ctx, int dir)
33 {
34 	struct pipeline_data *ppl_data = ctx->comp_data;
35 
36 	if (dev_comp_type(current) == SOF_COMP_HOST) {
37 		/* get host timestamps */
38 		platform_host_timestamp(current, ppl_data->posn);
39 
40 		/* send XRUN to host */
41 		mailbox_stream_write(ppl_data->p->posn_offset, ppl_data->posn,
42 				     sizeof(*ppl_data->posn));
43 		ipc_msg_send(ppl_data->p->msg, ppl_data->posn, true);
44 	}
45 
46 	return pipeline_for_each_comp(current, ctx, dir);
47 }
48 
49 #if NO_XRUN_RECOVERY
50 /* recover the pipeline from a XRUN condition */
pipeline_xrun_recover(struct pipeline * p)51 int pipeline_xrun_recover(struct pipeline *p)
52 {
53 	return -EINVAL;
54 }
55 
56 #else
57 /* recover the pipeline from a XRUN condition */
pipeline_xrun_recover(struct pipeline * p)58 int pipeline_xrun_recover(struct pipeline *p)
59 {
60 	int ret;
61 
62 	pipe_err(p, "pipeline_xrun_recover()");
63 
64 	/* prepare the pipeline */
65 	ret = pipeline_prepare(p, p->source_comp);
66 	if (ret < 0) {
67 		pipe_err(p, "pipeline_xrun_recover(): pipeline_prepare() failed, ret = %d",
68 			 ret);
69 		return ret;
70 	}
71 
72 	/* reset xrun status as we already in prepared */
73 	p->xrun_bytes = 0;
74 
75 	/* restart pipeline comps */
76 	ret = pipeline_trigger(p, p->source_comp, COMP_TRIGGER_START);
77 	if (ret < 0) {
78 		pipe_err(p, "pipeline_xrun_recover(): pipeline_trigger() failed, ret = %d",
79 			 ret);
80 		return ret;
81 	}
82 
83 	return 0;
84 }
85 #endif
86 
pipeline_xrun_set_limit(struct pipeline * p,uint32_t xrun_limit_usecs)87 int pipeline_xrun_set_limit(struct pipeline *p, uint32_t xrun_limit_usecs)
88 {
89 	/* TODO: these could be validated against min/max permissible values */
90 	p->xrun_limit_usecs = xrun_limit_usecs;
91 	return 0;
92 }
93 
94 /*
95  * trigger handler for pipelines in xrun, used for recovery from host only.
96  * return values:
97  *	0 -- success, further trigger in caller needed.
98  *	PPL_STATUS_PATH_STOP -- done, no more further trigger needed.
99  *	minus -- failed, caller should return failure.
100  */
pipeline_xrun_handle_trigger(struct pipeline * p,int cmd)101 int pipeline_xrun_handle_trigger(struct pipeline *p, int cmd)
102 {
103 	int ret = 0;
104 
105 	/* it is expected in paused status for xrun pipeline */
106 	if (!p->xrun_bytes || p->status != COMP_STATE_PAUSED)
107 		return 0;
108 
109 	/* in xrun, handle start/stop trigger */
110 	switch (cmd) {
111 	case COMP_TRIGGER_START:
112 		/* in xrun, prepare before trigger start needed */
113 		pipe_info(p, "in xrun, prepare it first");
114 		/* prepare the pipeline */
115 		ret = pipeline_prepare(p, p->source_comp);
116 		if (ret < 0) {
117 			pipe_err(p, "prepare: ret = %d", ret);
118 			return ret;
119 		}
120 		/* now ready for start, clear xrun_bytes */
121 		p->xrun_bytes = 0;
122 		break;
123 	case COMP_TRIGGER_STOP:
124 		/* in xrun, suppose pipeline is already stopped, ignore it */
125 		pipe_info(p, "already stopped in xrun");
126 		/* no more further trigger stop needed */
127 		ret = PPL_STATUS_PATH_STOP;
128 		break;
129 	}
130 
131 	return ret;
132 }
133 
134 /* Send an XRUN to each host for this component. */
pipeline_xrun(struct pipeline * p,struct comp_dev * dev,int32_t bytes)135 void pipeline_xrun(struct pipeline *p, struct comp_dev *dev,
136 		   int32_t bytes)
137 {
138 	struct pipeline_data data;
139 	struct pipeline_walk_context walk_ctx = {
140 		.comp_func = pipeline_comp_xrun,
141 		.comp_data = &data,
142 		.skip_incomplete = true,
143 	};
144 	struct sof_ipc_stream_posn posn;
145 	int ret;
146 
147 	/* don't flood host */
148 	if (p->xrun_bytes)
149 		return;
150 
151 	/* only send when we are running */
152 	if (dev->state != COMP_STATE_ACTIVE)
153 		return;
154 
155 	/* notify all pipeline comps we are in XRUN, and stop copying */
156 	ret = pipeline_trigger(p, p->source_comp, COMP_TRIGGER_XRUN);
157 	if (ret < 0)
158 		pipe_err(p, "pipeline_xrun(): Pipelines notification about XRUN failed, ret = %d",
159 			 ret);
160 
161 	memset(&posn, 0, sizeof(posn));
162 	ipc_build_stream_posn(&posn, SOF_IPC_STREAM_TRIG_XRUN,
163 			      dev_comp_id(dev));
164 	p->xrun_bytes = bytes;
165 	posn.xrun_size = bytes;
166 	posn.xrun_comp_id = dev_comp_id(dev);
167 	data.posn = &posn;
168 	data.p = p;
169 
170 	walk_ctx.comp_func(dev, NULL, &walk_ctx, dev->direction);
171 }
172