1// SPDX-License-Identifier: GPL-2.0
2//
3// xfer-libasound-irq-rw.c - IRQ-based scheduling model for read/write operation.
4//
5// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6//
7// Licensed under the terms of the GNU General Public License, version 2.
8
9#include "xfer-libasound.h"
10#include "misc.h"
11#include "frame-cache.h"
12
13struct rw_closure {
14	snd_pcm_access_t access;
15	int (*process_frames)(struct libasound_state *state,
16			      snd_pcm_state_t status, unsigned int *frame_count,
17			      struct mapper_context *mapper,
18		      struct container_context *cntrs);
19	struct frame_cache cache;
20};
21
22static int wait_for_avail(struct libasound_state *state)
23{
24	unsigned int msec_per_buffer;
25	unsigned short revents;
26	unsigned short event;
27	int err;
28
29	// Wait during msec equivalent to all audio data frames in buffer
30	// instead of period, for safe.
31	err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
32						&msec_per_buffer, NULL);
33	if (err < 0)
34		return err;
35	msec_per_buffer /= 1000;
36
37	// Wait for hardware IRQ when no available space.
38	err = xfer_libasound_wait_event(state, msec_per_buffer, &revents);
39	if (err < 0)
40		return err;
41
42	// TODO: error reporting.
43	if (revents & POLLERR)
44		return -EIO;
45
46	if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE)
47		event = POLLIN;
48	else
49		event = POLLOUT;
50
51	if (!(revents & event))
52		return -EAGAIN;
53
54	return 0;
55}
56
57static int read_frames(struct libasound_state *state, unsigned int *frame_count,
58		       unsigned int avail_count, struct mapper_context *mapper,
59		       struct container_context *cntrs)
60{
61	struct rw_closure *closure = state->private_data;
62	snd_pcm_sframes_t handled_frame_count;
63	unsigned int consumed_count;
64	int err;
65
66	// Trim according up to expected frame count.
67	if (*frame_count < avail_count)
68		avail_count = *frame_count;
69
70	// Cache required amount of frames.
71	if (avail_count > frame_cache_get_count(&closure->cache)) {
72		avail_count -= frame_cache_get_count(&closure->cache);
73
74		// Execute write operation according to the shape of buffer.
75		// These operations automatically start the substream.
76		if (closure->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
77			handled_frame_count = snd_pcm_readi(state->handle,
78							closure->cache.buf_ptr,
79							avail_count);
80		} else {
81			handled_frame_count = snd_pcm_readn(state->handle,
82							closure->cache.buf_ptr,
83							avail_count);
84		}
85		if (handled_frame_count < 0) {
86			err = handled_frame_count;
87			return err;
88		}
89		frame_cache_increase_count(&closure->cache, handled_frame_count);
90		avail_count = frame_cache_get_count(&closure->cache);
91	}
92
93	// Write out to file descriptors.
94	consumed_count = avail_count;
95	err = mapper_context_process_frames(mapper, closure->cache.buf,
96					    &consumed_count, cntrs);
97	if (err < 0)
98		return err;
99
100	frame_cache_reduce(&closure->cache, consumed_count);
101
102	*frame_count = consumed_count;
103
104	return 0;
105}
106
107static int r_process_frames_blocking(struct libasound_state *state,
108				     snd_pcm_state_t status,
109				     unsigned int *frame_count,
110				     struct mapper_context *mapper,
111				     struct container_context *cntrs)
112{
113	snd_pcm_sframes_t avail;
114	snd_pcm_uframes_t avail_count;
115	int err = 0;
116
117	if (status == SND_PCM_STATE_RUNNING) {
118		// Check available space on the buffer.
119		avail = snd_pcm_avail(state->handle);
120		if (avail < 0) {
121			err = avail;
122			goto error;
123		}
124		avail_count = (snd_pcm_uframes_t)avail;
125
126		if (avail_count == 0) {
127			// Request data frames so that blocking is just
128			// released.
129			err = snd_pcm_sw_params_get_avail_min(state->sw_params,
130							      &avail_count);
131			if (err < 0)
132				goto error;
133		}
134	} else {
135		// Request data frames so that the PCM substream starts.
136		snd_pcm_uframes_t frame_count;
137		err = snd_pcm_sw_params_get_start_threshold(state->sw_params,
138							    &frame_count);
139		if (err < 0)
140			goto error;
141
142		avail_count = (unsigned int)frame_count;
143	}
144
145	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
146	if (err < 0)
147		goto error;
148
149	return 0;
150error:
151	*frame_count = 0;
152	return err;
153}
154
155static int r_process_frames_nonblocking(struct libasound_state *state,
156					snd_pcm_state_t status,
157					unsigned int *frame_count,
158					struct mapper_context *mapper,
159					struct container_context *cntrs)
160{
161	snd_pcm_sframes_t avail;
162	snd_pcm_uframes_t avail_count;
163	int err = 0;
164
165	if (status != SND_PCM_STATE_RUNNING) {
166		err = snd_pcm_start(state->handle);
167		if (err < 0)
168			goto error;
169	}
170
171	if (state->use_waiter) {
172		err = wait_for_avail(state);
173		if (err < 0)
174			goto error;
175	}
176
177	// Check available space on the buffer.
178	avail = snd_pcm_avail(state->handle);
179	if (avail < 0) {
180		err = avail;
181		goto error;
182	}
183	avail_count = (snd_pcm_uframes_t)avail;
184
185	if (avail_count == 0) {
186		// Let's go to a next iteration.
187		err = 0;
188		goto error;
189	}
190
191	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
192	if (err < 0)
193		goto error;
194
195	return 0;
196error:
197	*frame_count = 0;
198	return err;
199}
200
201static int write_frames(struct libasound_state *state,
202			unsigned int *frame_count, unsigned int avail_count,
203			struct mapper_context *mapper,
204			struct container_context *cntrs)
205{
206	struct rw_closure *closure = state->private_data;
207	snd_pcm_uframes_t consumed_count;
208	snd_pcm_sframes_t handled_frame_count;
209	int err;
210
211	// Trim according up to expected frame count.
212	if (*frame_count < avail_count)
213		avail_count = *frame_count;
214
215	// Cache required amount of frames.
216	if (avail_count > frame_cache_get_count(&closure->cache)) {
217		avail_count -= frame_cache_get_count(&closure->cache);
218
219		// Read frames to transfer.
220		err = mapper_context_process_frames(mapper,
221				closure->cache.buf_ptr, &avail_count, cntrs);
222		if (err < 0)
223			return err;
224		frame_cache_increase_count(&closure->cache, avail_count);
225		avail_count = frame_cache_get_count(&closure->cache);
226	}
227
228	// Execute write operation according to the shape of buffer. These
229	// operations automatically start the stream.
230	consumed_count = avail_count;
231	if (closure->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
232		handled_frame_count = snd_pcm_writei(state->handle,
233					closure->cache.buf, consumed_count);
234	} else {
235		handled_frame_count = snd_pcm_writen(state->handle,
236					closure->cache.buf, consumed_count);
237	}
238	if (handled_frame_count < 0) {
239		err = handled_frame_count;
240		return err;
241	}
242
243	consumed_count = handled_frame_count;
244	frame_cache_reduce(&closure->cache, consumed_count);
245
246	*frame_count = consumed_count;
247
248	return 0;
249}
250
251static int w_process_frames_blocking(struct libasound_state *state,
252				     snd_pcm_state_t status,
253				     unsigned int *frame_count,
254				     struct mapper_context *mapper,
255				     struct container_context *cntrs)
256{
257	snd_pcm_sframes_t avail;
258	unsigned int avail_count;
259	int err;
260
261	if (status == SND_PCM_STATE_RUNNING) {
262		// Check available space on the buffer.
263		avail = snd_pcm_avail(state->handle);
264		if (avail < 0) {
265			err = avail;
266			goto error;
267		}
268		avail_count = (unsigned int)avail;
269
270		if (avail_count == 0) {
271			// Fill with data frames so that blocking is just
272			// released.
273			snd_pcm_uframes_t avail_min;
274			err = snd_pcm_sw_params_get_avail_min(state->sw_params,
275							      &avail_min);
276			if (err < 0)
277				goto error;
278			avail_count = (unsigned int)avail_min;
279		}
280	} else {
281		snd_pcm_uframes_t frames_for_start_threshold;
282		snd_pcm_uframes_t frames_per_period;
283
284		// Fill with data frames so that the PCM substream starts.
285		err = snd_pcm_sw_params_get_start_threshold(state->sw_params,
286						&frames_for_start_threshold);
287		if (err < 0)
288			goto error;
289
290		// But the above number can be too small and cause XRUN because
291		// I/O operation is done per period.
292		err = snd_pcm_hw_params_get_period_size(state->hw_params,
293						&frames_per_period, NULL);
294		if (err < 0)
295			goto error;
296
297		// Use larger one to prevent from both of XRUN and successive
298		// blocking.
299		if (frames_for_start_threshold > frames_per_period)
300			avail_count = (unsigned int)frames_for_start_threshold;
301		else
302			avail_count = (unsigned int)frames_per_period;
303	}
304
305	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
306	if (err < 0)
307		goto error;
308
309	return 0;
310error:
311	*frame_count = 0;
312	return err;
313}
314
315static int w_process_frames_nonblocking(struct libasound_state *state,
316					snd_pcm_state_t pcm_state ATTRIBUTE_UNUSED,
317					unsigned int *frame_count,
318					struct mapper_context *mapper,
319					struct container_context *cntrs)
320{
321	snd_pcm_sframes_t avail;
322	unsigned int avail_count;
323	int err;
324
325	if (state->use_waiter) {
326		err = wait_for_avail(state);
327		if (err < 0)
328			goto error;
329	}
330
331	// Check available space on the buffer.
332	avail = snd_pcm_avail(state->handle);
333	if (avail < 0) {
334		err = avail;
335		goto error;
336	}
337	avail_count = (unsigned int)avail;
338
339	if (avail_count == 0) {
340		// Let's go to a next iteration.
341		err = 0;
342		goto error;
343	}
344
345	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
346	if (err < 0)
347		goto error;
348
349	// NOTE: The substream starts automatically when the accumulated number
350	// of queued data frame exceeds start_threshold.
351
352	return 0;
353error:
354	*frame_count = 0;
355	return err;
356}
357
358static int irq_rw_pre_process(struct libasound_state *state)
359{
360	struct rw_closure *closure = state->private_data;
361	snd_pcm_format_t format;
362	snd_pcm_uframes_t frames_per_buffer;
363	int bytes_per_sample;
364	unsigned int samples_per_frame;
365	int err;
366
367	err = snd_pcm_hw_params_get_format(state->hw_params, &format);
368	if (err < 0)
369		return err;
370	bytes_per_sample = snd_pcm_format_physical_width(format) / 8;
371	if (bytes_per_sample <= 0)
372		return -ENXIO;
373
374	err = snd_pcm_hw_params_get_channels(state->hw_params,
375					     &samples_per_frame);
376	if (err < 0)
377		return err;
378
379	err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
380						&frames_per_buffer);
381	if (err < 0)
382		return err;
383
384	err = snd_pcm_hw_params_get_access(state->hw_params, &closure->access);
385	if (err < 0)
386		return err;
387
388	err = frame_cache_init(&closure->cache, closure->access,
389			       bytes_per_sample, samples_per_frame,
390			       frames_per_buffer);
391	if (err < 0)
392		return err;
393
394	if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) {
395		if (state->nonblock)
396			closure->process_frames = r_process_frames_nonblocking;
397		else
398			closure->process_frames = r_process_frames_blocking;
399	} else {
400		if (state->nonblock)
401			closure->process_frames = w_process_frames_nonblocking;
402		else
403			closure->process_frames = w_process_frames_blocking;
404	}
405
406	return 0;
407}
408
409static int irq_rw_process_frames(struct libasound_state *state,
410				unsigned int *frame_count,
411				struct mapper_context *mapper,
412				struct container_context *cntrs)
413{
414	struct rw_closure *closure = state->private_data;
415	snd_pcm_state_t status;
416
417	// Need to recover the stream.
418	status = snd_pcm_state(state->handle);
419	if (status != SND_PCM_STATE_RUNNING && status != SND_PCM_STATE_PREPARED)
420		return -EPIPE;
421
422	// NOTE: Actually, status can be shift always.
423	return closure->process_frames(state, status, frame_count, mapper, cntrs);
424}
425
426static void irq_rw_post_process(struct libasound_state *state)
427{
428	struct rw_closure *closure = state->private_data;
429
430	frame_cache_destroy(&closure->cache);
431}
432
433const struct xfer_libasound_ops xfer_libasound_irq_rw_ops = {
434	.pre_process	= irq_rw_pre_process,
435	.process_frames	= irq_rw_process_frames,
436	.post_process	= irq_rw_post_process,
437	.private_size	= sizeof(struct rw_closure),
438};
439