1c72fcc34Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
2c72fcc34Sopenharmony_ci//
3c72fcc34Sopenharmony_ci// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation.
4c72fcc34Sopenharmony_ci//
5c72fcc34Sopenharmony_ci// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6c72fcc34Sopenharmony_ci//
7c72fcc34Sopenharmony_ci// Licensed under the terms of the GNU General Public License, version 2.
8c72fcc34Sopenharmony_ci
9c72fcc34Sopenharmony_ci#include "xfer-libasound.h"
10c72fcc34Sopenharmony_ci#include "misc.h"
11c72fcc34Sopenharmony_ci
12c72fcc34Sopenharmony_cistruct map_layout {
13c72fcc34Sopenharmony_ci	snd_pcm_status_t *status;
14c72fcc34Sopenharmony_ci
15c72fcc34Sopenharmony_ci	char **vector;
16c72fcc34Sopenharmony_ci	unsigned int samples_per_frame;
17c72fcc34Sopenharmony_ci};
18c72fcc34Sopenharmony_ci
19c72fcc34Sopenharmony_cistatic int irq_mmap_pre_process(struct libasound_state *state)
20c72fcc34Sopenharmony_ci{
21c72fcc34Sopenharmony_ci	struct map_layout *layout = state->private_data;
22c72fcc34Sopenharmony_ci	snd_pcm_access_t access;
23c72fcc34Sopenharmony_ci	snd_pcm_uframes_t frame_offset;
24c72fcc34Sopenharmony_ci	snd_pcm_uframes_t avail = 0;
25c72fcc34Sopenharmony_ci	int i;
26c72fcc34Sopenharmony_ci	int err;
27c72fcc34Sopenharmony_ci
28c72fcc34Sopenharmony_ci	err = snd_pcm_status_malloc(&layout->status);
29c72fcc34Sopenharmony_ci	if (err < 0)
30c72fcc34Sopenharmony_ci		return err;
31c72fcc34Sopenharmony_ci
32c72fcc34Sopenharmony_ci	err = snd_pcm_hw_params_get_access(state->hw_params, &access);
33c72fcc34Sopenharmony_ci	if (err < 0)
34c72fcc34Sopenharmony_ci		return err;
35c72fcc34Sopenharmony_ci
36c72fcc34Sopenharmony_ci	err = snd_pcm_hw_params_get_channels(state->hw_params,
37c72fcc34Sopenharmony_ci					     &layout->samples_per_frame);
38c72fcc34Sopenharmony_ci	if (err < 0)
39c72fcc34Sopenharmony_ci		return err;
40c72fcc34Sopenharmony_ci
41c72fcc34Sopenharmony_ci	if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
42c72fcc34Sopenharmony_ci		layout->vector = calloc(layout->samples_per_frame,
43c72fcc34Sopenharmony_ci					sizeof(*layout->vector));
44c72fcc34Sopenharmony_ci		if (layout->vector == NULL)
45c72fcc34Sopenharmony_ci			return err;
46c72fcc34Sopenharmony_ci	}
47c72fcc34Sopenharmony_ci
48c72fcc34Sopenharmony_ci	if (state->verbose) {
49c72fcc34Sopenharmony_ci		const snd_pcm_channel_area_t *areas;
50c72fcc34Sopenharmony_ci		err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
51c72fcc34Sopenharmony_ci					 &avail);
52c72fcc34Sopenharmony_ci		if (err < 0)
53c72fcc34Sopenharmony_ci			return err;
54c72fcc34Sopenharmony_ci
55c72fcc34Sopenharmony_ci		logging(state, "attributes for mapped page frame:\n");
56c72fcc34Sopenharmony_ci		for (i = 0; i < (int)layout->samples_per_frame; ++i) {
57c72fcc34Sopenharmony_ci			const snd_pcm_channel_area_t *area = areas + i;
58c72fcc34Sopenharmony_ci
59c72fcc34Sopenharmony_ci			logging(state, "  sample number: %d\n", i);
60c72fcc34Sopenharmony_ci			logging(state, "    address: %p\n", area->addr);
61c72fcc34Sopenharmony_ci			logging(state, "    bits for offset: %u\n", area->first);
62c72fcc34Sopenharmony_ci			logging(state, "    bits/frame: %u\n", area->step);
63c72fcc34Sopenharmony_ci		}
64c72fcc34Sopenharmony_ci		logging(state, "\n");
65c72fcc34Sopenharmony_ci	}
66c72fcc34Sopenharmony_ci
67c72fcc34Sopenharmony_ci	return 0;
68c72fcc34Sopenharmony_ci}
69c72fcc34Sopenharmony_ci
70c72fcc34Sopenharmony_cistatic int irq_mmap_process_frames(struct libasound_state *state,
71c72fcc34Sopenharmony_ci				   unsigned int *frame_count,
72c72fcc34Sopenharmony_ci				   struct mapper_context *mapper,
73c72fcc34Sopenharmony_ci				   struct container_context *cntrs)
74c72fcc34Sopenharmony_ci{
75c72fcc34Sopenharmony_ci	struct map_layout *layout = state->private_data;
76c72fcc34Sopenharmony_ci	const snd_pcm_channel_area_t *areas;
77c72fcc34Sopenharmony_ci	snd_pcm_uframes_t frame_offset;
78c72fcc34Sopenharmony_ci	snd_pcm_uframes_t avail;
79c72fcc34Sopenharmony_ci	unsigned int avail_count;
80c72fcc34Sopenharmony_ci	void *frame_buf;
81c72fcc34Sopenharmony_ci	snd_pcm_sframes_t consumed_count;
82c72fcc34Sopenharmony_ci	int err;
83c72fcc34Sopenharmony_ci
84c72fcc34Sopenharmony_ci	if (state->use_waiter) {
85c72fcc34Sopenharmony_ci		unsigned int msec_per_buffer;
86c72fcc34Sopenharmony_ci		unsigned short revents;
87c72fcc34Sopenharmony_ci
88c72fcc34Sopenharmony_ci		// Wait during msec equivalent to all audio data frames in
89c72fcc34Sopenharmony_ci		// buffer instead of period, for safe.
90c72fcc34Sopenharmony_ci		err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
91c72fcc34Sopenharmony_ci							&msec_per_buffer, NULL);
92c72fcc34Sopenharmony_ci		if (err < 0)
93c72fcc34Sopenharmony_ci			return err;
94c72fcc34Sopenharmony_ci		msec_per_buffer /= 1000;
95c72fcc34Sopenharmony_ci
96c72fcc34Sopenharmony_ci		// Wait for hardware IRQ when no avail space in buffer.
97c72fcc34Sopenharmony_ci		err = xfer_libasound_wait_event(state, msec_per_buffer,
98c72fcc34Sopenharmony_ci						&revents);
99c72fcc34Sopenharmony_ci		if (err == -ETIMEDOUT) {
100c72fcc34Sopenharmony_ci			logging(state,
101c72fcc34Sopenharmony_ci				"No event occurs for PCM substream during %u "
102c72fcc34Sopenharmony_ci				"msec. The implementaion of kernel driver or "
103c72fcc34Sopenharmony_ci				"userland backend causes this issue.\n",
104c72fcc34Sopenharmony_ci				msec_per_buffer);
105c72fcc34Sopenharmony_ci			return err;
106c72fcc34Sopenharmony_ci		}
107c72fcc34Sopenharmony_ci		if (err < 0)
108c72fcc34Sopenharmony_ci			return err;
109c72fcc34Sopenharmony_ci		if (revents & POLLERR) {
110c72fcc34Sopenharmony_ci			// TODO: error reporting?
111c72fcc34Sopenharmony_ci			return -EIO;
112c72fcc34Sopenharmony_ci		}
113c72fcc34Sopenharmony_ci		if (!(revents & (POLLIN | POLLOUT)))
114c72fcc34Sopenharmony_ci			return -EAGAIN;
115c72fcc34Sopenharmony_ci
116c72fcc34Sopenharmony_ci		// When rescheduled, current position of data transmission was
117c72fcc34Sopenharmony_ci		// queried to actual hardware by a handler of IRQ. No need to
118c72fcc34Sopenharmony_ci		// perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC.
119c72fcc34Sopenharmony_ci	}
120c72fcc34Sopenharmony_ci
121c72fcc34Sopenharmony_ci	// Sync cache in user space to data in kernel space to calculate avail
122c72fcc34Sopenharmony_ci	// frames according to the latest positions on PCM buffer.
123c72fcc34Sopenharmony_ci	//
124c72fcc34Sopenharmony_ci	// This has an additional advantage to handle libasound PCM plugins.
125c72fcc34Sopenharmony_ci	// Most of libasound PCM plugins perform resampling in .avail_update()
126c72fcc34Sopenharmony_ci	// callback for capture PCM substream, then update positions on buffer.
127c72fcc34Sopenharmony_ci	//
128c72fcc34Sopenharmony_ci	// MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can
129c72fcc34Sopenharmony_ci	// return the same number of available frames.
130c72fcc34Sopenharmony_ci	avail = snd_pcm_avail_update(state->handle);
131c72fcc34Sopenharmony_ci	if ((snd_pcm_sframes_t)avail < 0)
132c72fcc34Sopenharmony_ci		return (int)avail;
133c72fcc34Sopenharmony_ci	if (*frame_count < avail)
134c72fcc34Sopenharmony_ci		avail = *frame_count;
135c72fcc34Sopenharmony_ci
136c72fcc34Sopenharmony_ci	err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
137c72fcc34Sopenharmony_ci	if (err < 0)
138c72fcc34Sopenharmony_ci		return err;
139c72fcc34Sopenharmony_ci
140c72fcc34Sopenharmony_ci	// Trim according up to expected frame count.
141c72fcc34Sopenharmony_ci	if (*frame_count < avail)
142c72fcc34Sopenharmony_ci		avail_count = *frame_count;
143c72fcc34Sopenharmony_ci	else
144c72fcc34Sopenharmony_ci		avail_count = (unsigned int)avail;
145c72fcc34Sopenharmony_ci
146c72fcc34Sopenharmony_ci	// TODO: Perhaps, the complex layout can be supported as a variation of
147c72fcc34Sopenharmony_ci	// vector type. However, there's no driver with this layout.
148c72fcc34Sopenharmony_ci	if (layout->vector == NULL) {
149c72fcc34Sopenharmony_ci		char *buf;
150c72fcc34Sopenharmony_ci		buf = areas[0].addr;
151c72fcc34Sopenharmony_ci		buf += snd_pcm_frames_to_bytes(state->handle, frame_offset);
152c72fcc34Sopenharmony_ci		frame_buf = buf;
153c72fcc34Sopenharmony_ci	} else {
154c72fcc34Sopenharmony_ci		int i;
155c72fcc34Sopenharmony_ci		for (i = 0; i < (int)layout->samples_per_frame; ++i) {
156c72fcc34Sopenharmony_ci			layout->vector[i] = areas[i].addr;
157c72fcc34Sopenharmony_ci			layout->vector[i] += snd_pcm_samples_to_bytes(
158c72fcc34Sopenharmony_ci						state->handle, frame_offset);
159c72fcc34Sopenharmony_ci		}
160c72fcc34Sopenharmony_ci		frame_buf = layout->vector;
161c72fcc34Sopenharmony_ci	}
162c72fcc34Sopenharmony_ci
163c72fcc34Sopenharmony_ci	err = mapper_context_process_frames(mapper, frame_buf, &avail_count,
164c72fcc34Sopenharmony_ci					    cntrs);
165c72fcc34Sopenharmony_ci	if (err < 0)
166c72fcc34Sopenharmony_ci		return err;
167c72fcc34Sopenharmony_ci	if (avail_count == 0) {
168c72fcc34Sopenharmony_ci		*frame_count = 0;
169c72fcc34Sopenharmony_ci		return 0;
170c72fcc34Sopenharmony_ci	}
171c72fcc34Sopenharmony_ci
172c72fcc34Sopenharmony_ci	consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
173c72fcc34Sopenharmony_ci					     avail_count);
174c72fcc34Sopenharmony_ci	if (consumed_count < 0)
175c72fcc34Sopenharmony_ci		return (int)consumed_count;
176c72fcc34Sopenharmony_ci	if (consumed_count != avail_count)
177c72fcc34Sopenharmony_ci		logging(state, "A bug of access plugin for this PCM node.\n");
178c72fcc34Sopenharmony_ci
179c72fcc34Sopenharmony_ci	*frame_count = consumed_count;
180c72fcc34Sopenharmony_ci
181c72fcc34Sopenharmony_ci	return 0;
182c72fcc34Sopenharmony_ci}
183c72fcc34Sopenharmony_ci
184c72fcc34Sopenharmony_cistatic int irq_mmap_r_process_frames(struct libasound_state *state,
185c72fcc34Sopenharmony_ci				     unsigned *frame_count,
186c72fcc34Sopenharmony_ci				     struct mapper_context *mapper,
187c72fcc34Sopenharmony_ci				     struct container_context *cntrs)
188c72fcc34Sopenharmony_ci{
189c72fcc34Sopenharmony_ci	struct map_layout *layout = state->private_data;
190c72fcc34Sopenharmony_ci	snd_pcm_state_t s;
191c72fcc34Sopenharmony_ci	int err;
192c72fcc34Sopenharmony_ci
193c72fcc34Sopenharmony_ci	// To querying current status of hardware, we need to care of
194c72fcc34Sopenharmony_ci	// synchronization between 3 levels:
195c72fcc34Sopenharmony_ci	//  1. status to actual hardware by driver.
196c72fcc34Sopenharmony_ci	//  2. status data in kernel space.
197c72fcc34Sopenharmony_ci	//  3. status data in user space.
198c72fcc34Sopenharmony_ci	//
199c72fcc34Sopenharmony_ci	// Kernel driver query 1 and sync 2, according to requests of some
200c72fcc34Sopenharmony_ci	// ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core
201c72fcc34Sopenharmony_ci	// supports mmap(2) operation on cache coherent architectures, some
202c72fcc34Sopenharmony_ci	// ioctl(2) commands on cache incoherent architecture. In usage of the
203c72fcc34Sopenharmony_ci	// former mechanism, we need to care of concurrent access by IRQ context
204c72fcc34Sopenharmony_ci	// and process context to the mapped page frame.
205c72fcc34Sopenharmony_ci	// In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and
206c72fcc34Sopenharmony_ci	// SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because
207c72fcc34Sopenharmony_ci	// mapped page frame is unused regardless of architectures in a point of
208c72fcc34Sopenharmony_ci	// cache coherency.
209c72fcc34Sopenharmony_ci	err = snd_pcm_status(state->handle, layout->status);
210c72fcc34Sopenharmony_ci	if (err < 0)
211c72fcc34Sopenharmony_ci		goto error;
212c72fcc34Sopenharmony_ci	s = snd_pcm_status_get_state(layout->status);
213c72fcc34Sopenharmony_ci
214c72fcc34Sopenharmony_ci	// TODO: if reporting something, do here with the status data.
215c72fcc34Sopenharmony_ci
216c72fcc34Sopenharmony_ci	// For capture direction, need to start stream explicitly.
217c72fcc34Sopenharmony_ci	if (s != SND_PCM_STATE_RUNNING) {
218c72fcc34Sopenharmony_ci		if (s != SND_PCM_STATE_PREPARED) {
219c72fcc34Sopenharmony_ci			err = -EPIPE;
220c72fcc34Sopenharmony_ci			goto error;
221c72fcc34Sopenharmony_ci		}
222c72fcc34Sopenharmony_ci
223c72fcc34Sopenharmony_ci		err = snd_pcm_start(state->handle);
224c72fcc34Sopenharmony_ci		if (err < 0)
225c72fcc34Sopenharmony_ci			goto error;
226c72fcc34Sopenharmony_ci	}
227c72fcc34Sopenharmony_ci
228c72fcc34Sopenharmony_ci	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
229c72fcc34Sopenharmony_ci	if (err < 0)
230c72fcc34Sopenharmony_ci		goto error;
231c72fcc34Sopenharmony_ci
232c72fcc34Sopenharmony_ci	return 0;
233c72fcc34Sopenharmony_cierror:
234c72fcc34Sopenharmony_ci	*frame_count = 0;
235c72fcc34Sopenharmony_ci	return err;
236c72fcc34Sopenharmony_ci}
237c72fcc34Sopenharmony_ci
238c72fcc34Sopenharmony_cistatic int irq_mmap_w_process_frames(struct libasound_state *state,
239c72fcc34Sopenharmony_ci				     unsigned *frame_count,
240c72fcc34Sopenharmony_ci				     struct mapper_context *mapper,
241c72fcc34Sopenharmony_ci				     struct container_context *cntrs)
242c72fcc34Sopenharmony_ci{
243c72fcc34Sopenharmony_ci	struct map_layout *layout = state->private_data;
244c72fcc34Sopenharmony_ci	snd_pcm_state_t s;
245c72fcc34Sopenharmony_ci	int err;
246c72fcc34Sopenharmony_ci
247c72fcc34Sopenharmony_ci	// Read my comment in 'irq_mmap_r_process_frames().
248c72fcc34Sopenharmony_ci	err = snd_pcm_status(state->handle, layout->status);
249c72fcc34Sopenharmony_ci	if (err < 0)
250c72fcc34Sopenharmony_ci		goto error;
251c72fcc34Sopenharmony_ci	s = snd_pcm_status_get_state(layout->status);
252c72fcc34Sopenharmony_ci
253c72fcc34Sopenharmony_ci	// TODO: if reporting something, do here with the status data.
254c72fcc34Sopenharmony_ci
255c72fcc34Sopenharmony_ci	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
256c72fcc34Sopenharmony_ci	if (err < 0)
257c72fcc34Sopenharmony_ci		goto error;
258c72fcc34Sopenharmony_ci
259c72fcc34Sopenharmony_ci	// Need to start playback stream explicitly
260c72fcc34Sopenharmony_ci	if (s != SND_PCM_STATE_RUNNING) {
261c72fcc34Sopenharmony_ci		if (s != SND_PCM_STATE_PREPARED) {
262c72fcc34Sopenharmony_ci			err = -EPIPE;
263c72fcc34Sopenharmony_ci			goto error;
264c72fcc34Sopenharmony_ci		}
265c72fcc34Sopenharmony_ci
266c72fcc34Sopenharmony_ci		err = snd_pcm_start(state->handle);
267c72fcc34Sopenharmony_ci		if (err < 0)
268c72fcc34Sopenharmony_ci			goto error;
269c72fcc34Sopenharmony_ci	}
270c72fcc34Sopenharmony_ci
271c72fcc34Sopenharmony_ci	return 0;
272c72fcc34Sopenharmony_cierror:
273c72fcc34Sopenharmony_ci	*frame_count = 0;
274c72fcc34Sopenharmony_ci	return err;
275c72fcc34Sopenharmony_ci}
276c72fcc34Sopenharmony_ci
277c72fcc34Sopenharmony_cistatic void irq_mmap_post_process(struct libasound_state *state)
278c72fcc34Sopenharmony_ci{
279c72fcc34Sopenharmony_ci	struct map_layout *layout = state->private_data;
280c72fcc34Sopenharmony_ci
281c72fcc34Sopenharmony_ci	if (layout->status)
282c72fcc34Sopenharmony_ci		snd_pcm_status_free(layout->status);
283c72fcc34Sopenharmony_ci	layout->status = NULL;
284c72fcc34Sopenharmony_ci
285c72fcc34Sopenharmony_ci	free(layout->vector);
286c72fcc34Sopenharmony_ci	layout->vector = NULL;
287c72fcc34Sopenharmony_ci}
288c72fcc34Sopenharmony_ci
289c72fcc34Sopenharmony_ciconst struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = {
290c72fcc34Sopenharmony_ci	.pre_process	= irq_mmap_pre_process,
291c72fcc34Sopenharmony_ci	.process_frames	= irq_mmap_w_process_frames,
292c72fcc34Sopenharmony_ci	.post_process	= irq_mmap_post_process,
293c72fcc34Sopenharmony_ci	.private_size	= sizeof(struct map_layout),
294c72fcc34Sopenharmony_ci};
295c72fcc34Sopenharmony_ci
296c72fcc34Sopenharmony_ciconst struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = {
297c72fcc34Sopenharmony_ci	.pre_process	= irq_mmap_pre_process,
298c72fcc34Sopenharmony_ci	.process_frames	= irq_mmap_r_process_frames,
299c72fcc34Sopenharmony_ci	.post_process	= irq_mmap_post_process,
300c72fcc34Sopenharmony_ci	.private_size	= sizeof(struct map_layout),
301c72fcc34Sopenharmony_ci};
302